about summary refs log tree commit diff
path: root/nixpkgs/nixos
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2023-06-16 06:56:35 +0000
committerAlyssa Ross <hi@alyssa.is>2023-06-16 06:56:35 +0000
commit99fcaeccb89621dd492203ce1f2d551c06f228ed (patch)
tree41cb730ae07383004789779b0f6e11cb3f4642a3 /nixpkgs/nixos
parent59c5f5ac8682acc13bb22bc29c7cf02f7d75f01f (diff)
parent75a5ebf473cd60148ba9aec0d219f72e5cf52519 (diff)
downloadnixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.tar
nixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.tar.gz
nixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.tar.bz2
nixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.tar.lz
nixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.tar.xz
nixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.tar.zst
nixlib-99fcaeccb89621dd492203ce1f2d551c06f228ed.zip
Merge branch 'nixos-unstable' of https://github.com/NixOS/nixpkgs
Conflicts:
	nixpkgs/nixos/modules/config/console.nix
	nixpkgs/nixos/modules/services/mail/mailman.nix
	nixpkgs/nixos/modules/services/mail/public-inbox.nix
	nixpkgs/nixos/modules/services/mail/rss2email.nix
	nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
	nixpkgs/pkgs/applications/networking/instant-messengers/dino/default.nix
	nixpkgs/pkgs/applications/networking/irc/weechat/default.nix
	nixpkgs/pkgs/applications/window-managers/sway/default.nix
	nixpkgs/pkgs/build-support/go/module.nix
	nixpkgs/pkgs/build-support/rust/build-rust-package/default.nix
	nixpkgs/pkgs/development/interpreters/python/default.nix
	nixpkgs/pkgs/development/node-packages/overrides.nix
	nixpkgs/pkgs/development/tools/b4/default.nix
	nixpkgs/pkgs/servers/dict/dictd-db.nix
	nixpkgs/pkgs/servers/mail/public-inbox/default.nix
	nixpkgs/pkgs/tools/security/pinentry/default.nix
	nixpkgs/pkgs/tools/text/unoconv/default.nix
	nixpkgs/pkgs/top-level/all-packages.nix
Diffstat (limited to 'nixpkgs/nixos')
-rw-r--r--nixpkgs/nixos/doc/manual/.gitignore2
-rw-r--r--nixpkgs/nixos/doc/manual/Makefile30
-rw-r--r--nixpkgs/nixos/doc/manual/administration/containers.chapter.md8
-rw-r--r--nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/administration/running.md14
-rw-r--r--nixpkgs/nixos/doc/manual/administration/running.xml21
-rw-r--r--nixpkgs/nixos/doc/manual/administration/service-mgmt.chapter.md12
-rw-r--r--nixpkgs/nixos/doc/manual/administration/troubleshooting.chapter.md12
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/adding-custom-packages.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/config-file.section.md4
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/config-syntax.chapter.md9
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/configuration.md27
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/configuration.xml31
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/customizing-packages.section.md23
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/declarative-packages.section.md6
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/file-systems.chapter.md6
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/gpu-accel.chapter.md34
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/kubernetes.chapter.md10
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/linux-kernel.chapter.md116
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/modularity.section.md14
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/networking.chapter.md18
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/package-mgmt.chapter.md6
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/profiles.chapter.md26
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/profiles/hardened.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/ssh.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/sshfs-file-systems.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/summary.section.md46
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/user-mgmt.chapter.md5
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/wayland.chapter.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/wireless.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/x-windows.chapter.md37
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/xfce.chapter.md12
-rw-r--r--nixpkgs/nixos/doc/manual/contributing-to-this-manual.chapter.md25
-rw-r--r--nixpkgs/nixos/doc/manual/default.nix319
-rw-r--r--nixpkgs/nixos/doc/manual/development/activation-script.section.md4
-rw-r--r--nixpkgs/nixos/doc/manual/development/bootspec.chapter.md36
-rw-r--r--nixpkgs/nixos/doc/manual/development/developing-the-test-driver.chapter.md45
-rw-r--r--nixpkgs/nixos/doc/manual/development/development.md15
-rw-r--r--nixpkgs/nixos/doc/manual/development/development.xml19
-rw-r--r--nixpkgs/nixos/doc/manual/development/freeform-modules.section.md5
-rw-r--r--nixpkgs/nixos/doc/manual/development/meta-attributes.section.md6
-rw-r--r--nixpkgs/nixos/doc/manual/development/nixos-tests.chapter.md10
-rw-r--r--nixpkgs/nixos/doc/manual/development/option-declarations.section.md99
-rw-r--r--nixpkgs/nixos/doc/manual/development/option-def.section.md32
-rw-r--r--nixpkgs/nixos/doc/manual/development/option-types.section.md130
-rw-r--r--nixpkgs/nixos/doc/manual/development/replace-modules.section.md17
-rw-r--r--nixpkgs/nixos/doc/manual/development/running-nixos-tests-interactively.section.md47
-rw-r--r--nixpkgs/nixos/doc/manual/development/running-nixos-tests.section.md17
-rw-r--r--nixpkgs/nixos/doc/manual/development/settings-options.section.md18
-rw-r--r--nixpkgs/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md6
-rw-r--r--nixpkgs/nixos/doc/manual/development/writing-documentation.chapter.md4
-rw-r--r--nixpkgs/nixos/doc/manual/development/writing-modules.chapter.md34
-rw-r--r--nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md111
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/README.md5
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/boot-problems.section.xml144
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml72
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/container-networking.section.xml54
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/containers.chapter.xml31
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/control-groups.chapter.xml67
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml60
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/imperative-containers.section.xml132
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/logging.chapter.xml45
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/maintenance-mode.section.xml14
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/network-problems.section.xml25
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/rebooting.chapter.xml38
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/rollback.section.xml42
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/service-mgmt.chapter.xml141
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/store-corruption.section.xml34
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml12
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/user-sessions.chapter.xml46
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/abstractions.section.xml101
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-network-config.section.xml16
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-packages.section.xml59
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml118
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/config-file.section.xml231
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml21
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/customizing-packages.section.xml90
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml53
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml55
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/firewall.section.xml39
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml239
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/ipv4-config.section.xml43
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/ipv6-config.section.xml47
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml126
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml157
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/luks-file-systems.section.xml84
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/modularity.section.xml152
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/network-manager.section.xml49
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/networking.chapter.xml15
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml28
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles.chapter.xml38
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/all-hardware.section.xml15
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/base.section.xml10
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/clone-config.section.xml16
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/demo.section.xml10
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/docker-container.section.xml12
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/graphical.section.xml14
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml25
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/headless.section.xml15
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/installation-device.section.xml32
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/minimal.section.xml13
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/profiles/qemu-guest.section.xml11
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml62
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/ssh.section.xml23
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/sshfs-file-systems.section.xml139
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/subversion.chapter.xml121
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/summary.section.xml332
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml105
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/wayland.chapter.xml31
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/wireless.section.xml73
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml381
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/xfce.chapter.xml68
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml22
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/activation-script.section.xml150
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/assertions.section.xml58
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/building-parts.chapter.xml124
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/freeform-modules.section.xml87
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/importing-modules.section.xml47
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/linking-nixos-tests-to-packages.section.xml10
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/meta-attributes.section.xml95
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml14
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/option-declarations.section.xml337
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/option-def.section.xml104
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/option-types.section.xml1081
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/replace-modules.section.xml70
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml39
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml34
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/settings-options.section.xml421
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/sources.chapter.xml90
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/testing-installer.chapter.xml22
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/unit-handling.section.xml131
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml122
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/writing-documentation.chapter.xml144
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/writing-modules.chapter.xml245
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml692
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml111
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/changing-config.chapter.xml117
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing-behind-a-proxy.section.xml41
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml388
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing-kexec.section.xml94
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing-pxe.section.xml42
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing-usb.section.xml35
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml92
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml665
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/obtaining.chapter.xml48
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml152
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1310.section.xml6
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1404.section.xml189
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1412.section.xml466
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1509.section.xml776
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml695
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1609.section.xml273
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1703.section.xml818
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml922
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1803.section.xml879
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1809.section.xml941
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml790
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml1197
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2003.section.xml1497
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml2210
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml1567
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml2092
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml2829
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml505
-rw-r--r--nixpkgs/nixos/doc/manual/installation/building-nixos.chapter.md4
-rw-r--r--nixpkgs/nixos/doc/manual/installation/changing-config.chapter.md4
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installation.md11
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installation.xml18
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installing-from-other-distro.section.md46
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installing-kexec.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installing-usb.section.md93
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installing-virtualbox-guest.section.md8
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installing.chapter.md239
-rw-r--r--nixpkgs/nixos/doc/manual/installation/obtaining.chapter.md19
-rw-r--r--nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md28
-rw-r--r--nixpkgs/nixos/doc/manual/man-configuration.xml31
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-build-vms.xml138
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-enter.xml154
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-generate-config.xml214
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-install.xml357
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-option.xml134
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml716
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-version.xml137
-rw-r--r--nixpkgs/nixos/doc/manual/man-pages.xml37
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/README.md57
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-build-vms.8105
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-enter.872
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-generate-config.8165
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-install.8191
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-option.889
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-rebuild.8452
-rw-r--r--nixpkgs/nixos/doc/manual/manpages/nixos-version.886
-rw-r--r--nixpkgs/nixos/doc/manual/manual.md56
-rw-r--r--nixpkgs/nixos/doc/manual/manual.xml24
-rwxr-xr-xnixpkgs/nixos/doc/manual/md-to-db.sh49
-rw-r--r--nixpkgs/nixos/doc/manual/nixos-options.md7
-rw-r--r--nixpkgs/nixos/doc/manual/preface.md11
-rw-r--r--nixpkgs/nixos/doc/manual/preface.xml42
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/release-notes.md26
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/release-notes.xml29
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1509.section.md12
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1603.section.md16
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1609.section.md6
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1703.section.md10
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1709.section.md20
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1803.section.md12
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1809.section.md10
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1903.section.md24
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-1909.section.md36
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2003.section.md58
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2009.section.md44
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2105.section.md40
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md12
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md56
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2211.section.md505
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2305.section.md662
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md45
-rw-r--r--nixpkgs/nixos/doc/manual/shell.nix8
-rwxr-xr-xnixpkgs/nixos/doc/varlistentry-fixer.rb124
-rw-r--r--nixpkgs/nixos/doc/xmlformat.conf72
-rw-r--r--nixpkgs/nixos/lib/build-vms.nix113
-rw-r--r--nixpkgs/nixos/lib/default.nix8
-rw-r--r--nixpkgs/nixos/lib/eval-cacheable-options.nix1
-rw-r--r--nixpkgs/nixos/lib/eval-config-minimal.nix1
-rw-r--r--nixpkgs/nixos/lib/eval-config.nix26
-rw-r--r--nixpkgs/nixos/lib/make-channel.nix2
-rw-r--r--nixpkgs/nixos/lib/make-disk-image.nix211
-rw-r--r--nixpkgs/nixos/lib/make-iso9660-image.nix6
-rw-r--r--nixpkgs/nixos/lib/make-iso9660-image.sh25
-rw-r--r--nixpkgs/nixos/lib/make-multi-disk-zfs-image.nix15
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/default.nix145
-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/mergeJSON.py228
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl283
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix6
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/sortXML.py27
-rw-r--r--nixpkgs/nixos/lib/make-single-disk-zfs-image.nix11
-rw-r--r--nixpkgs/nixos/lib/make-squashfs.nix1
-rw-r--r--nixpkgs/nixos/lib/make-system-tarball.nix2
-rw-r--r--nixpkgs/nixos/lib/qemu-common.nix62
-rw-r--r--nixpkgs/nixos/lib/systemd-lib.nix53
-rw-r--r--nixpkgs/nixos/lib/systemd-types.nix8
-rw-r--r--nixpkgs/nixos/lib/systemd-unit-options.nix230
-rw-r--r--nixpkgs/nixos/lib/test-driver/default.nix2
-rwxr-xr-xnixpkgs/nixos/lib/test-driver/test_driver/__init__.py6
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/driver.py35
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/logger.py8
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/machine.py304
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/polling_condition.py29
-rw-r--r--nixpkgs/nixos/lib/testing-python.nix273
-rw-r--r--nixpkgs/nixos/lib/testing/call-test.nix12
-rw-r--r--nixpkgs/nixos/lib/testing/default.nix27
-rw-r--r--nixpkgs/nixos/lib/testing/driver.nix181
-rw-r--r--nixpkgs/nixos/lib/testing/interactive.nix45
-rw-r--r--nixpkgs/nixos/lib/testing/legacy.nix26
-rw-r--r--nixpkgs/nixos/lib/testing/meta.nix42
-rw-r--r--nixpkgs/nixos/lib/testing/name.nix14
-rw-r--r--nixpkgs/nixos/lib/testing/network.nix131
-rw-r--r--nixpkgs/nixos/lib/testing/nixos-test-base.nix23
-rw-r--r--nixpkgs/nixos/lib/testing/nodes.nix149
-rw-r--r--nixpkgs/nixos/lib/testing/pkgs.nix11
-rw-r--r--nixpkgs/nixos/lib/testing/run.nix57
-rw-r--r--nixpkgs/nixos/lib/testing/testScript.nix84
-rw-r--r--nixpkgs/nixos/lib/utils.nix34
-rw-r--r--nixpkgs/nixos/maintainers/scripts/cloudstack/cloudstack-image.nix2
-rw-r--r--nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix25
-rw-r--r--nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix9
-rw-r--r--nixpkgs/nixos/maintainers/scripts/lxd/lxd-image.nix7
-rw-r--r--nixpkgs/nixos/maintainers/scripts/lxd/nix.tpl2
-rw-r--r--nixpkgs/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix8
-rw-r--r--nixpkgs/nixos/modules/config/console.nix47
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fontconfig.nix19
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fonts.nix38
-rw-r--r--nixpkgs/nixos/modules/config/gnu.nix1
-rw-r--r--nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix4
-rw-r--r--nixpkgs/nixos/modules/config/i18n.nix4
-rw-r--r--nixpkgs/nixos/modules/config/iproute2.nix2
-rw-r--r--nixpkgs/nixos/modules/config/krb5/default.nix16
-rw-r--r--nixpkgs/nixos/modules/config/ldap.nix12
-rw-r--r--nixpkgs/nixos/modules/config/malloc.nix22
-rw-r--r--nixpkgs/nixos/modules/config/mysql.nix233
-rw-r--r--nixpkgs/nixos/modules/config/networking.nix4
-rw-r--r--nixpkgs/nixos/modules/config/no-x-libs.nix36
-rw-r--r--nixpkgs/nixos/modules/config/power-management.nix2
-rw-r--r--nixpkgs/nixos/modules/config/pulseaudio.nix13
-rw-r--r--nixpkgs/nixos/modules/config/qt.nix (renamed from nixpkgs/nixos/modules/config/qt5.nix)42
-rw-r--r--nixpkgs/nixos/modules/config/resolvconf.nix4
-rw-r--r--nixpkgs/nixos/modules/config/shells-environment.nix8
-rw-r--r--nixpkgs/nixos/modules/config/stevenblack.nix34
-rw-r--r--nixpkgs/nixos/modules/config/swap.nix80
-rw-r--r--nixpkgs/nixos/modules/config/sysctl.nix16
-rw-r--r--nixpkgs/nixos/modules/config/system-environment.nix5
-rw-r--r--nixpkgs/nixos/modules/config/system-path.nix19
-rw-r--r--nixpkgs/nixos/modules/config/update-users-groups.pl14
-rw-r--r--nixpkgs/nixos/modules/config/users-groups.nix242
-rw-r--r--nixpkgs/nixos/modules/config/xdg/portal.nix28
-rw-r--r--nixpkgs/nixos/modules/config/xdg/portals/lxqt.nix12
-rw-r--r--nixpkgs/nixos/modules/config/xdg/portals/wlr.nix14
-rw-r--r--nixpkgs/nixos/modules/config/zram.nix196
-rw-r--r--nixpkgs/nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/brillo.nix9
-rw-r--r--nixpkgs/nixos/modules/hardware/ckb-next.nix4
-rw-r--r--nixpkgs/nixos/modules/hardware/corectrl.nix10
-rw-r--r--nixpkgs/nixos/modules/hardware/cpu/amd-sev.nix8
-rw-r--r--nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/device-tree.nix43
-rw-r--r--nixpkgs/nixos/modules/hardware/flipperzero.nix18
-rw-r--r--nixpkgs/nixos/modules/hardware/flirc.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/gkraken.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/gpgsmartcards.nix4
-rw-r--r--nixpkgs/nixos/modules/hardware/i2c.nix18
-rw-r--r--nixpkgs/nixos/modules/hardware/keyboard/qmk.nix16
-rw-r--r--nixpkgs/nixos/modules/hardware/keyboard/teck.nix6
-rw-r--r--nixpkgs/nixos/modules/hardware/keyboard/uhk.nix9
-rw-r--r--nixpkgs/nixos/modules/hardware/keyboard/zsa.nix19
-rw-r--r--nixpkgs/nixos/modules/hardware/ksm.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/ledger.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/logitech.nix4
-rw-r--r--nixpkgs/nixos/modules/hardware/nitrokey.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/opengl.nix38
-rw-r--r--nixpkgs/nixos/modules/hardware/openrazer.nix6
-rw-r--r--nixpkgs/nixos/modules/hardware/opentabletdriver.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/printers.nix21
-rw-r--r--nixpkgs/nixos/modules/hardware/raid/hpsa.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/saleae-logic.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/sata.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/sensor/hddtemp.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/system-76.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/tuxedo-keyboard.nix14
-rw-r--r--nixpkgs/nixos/modules/hardware/ubertooth.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/uinput.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/usb-storage.nix20
-rw-r--r--nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/video/hidpi.nix24
-rw-r--r--nixpkgs/nixos/modules/hardware/video/nvidia.nix133
-rw-r--r--nixpkgs/nixos/modules/hardware/video/switcheroo-control.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix4
-rw-r--r--nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/video/webcam/ipu6.nix57
-rw-r--r--nixpkgs/nixos/modules/hardware/wooting.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/xone.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/xpadneo.nix4
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/default.md160
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/default.nix25
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/default.xml291
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/fcitx.nix46
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/fcitx5.nix38
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/ibus.nix5
-rw-r--r--nixpkgs/nixos/modules/i18n/input-method/kime.nix68
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/channel.nix14
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix6
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix2
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix7
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix15
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix22
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix157
-rw-r--r--nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix15
-rw-r--r--nixpkgs/nixos/modules/installer/netboot/netboot.nix15
-rw-r--r--nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix15
-rw-r--r--nixpkgs/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix49
-rw-r--r--nixpkgs/nixos/modules/installer/sd-card/sd-image.nix43
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix2
-rwxr-xr-x[-rw-r--r--]nixpkgs/nixos/modules/installer/tools/nixos-enter.sh7
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl63
-rwxr-xr-x[-rw-r--r--]nixpkgs/nixos/modules/installer/tools/nixos-install.sh15
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-version.sh9
-rw-r--r--nixpkgs/nixos/modules/installer/tools/tools.nix19
-rw-r--r--nixpkgs/nixos/modules/misc/assertions.nix4
-rw-r--r--nixpkgs/nixos/modules/misc/documentation.nix67
-rw-r--r--nixpkgs/nixos/modules/misc/documentation/test.nix2
-rw-r--r--nixpkgs/nixos/modules/misc/ids.nix12
-rw-r--r--nixpkgs/nixos/modules/misc/label.nix25
-rw-r--r--nixpkgs/nixos/modules/misc/locate.nix4
-rw-r--r--nixpkgs/nixos/modules/misc/man-db.nix20
-rw-r--r--nixpkgs/nixos/modules/misc/mandoc.nix2
-rw-r--r--nixpkgs/nixos/modules/misc/meta.nix8
-rw-r--r--nixpkgs/nixos/modules/misc/nixops-autoluks.nix2
-rw-r--r--nixpkgs/nixos/modules/misc/nixpkgs.nix66
-rw-r--r--nixpkgs/nixos/modules/misc/nixpkgs/read-only.nix74
-rw-r--r--nixpkgs/nixos/modules/misc/nixpkgs/test.nix65
-rw-r--r--nixpkgs/nixos/modules/misc/passthru.nix2
-rw-r--r--nixpkgs/nixos/modules/misc/version.nix61
-rw-r--r--nixpkgs/nixos/modules/misc/wordlist.nix4
-rw-r--r--nixpkgs/nixos/modules/module-list.nix511
-rw-r--r--nixpkgs/nixos/modules/profiles/all-hardware.nix2
-rw-r--r--nixpkgs/nixos/modules/profiles/base.nix30
-rw-r--r--nixpkgs/nixos/modules/profiles/docker-container.nix11
-rw-r--r--nixpkgs/nixos/modules/profiles/installation-device.nix19
-rw-r--r--nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key7
-rw-r--r--nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub1
-rw-r--r--nixpkgs/nixos/modules/profiles/macos-builder.nix239
-rw-r--r--nixpkgs/nixos/modules/profiles/minimal.nix15
-rw-r--r--nixpkgs/nixos/modules/programs/_1password-gui.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/_1password.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/appgate-sdp.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/atop.nix5
-rw-r--r--nixpkgs/nixos/modules/programs/ausweisapp.nix25
-rw-r--r--nixpkgs/nixos/modules/programs/bash-my-aws.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/bash/bash-completion.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/bash/bash.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/bash/blesh.nix16
-rw-r--r--nixpkgs/nixos/modules/programs/bash/ls-colors.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/bash/undistract-me.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/bcc.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/browserpass.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/calls.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/captive-browser.nix8
-rw-r--r--nixpkgs/nixos/modules/programs/ccache.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/cfs-zen-tweaks.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/chromium.nix6
-rw-r--r--nixpkgs/nixos/modules/programs/clash-verge.nix41
-rw-r--r--nixpkgs/nixos/modules/programs/cnping.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/darling.nix21
-rw-r--r--nixpkgs/nixos/modules/programs/dconf.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/digitalbitbox/default.md47
-rw-r--r--nixpkgs/nixos/modules/programs/digitalbitbox/default.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml74
-rw-r--r--nixpkgs/nixos/modules/programs/droidcam.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/evince.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/extra-container.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/feedbackd.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/file-roller.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/firefox.nix268
-rw-r--r--nixpkgs/nixos/modules/programs/firejail.nix32
-rw-r--r--nixpkgs/nixos/modules/programs/fish.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/flashrom.nix6
-rw-r--r--nixpkgs/nixos/modules/programs/flexoptix-app.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/fzf.nix32
-rw-r--r--nixpkgs/nixos/modules/programs/gamemode.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/gamescope.nix85
-rw-r--r--nixpkgs/nixos/modules/programs/geary.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/git.nix42
-rw-r--r--nixpkgs/nixos/modules/programs/gnome-documents.nix54
-rw-r--r--nixpkgs/nixos/modules/programs/gnome-terminal.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/gnupg.nix17
-rw-r--r--nixpkgs/nixos/modules/programs/haguichi.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/hamster.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/htop.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/hyprland.nix81
-rw-r--r--nixpkgs/nixos/modules/programs/i3lock.nix58
-rw-r--r--nixpkgs/nixos/modules/programs/iay.nix37
-rw-r--r--nixpkgs/nixos/modules/programs/iftop.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/iotop.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/java.nix49
-rw-r--r--nixpkgs/nixos/modules/programs/k3b.nix18
-rw-r--r--nixpkgs/nixos/modules/programs/k40-whisperer.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/kbdlight.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/kclock.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/kdeconnect.nix8
-rw-r--r--nixpkgs/nixos/modules/programs/less.nix7
-rw-r--r--nixpkgs/nixos/modules/programs/liboping.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/mdevctl.nix18
-rw-r--r--nixpkgs/nixos/modules/programs/mepo.nix46
-rw-r--r--nixpkgs/nixos/modules/programs/mininet.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/minipro.nix29
-rw-r--r--nixpkgs/nixos/modules/programs/miriway.nix78
-rw-r--r--nixpkgs/nixos/modules/programs/mosh.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/msmtp.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/nano.nix13
-rw-r--r--nixpkgs/nixos/modules/programs/nbd.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/neovim.nix68
-rw-r--r--nixpkgs/nixos/modules/programs/nexttrace.nix25
-rw-r--r--nixpkgs/nixos/modules/programs/nix-index.nix62
-rw-r--r--nixpkgs/nixos/modules/programs/nix-ld.nix63
-rw-r--r--nixpkgs/nixos/modules/programs/nm-applet.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/nncp.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/noisetorch.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/npm.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/openvpn3.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/pantheon-tweaks.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/partition-manager.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/plotinus.md17
-rw-r--r--nixpkgs/nixos/modules/programs/plotinus.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/plotinus.xml30
-rw-r--r--nixpkgs/nixos/modules/programs/proxychains.nix12
-rw-r--r--nixpkgs/nixos/modules/programs/qdmr.nix25
-rw-r--r--nixpkgs/nixos/modules/programs/regreet.nix75
-rw-r--r--nixpkgs/nixos/modules/programs/rog-control-center.nix29
-rw-r--r--nixpkgs/nixos/modules/programs/rust-motd.nix92
-rw-r--r--nixpkgs/nixos/modules/programs/seahorse.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/sedutil.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/shadow.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/sharing.nix19
-rw-r--r--nixpkgs/nixos/modules/programs/singularity.nix102
-rw-r--r--nixpkgs/nixos/modules/programs/skim.nix34
-rw-r--r--nixpkgs/nixos/modules/programs/sniffnet.nix24
-rw-r--r--nixpkgs/nixos/modules/programs/ssh.nix20
-rw-r--r--nixpkgs/nixos/modules/programs/starship.nix27
-rw-r--r--nixpkgs/nixos/modules/programs/steam.nix108
-rw-r--r--nixpkgs/nixos/modules/programs/streamdeck-ui.nix16
-rw-r--r--nixpkgs/nixos/modules/programs/sysdig.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/system-config-printer.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/thefuck.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/thunar.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/tmux.nix40
-rw-r--r--nixpkgs/nixos/modules/programs/trippy.nix24
-rw-r--r--nixpkgs/nixos/modules/programs/tsm-client.nix36
-rw-r--r--nixpkgs/nixos/modules/programs/turbovnc.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/udevil.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/usbtop.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/vim.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/wayland/river.nix59
-rw-r--r--nixpkgs/nixos/modules/programs/wayland/sway.nix (renamed from nixpkgs/nixos/modules/programs/sway.nix)84
-rw-r--r--nixpkgs/nixos/modules/programs/wayland/waybar.nix (renamed from nixpkgs/nixos/modules/programs/waybar.nix)11
-rw-r--r--nixpkgs/nixos/modules/programs/wayland/wayland-session.nix23
-rw-r--r--nixpkgs/nixos/modules/programs/weylus.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/wireshark.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/wshowkeys.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/xastir.nix23
-rw-r--r--nixpkgs/nixos/modules/programs/xfconf.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/xfs_quota.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/xonsh.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/xss-lock.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/xwayland.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/yabar.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/zmap.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.md109
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix10
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml155
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix4
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix3
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh.nix13
-rw-r--r--nixpkgs/nixos/modules/rename.nix17
-rw-r--r--nixpkgs/nixos/modules/security/acme/default.md354
-rw-r--r--nixpkgs/nixos/modules/security/acme/default.nix51
-rw-r--r--nixpkgs/nixos/modules/security/acme/doc.xml413
-rw-r--r--nixpkgs/nixos/modules/security/apparmor.nix26
-rw-r--r--nixpkgs/nixos/modules/security/apparmor/includes.nix2
-rw-r--r--nixpkgs/nixos/modules/security/audit.nix2
-rw-r--r--nixpkgs/nixos/modules/security/auditd.nix2
-rw-r--r--nixpkgs/nixos/modules/security/dhparams.nix36
-rw-r--r--nixpkgs/nixos/modules/security/doas.nix20
-rw-r--r--nixpkgs/nixos/modules/security/ipa.nix258
-rw-r--r--nixpkgs/nixos/modules/security/misc.nix37
-rw-r--r--nixpkgs/nixos/modules/security/pam.nix223
-rw-r--r--nixpkgs/nixos/modules/security/pam_mount.nix20
-rw-r--r--nixpkgs/nixos/modules/security/please.nix122
-rw-r--r--nixpkgs/nixos/modules/security/polkit.nix10
-rw-r--r--nixpkgs/nixos/modules/security/sudo.nix10
-rw-r--r--nixpkgs/nixos/modules/security/systemd-confinement.nix35
-rw-r--r--nixpkgs/nixos/modules/security/tpm2.nix30
-rw-r--r--nixpkgs/nixos/modules/security/wrappers/default.nix37
-rw-r--r--nixpkgs/nixos/modules/services/admin/meshcentral.nix14
-rw-r--r--nixpkgs/nixos/modules/services/admin/oxidized.nix2
-rw-r--r--nixpkgs/nixos/modules/services/admin/pgadmin.nix76
-rw-r--r--nixpkgs/nixos/modules/services/admin/salt/master.nix2
-rw-r--r--nixpkgs/nixos/modules/services/admin/salt/minion.nix2
-rw-r--r--nixpkgs/nixos/modules/services/amqp/activemq/default.nix25
-rw-r--r--nixpkgs/nixos/modules/services/amqp/rabbitmq.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/botamusique.nix5
-rw-r--r--nixpkgs/nixos/modules/services/audio/gmediarender.nix116
-rw-r--r--nixpkgs/nixos/modules/services/audio/gonic.nix89
-rw-r--r--nixpkgs/nixos/modules/services/audio/hqplayerd.nix5
-rw-r--r--nixpkgs/nixos/modules/services/audio/icecast.nix6
-rw-r--r--nixpkgs/nixos/modules/services/audio/jack.nix6
-rw-r--r--nixpkgs/nixos/modules/services/audio/jmusicbot.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/liquidsoap.nix12
-rw-r--r--nixpkgs/nixos/modules/services/audio/mopidy.nix4
-rw-r--r--nixpkgs/nixos/modules/services/audio/mpd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/audio/mpdscribble.nix6
-rw-r--r--nixpkgs/nixos/modules/services/audio/navidrome.nix8
-rw-r--r--nixpkgs/nixos/modules/services/audio/networkaudiod.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/roon-bridge.nix11
-rw-r--r--nixpkgs/nixos/modules/services/audio/roon-server.nix10
-rw-r--r--nixpkgs/nixos/modules/services/audio/snapserver.nix13
-rw-r--r--nixpkgs/nixos/modules/services/audio/spotifyd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/squeezelite.nix4
-rw-r--r--nixpkgs/nixos/modules/services/audio/tts.nix151
-rw-r--r--nixpkgs/nixos/modules/services/audio/ympd.nix44
-rw-r--r--nixpkgs/nixos/modules/services/backup/automysqlbackup.nix23
-rw-r--r--nixpkgs/nixos/modules/services/backup/bacula.nix8
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.md163
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.nix70
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.xml209
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgmatic.nix87
-rw-r--r--nixpkgs/nixos/modules/services/backup/btrbk.nix159
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicati.nix16
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicity.nix10
-rw-r--r--nixpkgs/nixos/modules/services/backup/mysql-backup.nix4
-rw-r--r--nixpkgs/nixos/modules/services/backup/postgresql-backup.nix2
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic-rest-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic.nix51
-rw-r--r--nixpkgs/nixos/modules/services/backup/rsnapshot.nix2
-rw-r--r--nixpkgs/nixos/modules/services/backup/sanoid.nix10
-rw-r--r--nixpkgs/nixos/modules/services/backup/syncoid.nix8
-rw-r--r--nixpkgs/nixos/modules/services/backup/tarsnap.nix2
-rw-r--r--nixpkgs/nixos/modules/services/backup/tsm.nix6
-rw-r--r--nixpkgs/nixos/modules/services/backup/zfs-replication.nix6
-rw-r--r--nixpkgs/nixos/modules/services/backup/znapzend.nix78
-rw-r--r--nixpkgs/nixos/modules/services/backup/zrepl.nix2
-rw-r--r--nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix120
-rw-r--r--nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix52
-rw-r--r--nixpkgs/nixos/modules/services/blockchain/ethereum/lighthouse.nix315
-rw-r--r--nixpkgs/nixos/modules/services/cluster/corosync/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/default.nix10
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/hbase.nix230
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix2
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix4
-rw-r--r--nixpkgs/nixos/modules/services/cluster/k3s/default.nix71
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix4
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix19
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix19
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix11
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix5
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix35
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix12
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix2
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix4
-rw-r--r--nixpkgs/nixos/modules/services/cluster/pacemaker/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/cluster/patroni/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/cluster/spark/default.nix4
-rw-r--r--nixpkgs/nixos/modules/services/computing/boinc/client.nix51
-rw-r--r--nixpkgs/nixos/modules/services/computing/foldingathome/client.nix2
-rw-r--r--nixpkgs/nixos/modules/services/computing/slurm/slurm.nix12
-rw-r--r--nixpkgs/nixos/modules/services/computing/torque/mom.nix2
-rw-r--r--nixpkgs/nixos/modules/services/computing/torque/server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix23
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix11
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix6
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gitea-actions-runner.nix237
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix384
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/github-runner/options.nix211
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/github-runner/service.nix267
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/github-runners.nix58
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix155
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix10
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hail.nix4
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix206
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix9
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/settings.nix153
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix12
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix6
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix7
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/woodpecker/agents.nix144
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/woodpecker/server.nix98
-rw-r--r--nixpkgs/nixos/modules/services/databases/aerospike.nix2
-rw-r--r--nixpkgs/nixos/modules/services/databases/cassandra.nix35
-rw-r--r--nixpkgs/nixos/modules/services/databases/clickhouse.nix13
-rw-r--r--nixpkgs/nixos/modules/services/databases/cockroachdb.nix10
-rw-r--r--nixpkgs/nixos/modules/services/databases/couchdb.nix12
-rw-r--r--nixpkgs/nixos/modules/services/databases/dgraph.nix6
-rw-r--r--nixpkgs/nixos/modules/services/databases/dragonflydb.nix2
-rw-r--r--nixpkgs/nixos/modules/services/databases/firebird.nix6
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.md309
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.nix8
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.xml443
-rw-r--r--nixpkgs/nixos/modules/services/databases/hbase-standalone.nix4
-rw-r--r--nixpkgs/nixos/modules/services/databases/influxdb.nix6
-rw-r--r--nixpkgs/nixos/modules/services/databases/influxdb2.nix3
-rw-r--r--nixpkgs/nixos/modules/services/databases/lldap.nix121
-rw-r--r--nixpkgs/nixos/modules/services/databases/memcached.nix4
-rw-r--r--nixpkgs/nixos/modules/services/databases/monetdb.nix2
-rw-r--r--nixpkgs/nixos/modules/services/databases/mongodb.nix8
-rw-r--r--nixpkgs/nixos/modules/services/databases/mysql.nix56
-rw-r--r--nixpkgs/nixos/modules/services/databases/neo4j.nix8
-rw-r--r--nixpkgs/nixos/modules/services/databases/openldap.nix2
-rw-r--r--nixpkgs/nixos/modules/services/databases/opentsdb.nix10
-rw-r--r--nixpkgs/nixos/modules/services/databases/pgmanage.nix4
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.md210
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.nix235
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.xml214
-rw-r--r--nixpkgs/nixos/modules/services/databases/redis.nix51
-rw-r--r--nixpkgs/nixos/modules/services/databases/rethinkdb.nix2
-rw-r--r--nixpkgs/nixos/modules/services/databases/surrealdb.nix113
-rw-r--r--nixpkgs/nixos/modules/services/databases/victoriametrics.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/bamf.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/blueman.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/deepin/app-services.nix36
-rw-r--r--nixpkgs/nixos/modules/services/desktops/deepin/dde-api.nix50
-rw-r--r--nixpkgs/nixos/modules/services/desktops/deepin/dde-daemon.nix40
-rw-r--r--nixpkgs/nixos/modules/services/desktops/espanso.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.md39
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.nix4
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.xml56
-rw-r--r--nixpkgs/nixos/modules/services/desktops/geoclue2.nix1
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/at-spi2-core.nix5
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/evolution-data-server.nix4
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/glib-networking.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix47
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/gnome-user-share.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome/tracker.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gvfs.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/malcontent.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/neard.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json39
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json31
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json38
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json120
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json103
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json97
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json34
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json36
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json68
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json30
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix141
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix121
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/wireplumber.nix4
-rw-r--r--nixpkgs/nixos/modules/services/desktops/system-config-printer.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/system76-scheduler.nix296
-rw-r--r--nixpkgs/nixos/modules/services/desktops/tumbler.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/zeitgeist.nix2
-rw-r--r--nixpkgs/nixos/modules/services/development/blackfire.md39
-rw-r--r--nixpkgs/nixos/modules/services/development/blackfire.nix4
-rw-r--r--nixpkgs/nixos/modules/services/development/blackfire.xml46
-rw-r--r--nixpkgs/nixos/modules/services/development/distccd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/development/gemstash.nix103
-rw-r--r--nixpkgs/nixos/modules/services/development/hoogle.nix6
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyter/default.nix19
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix13
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyterhub/default.nix4
-rw-r--r--nixpkgs/nixos/modules/services/development/lorri.nix4
-rw-r--r--nixpkgs/nixos/modules/services/development/rstudio-server/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/development/zammad.nix22
-rw-r--r--nixpkgs/nixos/modules/services/display-managers/greetd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.md420
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.nix14
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.xml580
-rw-r--r--nixpkgs/nixos/modules/services/editors/haste.nix4
-rw-r--r--nixpkgs/nixos/modules/services/editors/infinoted.nix4
-rw-r--r--nixpkgs/nixos/modules/services/finance/odoo.nix2
-rw-r--r--nixpkgs/nixos/modules/services/games/asf.nix22
-rw-r--r--nixpkgs/nixos/modules/services/games/crossfire-server.nix10
-rw-r--r--nixpkgs/nixos/modules/services/games/deliantra-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/games/factorio.nix15
-rw-r--r--nixpkgs/nixos/modules/services/games/freeciv.nix12
-rw-r--r--nixpkgs/nixos/modules/services/games/minetest-server.nix10
-rw-r--r--nixpkgs/nixos/modules/services/games/openarena.nix2
-rw-r--r--nixpkgs/nixos/modules/services/games/quake3-server.nix4
-rw-r--r--nixpkgs/nixos/modules/services/games/teeworlds.nix8
-rw-r--r--nixpkgs/nixos/modules/services/games/terraria.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/acpid.nix10
-rw-r--r--nixpkgs/nixos/modules/services/hardware/actkbd.nix8
-rw-r--r--nixpkgs/nixos/modules/services/hardware/argonone.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/asusd.nix104
-rw-r--r--nixpkgs/nixos/modules/services/hardware/auto-cpufreq.nix23
-rw-r--r--nixpkgs/nixos/modules/services/hardware/bluetooth.nix39
-rw-r--r--nixpkgs/nixos/modules/services/hardware/ddccontrol.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/fancontrol.nix9
-rw-r--r--nixpkgs/nixos/modules/services/hardware/fwupd.nix139
-rw-r--r--nixpkgs/nixos/modules/services/hardware/irqbalance.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/joycond.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/kanata.nix98
-rw-r--r--nixpkgs/nixos/modules/services/hardware/keyd.nix126
-rw-r--r--nixpkgs/nixos/modules/services/hardware/lcd.nix12
-rw-r--r--nixpkgs/nixos/modules/services/hardware/lirc.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/openrgb.nix53
-rw-r--r--nixpkgs/nixos/modules/services/hardware/pcscd.nix12
-rw-r--r--nixpkgs/nixos/modules/services/hardware/rasdaemon.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/ratbagd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane.nix40
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix3
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/spacenavd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/supergfxd.nix42
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thermald.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thinkfan.nix62
-rw-r--r--nixpkgs/nixos/modules/services/hardware/throttled.nix10
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.md17
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.xml26
-rw-r--r--nixpkgs/nixos/modules/services/hardware/udev.nix52
-rw-r--r--nixpkgs/nixos/modules/services/hardware/udisks2.nix31
-rw-r--r--nixpkgs/nixos/modules/services/hardware/undervolt.nix8
-rw-r--r--nixpkgs/nixos/modules/services/hardware/upower.nix8
-rw-r--r--nixpkgs/nixos/modules/services/hardware/usbmuxd.nix12
-rw-r--r--nixpkgs/nixos/modules/services/hardware/usbrelayd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/hardware/vdr.nix4
-rw-r--r--nixpkgs/nixos/modules/services/home-automation/esphome.nix136
-rw-r--r--nixpkgs/nixos/modules/services/home-automation/evcc.nix96
-rw-r--r--nixpkgs/nixos/modules/services/home-automation/home-assistant.nix59
-rw-r--r--nixpkgs/nixos/modules/services/home-automation/zigbee2mqtt.nix7
-rw-r--r--nixpkgs/nixos/modules/services/logging/awstats.nix16
-rw-r--r--nixpkgs/nixos/modules/services/logging/filebeat.nix14
-rw-r--r--nixpkgs/nixos/modules/services/logging/fluentd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/logging/graylog.nix6
-rw-r--r--nixpkgs/nixos/modules/services/logging/heartbeat.nix2
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalbeat.nix2
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalwatch.nix2
-rw-r--r--nixpkgs/nixos/modules/services/logging/logcheck.nix8
-rw-r--r--nixpkgs/nixos/modules/services/logging/logrotate.nix217
-rw-r--r--nixpkgs/nixos/modules/services/logging/promtail.nix2
-rw-r--r--nixpkgs/nixos/modules/services/logging/rsyslogd.nix12
-rw-r--r--nixpkgs/nixos/modules/services/logging/syslogd.nix14
-rw-r--r--nixpkgs/nixos/modules/services/logging/ulogd.nix48
-rw-r--r--nixpkgs/nixos/modules/services/logging/vector.nix23
-rw-r--r--nixpkgs/nixos/modules/services/mail/davmail.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/dovecot.nix22
-rw-r--r--nixpkgs/nixos/modules/services/mail/exim.nix5
-rw-r--r--nixpkgs/nixos/modules/services/mail/goeland.nix74
-rw-r--r--nixpkgs/nixos/modules/services/mail/listmonk.nix222
-rw-r--r--nixpkgs/nixos/modules/services/mail/maddy.nix233
-rw-r--r--nixpkgs/nixos/modules/services/mail/mail.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailcatcher.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailhog.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.md82
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.nix22
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.xml94
-rw-r--r--nixpkgs/nixos/modules/services/mail/nullmailer.nix4
-rw-r--r--nixpkgs/nixos/modules/services/mail/offlineimap.nix6
-rw-r--r--nixpkgs/nixos/modules/services/mail/pfix-srsd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/mail/postfix.nix86
-rw-r--r--nixpkgs/nixos/modules/services/mail/postgrey.nix8
-rw-r--r--nixpkgs/nixos/modules/services/mail/public-inbox.nix55
-rw-r--r--nixpkgs/nixos/modules/services/mail/roundcube.nix30
-rw-r--r--nixpkgs/nixos/modules/services/mail/rspamd.nix10
-rw-r--r--nixpkgs/nixos/modules/services/mail/rss2email.nix3
-rw-r--r--nixpkgs/nixos/modules/services/mail/schleuder.nix4
-rw-r--r--nixpkgs/nixos/modules/services/mail/spamassassin.nix23
-rw-r--r--nixpkgs/nixos/modules/services/mail/sympa.nix6
-rw-r--r--nixpkgs/nixos/modules/services/mail/zeyple.nix125
-rw-r--r--nixpkgs/nixos/modules/services/matrix/appservice-discord.nix20
-rw-r--r--nixpkgs/nixos/modules/services/matrix/appservice-irc.nix2
-rw-r--r--nixpkgs/nixos/modules/services/matrix/conduit.nix5
-rw-r--r--nixpkgs/nixos/modules/services/matrix/dendrite.nix69
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mautrix-facebook.nix16
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mautrix-telegram.nix53
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mjolnir.md110
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mjolnir.nix12
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mjolnir.xml134
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mx-puppet-discord.nix (renamed from nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix)6
-rw-r--r--nixpkgs/nixos/modules/services/matrix/synapse.md213
-rw-r--r--nixpkgs/nixos/modules/services/matrix/synapse.nix67
-rw-r--r--nixpkgs/nixos/modules/services/matrix/synapse.xml276
-rw-r--r--nixpkgs/nixos/modules/services/misc/airsonic.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/ananicy.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/ankisyncd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/apache-kafka.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/atuin.nix100
-rw-r--r--nixpkgs/nixos/modules/services/misc/autorandr.nix28
-rw-r--r--nixpkgs/nixos/modules/services/misc/autosuspend.nix230
-rw-r--r--nixpkgs/nixos/modules/services/misc/bazarr.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/beanstalkd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/bepasty.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/calibre-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/cfdyndns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/cgminer.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/clipcat.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/clipmenu.nix2
-rwxr-xr-xnixpkgs/nixos/modules/services/misc/confd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/devmon.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/disnix.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/docker-registry.nix18
-rw-r--r--nixpkgs/nixos/modules/services/misc/domoticz.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/duckling.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/dwm-status.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/dysnomia.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/etcd.nix9
-rw-r--r--nixpkgs/nixos/modules/services/misc/etebase-server.nix18
-rw-r--r--nixpkgs/nixos/modules/services/misc/etesync-dav.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/ethminer.nix117
-rw-r--r--nixpkgs/nixos/modules/services/misc/exhibitor.nix11
-rw-r--r--nixpkgs/nixos/modules/services/misc/felix.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/freeswitch.nix12
-rw-r--r--nixpkgs/nixos/modules/services/misc/fstrim.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/gammu-smsd.nix18
-rw-r--r--nixpkgs/nixos/modules/services/misc/geoipupdate.nix35
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitea.nix293
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitit.nix725
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.md112
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.nix455
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.xml151
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitolite.nix35
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitweb.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/gogs.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/gollum.nix18
-rw-r--r--nixpkgs/nixos/modules/services/misc/gpsd.nix57
-rw-r--r--nixpkgs/nixos/modules/services/misc/greenclip.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/heisenbridge.nix7
-rw-r--r--nixpkgs/nixos/modules/services/misc/ihaskell.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/input-remapper.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/jackett.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/jellyfin.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/jellyseerr.nix62
-rw-r--r--nixpkgs/nixos/modules/services/misc/klipper.nix100
-rw-r--r--nixpkgs/nixos/modules/services/misc/languagetool.nix78
-rw-r--r--nixpkgs/nixos/modules/services/misc/leaps.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/libreddit.nix11
-rw-r--r--nixpkgs/nixos/modules/services/misc/lidarr.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/lifecycled.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/logkeys.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/mbpfan.nix43
-rw-r--r--nixpkgs/nixos/modules/services/misc/mediatomb.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/metabase.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/moonraker.nix59
-rw-r--r--nixpkgs/nixos/modules/services/misc/n8n.nix9
-rw-r--r--nixpkgs/nixos/modules/services/misc/nitter.nix34
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-daemon.nix132
-rw-r--r--nixpkgs/nixos/modules/services/misc/novacomd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/ntfy-sh.nix117
-rw-r--r--nixpkgs/nixos/modules/services/misc/nzbget.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/nzbhydra2.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/octoprint.nix19
-rw-r--r--nixpkgs/nixos/modules/services/misc/ombi.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/osrm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/owncast.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/packagekit.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/paperless.nix155
-rw-r--r--nixpkgs/nixos/modules/services/misc/parsoid.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/persistent-evdev.nix14
-rw-r--r--nixpkgs/nixos/modules/services/misc/pinnwand.nix99
-rw-r--r--nixpkgs/nixos/modules/services/misc/plex.nix3
-rw-r--r--nixpkgs/nixos/modules/services/misc/plikd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/podgrab.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/polaris.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/portunus.nix71
-rw-r--r--nixpkgs/nixos/modules/services/misc/prowlarr.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/pufferpanel.nix176
-rw-r--r--nixpkgs/nixos/modules/services/misc/pykms.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/radarr.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/readarr.nix88
-rw-r--r--nixpkgs/nixos/modules/services/misc/redmine.nix91
-rw-r--r--nixpkgs/nixos/modules/services/misc/ripple-data-api.nix8
-rw-r--r--nixpkgs/nixos/modules/services/misc/rippled.nix8
-rw-r--r--nixpkgs/nixos/modules/services/misc/rmfakecloud.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/rshim.nix99
-rw-r--r--nixpkgs/nixos/modules/services/misc/safeeyes.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sdrplay.nix10
-rw-r--r--nixpkgs/nixos/modules/services/misc/serviio.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/signald.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/siproxd.nix8
-rw-r--r--nixpkgs/nixos/modules/services/misc/snapper.nix148
-rw-r--r--nixpkgs/nixos/modules/services/misc/sonarr.nix13
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/default.md93
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/default.nix92
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/service.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml119
-rw-r--r--nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/spice-webdavd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/ssm-agent.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/sssd.nix21
-rw-r--r--nixpkgs/nixos/modules/services/misc/subsonic.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/sundtek.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/synergy.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/sysprof.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/tandoor-recipes.nix145
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/default.md93
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/default.nix32
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/doc.xml135
-rw-r--r--nixpkgs/nixos/modules/services/misc/tautulli.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/tiddlywiki.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/tp-auto-kbbl.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/uhub.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.md46
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.xml66
-rw-r--r--nixpkgs/nixos/modules/services/misc/xmr-stak.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/xmrig.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/zoneminder.nix20
-rw-r--r--nixpkgs/nixos/modules/services/misc/zookeeper.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/alerta.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/apcupsd.nix17
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/arbtt.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/bosun.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/cadvisor.nix10
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/cockpit.nix231
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/collectd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix14
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix236
-rwxr-xr-xnixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults9
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/do-agent.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana-agent.nix52
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana-image-renderer.nix42
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix6
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana.nix1621
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/graphite.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/hdaps.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/heapster.nix6
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/incron.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/kapacitor.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/karma.nix128
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/kthxbye.nix166
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/loki.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/mackerel-agent.nix14
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/metricbeat.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/mimir.nix11
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/monit.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/nagios.nix26
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/netdata.nix18
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/parsedmarc.md5
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix60
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/parsedmarc.xml125
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix107
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix30
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix136
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.md180
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix46
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml248
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix18
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/graphite.nix41
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix41
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/kea.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix12
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nut.nix50
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix16
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix36
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix10
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pve.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix27
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix37
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix19
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix34
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix37
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix29
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/zfs.nix44
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/sachet.nix88
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann.nix9
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/smartd.nix9
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/statsd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/sysstat.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/teamviewer.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/telegraf.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/thanos.nix132
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/tremor-rs.nix129
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/tuptime.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/unpoller.nix (renamed from nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix)30
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/ups.nix6
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/uptime-kuma.nix81
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/uptime.nix4
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/vmagent.nix100
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/vmalert.nix136
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/vnstat.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix6
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix8
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ceph.nix14
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix2
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix323
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/kubo.nix424
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/litestream/default.md (renamed from nixpkgs/nixos/modules/services/network-filesystems/litestream/litestream.xml)33
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/litestream/default.nix13
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/moosefs.nix10
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix2
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix4
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix86
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix4
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix4
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/samba-wsdd.nix16
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/samba.nix13
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix12
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix10
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/webdav.nix2
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix12
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/3proxy.nix86
-rw-r--r--nixpkgs/nixos/modules/services/networking/acme-dns.nix154
-rw-r--r--nixpkgs/nixos/modules/services/networking/adguardhome.nix96
-rw-r--r--nixpkgs/nixos/modules/services/networking/alice-lg.nix101
-rw-r--r--nixpkgs/nixos/modules/services/networking/antennas.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/avahi-daemon.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/babeld.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/bee-clef.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/bee.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/biboumi.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/bind.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/bird-lg.nix116
-rw-r--r--nixpkgs/nixos/modules/services/networking/bird.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/birdwatcher.nix129
-rw-r--r--nixpkgs/nixos/modules/services/networking/bitcoind.nix12
-rw-r--r--nixpkgs/nixos/modules/services/networking/bitlbee.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/blockbook-frontend.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/blocky.nix3
-rw-r--r--nixpkgs/nixos/modules/services/networking/cgit.nix203
-rw-r--r--nixpkgs/nixos/modules/services/networking/charybdis.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/chisel-server.nix99
-rw-r--r--nixpkgs/nixos/modules/services/networking/cloudflare-dyndns.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/cloudflared.nix331
-rw-r--r--nixpkgs/nixos/modules/services/networking/cntlm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/consul.nix14
-rw-r--r--nixpkgs/nixos/modules/services/networking/coredns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/corerad.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/coturn.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/create_ap.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/croc.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/dante.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ddclient.nix11
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpcd.nix24
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpd.nix7
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix5
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnsdist.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnsmasq.nix86
-rw-r--r--nixpkgs/nixos/modules/services/networking/doh-proxy-rust.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/envoy.nix53
-rw-r--r--nixpkgs/nixos/modules/services/networking/epmd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ergo.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ergochat.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/eternal-terminal.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/ferm.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/firefox-syncserver.nix176
-rw-r--r--nixpkgs/nixos/modules/services/networking/firefox-syncserver.xml77
-rw-r--r--nixpkgs/nixos/modules/services/networking/fireqos.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/firewall-iptables.nix334
-rw-r--r--nixpkgs/nixos/modules/services/networking/firewall-nftables.nix179
-rw-r--r--nixpkgs/nixos/modules/services/networking/firewall.nix592
-rw-r--r--nixpkgs/nixos/modules/services/networking/flannel.nix10
-rw-r--r--nixpkgs/nixos/modules/services/networking/freeradius.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/frr.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/gateone.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/gdomap.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ghostunnel.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/globalprotect-vpn.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/gnunet.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/go-autoconfig.nix66
-rw-r--r--nixpkgs/nixos/modules/services/networking/go-neb.nix5
-rw-r--r--nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/gobgpd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/gvpe.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/hans.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/harmonia.nix88
-rw-r--r--nixpkgs/nixos/modules/services/networking/headscale.nix813
-rw-r--r--nixpkgs/nixos/modules/services/networking/hostapd.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/https-dns-proxy.nix38
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/options.nix30
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2p.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2pd.nix46
-rw-r--r--nixpkgs/nixos/modules/services/networking/icecream/daemon.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/icecream/scheduler.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/imaginary.nix113
-rw-r--r--nixpkgs/nixos/modules/services/networking/inspircd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/iperf3.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh1
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix34
-rw-r--r--nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/iscsi/root-initiator.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/iscsi/target.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ivpn.nix51
-rw-r--r--nixpkgs/nixos/modules/services/networking/iwd.nix19
-rw-r--r--nixpkgs/nixos/modules/services/networking/jibri/default.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/jicofo.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix25
-rw-r--r--nixpkgs/nixos/modules/services/networking/kea.nix18
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/default.nix32
-rw-r--r--nixpkgs/nixos/modules/services/networking/knot.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/kresd.nix10
-rw-r--r--nixpkgs/nixos/modules/services/networking/legit.nix182
-rw-r--r--nixpkgs/nixos/modules/services/networking/libreswan.nix12
-rw-r--r--nixpkgs/nixos/modules/services/networking/lldpd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/lokinet.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/lxd-image-server.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/matterbridge.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/minidlna.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/miniupnpd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/miredo.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/mmsd.nix38
-rw-r--r--nixpkgs/nixos/modules/services/networking/monero.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/morty.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.nix19
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.xml147
-rw-r--r--nixpkgs/nixos/modules/services/networking/mozillavpn.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/mtr-exporter.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix48
-rw-r--r--nixpkgs/nixos/modules/services/networking/multipath.nix13
-rw-r--r--nixpkgs/nixos/modules/services/networking/murmur.nix48
-rw-r--r--nixpkgs/nixos/modules/services/networking/mxisd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/namecoind.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/nar-serve.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/nat-iptables.nix191
-rw-r--r--nixpkgs/nixos/modules/services/networking/nat-nftables.nix184
-rw-r--r--nixpkgs/nixos/modules/services/networking/nat.nix348
-rw-r--r--nixpkgs/nixos/modules/services/networking/nats.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/nbd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ncdns.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/ndppd.nix16
-rw-r--r--nixpkgs/nixos/modules/services/networking/nebula.nix71
-rw-r--r--nixpkgs/nixos/modules/services/networking/netbird.nix65
-rw-r--r--nixpkgs/nixos/modules/services/networking/networkd-dispatcher.nix98
-rw-r--r--nixpkgs/nixos/modules/services/networking/networkmanager.nix36
-rw-r--r--nixpkgs/nixos/modules/services/networking/nftables.nix84
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/ngircd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/nix-serve.nix17
-rw-r--r--nixpkgs/nixos/modules/services/networking/nixops-dns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/nntp-proxy.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/nomad.nix32
-rw-r--r--nixpkgs/nixos/modules/services/networking/nsd.nix63
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntopng.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/chrony.nix58
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/nullidentdmod.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/nylon.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/ocserv.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/ofono.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/onedrive.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/openconnect.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/openvpn.nix32
-rw-r--r--nixpkgs/nixos/modules/services/networking/ostinato.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/owamp.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/pdns-recursor.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/pdnsd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/peroxide.nix131
-rw-r--r--nixpkgs/nixos/modules/services/networking/picosnitch.nix26
-rw-r--r--nixpkgs/nixos/modules/services/networking/pixiecore.nix12
-rw-r--r--nixpkgs/nixos/modules/services/networking/pleroma.md180
-rw-r--r--nixpkgs/nixos/modules/services/networking/pleroma.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/pleroma.xml188
-rw-r--r--nixpkgs/nixos/modules/services/networking/polipo.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/powerdns.nix24
-rw-r--r--nixpkgs/nixos/modules/services/networking/pppd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/pptpd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/prayer.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/privoxy.nix42
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.md72
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.nix25
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.xml87
-rw-r--r--nixpkgs/nixos/modules/services/networking/quassel.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/quicktun.nix22
-rw-r--r--nixpkgs/nixos/modules/services/networking/quorum.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/r53-ddns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/radicale.nix12
-rw-r--r--nixpkgs/nixos/modules/services/networking/redsocks.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/resilio.nix58
-rw-r--r--nixpkgs/nixos/modules/services/networking/robustirc-bridge.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/routedns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/rpcbind.nix14
-rw-r--r--nixpkgs/nixos/modules/services/networking/rxe.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/sabnzbd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/seafile.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/searx.nix52
-rw-r--r--nixpkgs/nixos/modules/services/networking/shadowsocks.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/shellhub-agent.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/shorewall.nix15
-rw-r--r--nixpkgs/nixos/modules/services/networking/shorewall6.nix15
-rw-r--r--nixpkgs/nixos/modules/services/networking/shout.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/sitespeed-io.nix122
-rw-r--r--nixpkgs/nixos/modules/services/networking/skydns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/smartdns.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/smokeping.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/sniproxy.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/snowflake-proxy.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/softether.nix16
-rw-r--r--nixpkgs/nixos/modules/services/networking/soju.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/solanum.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/spacecookie.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/ssh/lshd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/ssh/sshd.nix341
-rw-r--r--nixpkgs/nixos/modules/services/networking/sslh.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix14
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix573
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/stubby.nix28
-rw-r--r--nixpkgs/nixos/modules/services/networking/stunnel.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/supplicant.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/supybot.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncplay.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing-relay.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/tailscale.nix27
-rw-r--r--nixpkgs/nixos/modules/services/networking/tayga.nix195
-rw-r--r--nixpkgs/nixos/modules/services/networking/teamspeak3.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/tedicross.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/teleport.nix26
-rw-r--r--nixpkgs/nixos/modules/services/networking/tetrd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/thelounge.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/tinc.nix169
-rw-r--r--nixpkgs/nixos/modules/services/networking/tmate-ssh-server.nix122
-rw-r--r--nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/tox-node.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/toxvpn.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/tvheadend.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/twingate.nix28
-rw-r--r--nixpkgs/nixos/modules/services/networking/ucarp.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/unbound.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/unifi.nix16
-rw-r--r--nixpkgs/nixos/modules/services/networking/uptermd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/v2ray.nix20
-rw-r--r--nixpkgs/nixos/modules/services/networking/v2raya.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/vdirsyncer.nix214
-rw-r--r--nixpkgs/nixos/modules/services/networking/vsftpd.nix41
-rw-r--r--nixpkgs/nixos/modules/services/networking/wasabibackend.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/webhook.nix214
-rw-r--r--nixpkgs/nixos/modules/services/networking/wg-netmanager.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/wg-quick.nix11
-rw-r--r--nixpkgs/nixos/modules/services/networking/wgautomesh.nix161
-rw-r--r--nixpkgs/nixos/modules/services/networking/wireguard.nix145
-rw-r--r--nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix106
-rw-r--r--nixpkgs/nixos/modules/services/networking/wstunnel.nix429
-rw-r--r--nixpkgs/nixos/modules/services/networking/x2goserver.nix10
-rw-r--r--nixpkgs/nixos/modules/services/networking/xandikos.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/xinetd.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/xl2tpd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/xray.nix99
-rw-r--r--nixpkgs/nixos/modules/services/networking/xrdp.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/yggdrasil.md (renamed from nixpkgs/nixos/modules/services/networking/yggdrasil.xml)73
-rw-r--r--nixpkgs/nixos/modules/services/networking/yggdrasil.nix227
-rw-r--r--nixpkgs/nixos/modules/services/networking/zerobin.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/zeronet.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/zerotierone.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/znc/default.nix18
-rw-r--r--nixpkgs/nixos/modules/services/networking/znc/options.nix26
-rw-r--r--nixpkgs/nixos/modules/services/printing/cups-pdf.nix185
-rw-r--r--nixpkgs/nixos/modules/services/printing/cupsd.nix24
-rw-r--r--nixpkgs/nixos/modules/services/printing/ipp-usb.nix63
-rw-r--r--nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix4
-rw-r--r--nixpkgs/nixos/modules/services/search/elasticsearch.nix2
-rw-r--r--nixpkgs/nixos/modules/services/search/hound.nix1
-rw-r--r--nixpkgs/nixos/modules/services/search/kibana.nix10
-rw-r--r--nixpkgs/nixos/modules/services/search/meilisearch.md14
-rw-r--r--nixpkgs/nixos/modules/services/search/meilisearch.nix12
-rw-r--r--nixpkgs/nixos/modules/services/search/meilisearch.xml85
-rw-r--r--nixpkgs/nixos/modules/services/search/opensearch.nix248
-rw-r--r--nixpkgs/nixos/modules/services/search/qdrant.nix129
-rw-r--r--nixpkgs/nixos/modules/services/search/solr.nix110
-rw-r--r--nixpkgs/nixos/modules/services/security/aesmd.nix23
-rw-r--r--nixpkgs/nixos/modules/services/security/authelia.nix401
-rw-r--r--nixpkgs/nixos/modules/services/security/certmgr.nix14
-rw-r--r--nixpkgs/nixos/modules/services/security/cfssl.nix16
-rw-r--r--nixpkgs/nixos/modules/services/security/clamav.nix4
-rw-r--r--nixpkgs/nixos/modules/services/security/endlessh-go.nix138
-rw-r--r--nixpkgs/nixos/modules/services/security/endlessh.nix99
-rw-r--r--nixpkgs/nixos/modules/services/security/fail2ban.nix147
-rw-r--r--nixpkgs/nixos/modules/services/security/fprintd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/security/haka.nix10
-rw-r--r--nixpkgs/nixos/modules/services/security/haveged.nix4
-rw-r--r--nixpkgs/nixos/modules/services/security/hockeypuck.nix12
-rw-r--r--nixpkgs/nixos/modules/services/security/infnoise.nix4
-rw-r--r--nixpkgs/nixos/modules/services/security/kanidm.nix114
-rw-r--r--nixpkgs/nixos/modules/services/security/munge.nix2
-rw-r--r--nixpkgs/nixos/modules/services/security/nginx-sso.nix2
-rw-r--r--nixpkgs/nixos/modules/services/security/oauth2_proxy.nix13
-rw-r--r--nixpkgs/nixos/modules/services/security/opensnitch.nix85
-rw-r--r--nixpkgs/nixos/modules/services/security/pass-secret-service.nix2
-rw-r--r--nixpkgs/nixos/modules/services/security/physlock.nix10
-rw-r--r--nixpkgs/nixos/modules/services/security/privacyidea.nix124
-rw-r--r--nixpkgs/nixos/modules/services/security/shibboleth-sp.nix4
-rw-r--r--nixpkgs/nixos/modules/services/security/sks.nix4
-rw-r--r--nixpkgs/nixos/modules/services/security/sslmate-agent.nix2
-rw-r--r--nixpkgs/nixos/modules/services/security/step-ca.nix48
-rw-r--r--nixpkgs/nixos/modules/services/security/tor.nix321
-rw-r--r--nixpkgs/nixos/modules/services/security/torify.nix16
-rw-r--r--nixpkgs/nixos/modules/services/security/usbguard.nix6
-rw-r--r--nixpkgs/nixos/modules/services/security/vault-agent.nix128
-rw-r--r--nixpkgs/nixos/modules/services/security/vault.nix12
-rw-r--r--nixpkgs/nixos/modules/services/security/vaultwarden/default.nix66
-rw-r--r--nixpkgs/nixos/modules/services/security/yubikey-agent.nix3
-rw-r--r--nixpkgs/nixos/modules/services/system/automatic-timezoned.nix92
-rw-r--r--nixpkgs/nixos/modules/services/system/cachix-agent/default.nix16
-rw-r--r--nixpkgs/nixos/modules/services/system/cachix-watch-store.nix93
-rw-r--r--nixpkgs/nixos/modules/services/system/cloud-init.nix284
-rw-r--r--nixpkgs/nixos/modules/services/system/dbus.nix193
-rw-r--r--nixpkgs/nixos/modules/services/system/earlyoom.nix10
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/mit.nix2
-rw-r--r--nixpkgs/nixos/modules/services/system/nscd.nix48
-rw-r--r--nixpkgs/nixos/modules/services/system/saslauthd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/system/self-deploy.nix26
-rw-r--r--nixpkgs/nixos/modules/services/system/systembus-notify.nix4
-rw-r--r--nixpkgs/nixos/modules/services/torrent/deluge.nix10
-rw-r--r--nixpkgs/nixos/modules/services/torrent/flexget.nix6
-rw-r--r--nixpkgs/nixos/modules/services/torrent/magnetico.nix38
-rw-r--r--nixpkgs/nixos/modules/services/torrent/opentracker.nix2
-rw-r--r--nixpkgs/nixos/modules/services/torrent/rtorrent.nix15
-rw-r--r--nixpkgs/nixos/modules/services/torrent/transmission.nix62
-rw-r--r--nixpkgs/nixos/modules/services/tracing/tempo.nix2
-rw-r--r--nixpkgs/nixos/modules/services/ttys/getty.nix2
-rw-r--r--nixpkgs/nixos/modules/services/video/epgstation/default.nix44
-rw-r--r--nixpkgs/nixos/modules/services/video/frigate.nix368
-rw-r--r--nixpkgs/nixos/modules/services/video/go2rtc/default.nix115
-rw-r--r--nixpkgs/nixos/modules/services/video/mediamtx.nix (renamed from nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix)32
-rw-r--r--nixpkgs/nixos/modules/services/video/mirakurun.nix22
-rw-r--r--nixpkgs/nixos/modules/services/video/replay-sorcery.nix6
-rw-r--r--nixpkgs/nixos/modules/services/video/unifi-video.nix6
-rw-r--r--nixpkgs/nixos/modules/services/video/v4l2-relayd.nix199
-rw-r--r--nixpkgs/nixos/modules/services/wayland/cage.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/akkoma.md332
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/akkoma.nix1086
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/alps.nix132
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix10
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix8
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix10
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/baget.nix170
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/bookstack.nix15
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/calibre-web.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/changedetection-io.nix220
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix106
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/cloudlog.nix503
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/code-server.nix222
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/coder.nix217
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/convos.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/dex.nix17
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/discourse.md286
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/discourse.nix74
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/discourse.xml355
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/documize.nix39
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix453
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/dolibarr.nix323
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/fluidd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/freshrss.nix287
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/galene.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/gerrit.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/gotify-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/grocy.md66
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/grocy.nix8
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/grocy.xml77
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/healthchecks.nix46
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix89
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/hledger-web.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix12
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix153
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/invidious.nix12
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/invoiceplane.nix76
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/isso.nix32
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jirafeau.nix14
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md45
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix18
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml55
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/kasmweb/default.nix275
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh114
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/kavita.nix83
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/keycloak.md141
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/keycloak.nix113
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/keycloak.xml202
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/komga.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/lemmy.md3
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/lemmy.nix84
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/lemmy.xml56
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/limesurvey.nix37
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mainsail.nix66
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mastodon.nix337
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml107
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo.md77
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo.nix16
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mattermost.nix27
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mediawiki.nix182
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/miniflux.nix15
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/monica.nix468
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/moodle.nix13
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/netbox.nix167
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud-notify_push.nix123
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.md227
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.nix336
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.xml291
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nexus.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nifi.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/node-red.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/onlyoffice.nix20
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/openvscode-server.nix211
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/openwebrx.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/outline.nix781
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/peering-manager.nix265
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/peertube.nix413
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix20
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/photoprism.nix155
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/phylactery.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pict-rs.md1
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pict-rs.nix10
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pict-rs.xml162
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pixelfed.nix478
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/plausible.md35
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/plausible.nix40
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/plausible.xml51
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix5
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/prosody-filer.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/restya-board.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/rss-bridge.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/selfoss.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/sftpgo.nix375
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/shiori.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/snipe-it.nix64
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/sogo.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/trilium.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/tt-rss.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/vikunja.nix10
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/whitebophir.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/wiki-js.nix42
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/wordpress.nix221
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/writefreely.nix484
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/youtrack.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/zabbix.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/agate.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix30
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix38
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy/default.nix116
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix8
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/garage.md96
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/garage.nix97
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hitch/default.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hydron.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh1
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/keter/default.nix22
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix12
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/merecat.nix55
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/minio.nix81
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/molly-brown.nix8
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/default.nix511
-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.nix103
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix7
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/pomerium.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/stargazer.nix226
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/tomcat.nix16
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/traefik.nix27
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/ttyd.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/unit/default.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/uwsgi.nix28
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/varnish/default.nix26
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/zope2.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/clight.nix8
-rw-r--r--nixpkgs/nixos/modules/services/x11/colord.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/budgie.nix244
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix80
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/deepin.nix208
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix10
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.md167
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix68
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml253
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix10
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.md74
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix28
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml120
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/phosh.nix16
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix305
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/retroarch.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix4
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/default.nix18
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix6
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix26
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix149
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix7
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/sx.nix4
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/extra-layouts.nix6
-rw-r--r--nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix31
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/digimend.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/libinput.nix17
-rw-r--r--nixpkgs/nixos/modules/services/x11/imwheel.nix6
-rw-r--r--nixpkgs/nixos/modules/services/x11/picom.nix39
-rw-r--r--nixpkgs/nixos/modules/services/x11/touchegg.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/urserver.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix4
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/berry.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/default.nix10
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/dk.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/e16.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix6
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fvwm2.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fvwm3.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/hackedbox.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/hypr.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/i3.nix5
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/katriawm.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/mlvwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/nimdow.nix23
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/notion.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix47
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix6
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/twm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/wmderland.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix12
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/xautolock.nix8
-rw-r--r--nixpkgs/nixos/modules/services/x11/xbanish.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/xserver.nix79
-rw-r--r--nixpkgs/nixos/modules/system/activation/activation-script.nix27
-rw-r--r--nixpkgs/nixos/modules/system/activation/bootspec.cue31
-rw-r--r--nixpkgs/nixos/modules/system/activation/bootspec.nix118
-rw-r--r--nixpkgs/nixos/modules/system/activation/specialisation.nix85
-rwxr-xr-xnixpkgs/nixos/modules/system/activation/switch-to-configuration.pl4
-rw-r--r--nixpkgs/nixos/modules/system/activation/test.nix27
-rw-r--r--nixpkgs/nixos/modules/system/activation/top-level.nix219
-rw-r--r--nixpkgs/nixos/modules/system/boot/binfmt.nix42
-rw-r--r--nixpkgs/nixos/modules/system/boot/grow-partition.nix7
-rw-r--r--nixpkgs/nixos/modules/system/boot/initrd-network.nix6
-rw-r--r--nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix38
-rw-r--r--nixpkgs/nixos/modules/system/boot/initrd-ssh.nix129
-rw-r--r--nixpkgs/nixos/modules/system/boot/kernel.nix70
-rw-r--r--nixpkgs/nixos/modules/system/boot/kernel_config.nix4
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/external/external.md26
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/external/external.nix36
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix197
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl502
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix6
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix56
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh6
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix1
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix2
-rwxr-xr-x[-rw-r--r--]nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py110
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix66
-rw-r--r--nixpkgs/nixos/modules/system/boot/luksroot.nix122
-rw-r--r--nixpkgs/nixos/modules/system/boot/modprobe.nix5
-rw-r--r--nixpkgs/nixos/modules/system/boot/networkd.nix1423
-rw-r--r--nixpkgs/nixos/modules/system/boot/plymouth.nix11
-rw-r--r--nixpkgs/nixos/modules/system/boot/resolved.nix4
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-1-init.sh46
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-1.nix72
-rwxr-xr-xnixpkgs/nixos/modules/system/boot/stage-2-init.sh4
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-2.nix15
-rw-r--r--nixpkgs/nixos/modules/system/boot/stratisroot.nix64
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd.nix29
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/coredump.nix16
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/homed.nix43
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix4
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/initrd.nix138
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/logind.nix127
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix8
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/oomd.nix57
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/repart.nix152
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix24
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix1
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/user.nix80
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix18
-rw-r--r--nixpkgs/nixos/modules/system/boot/tmp.nix81
-rw-r--r--nixpkgs/nixos/modules/system/boot/uvesafb.nix39
-rw-r--r--nixpkgs/nixos/modules/system/etc/setup-etc.pl25
-rw-r--r--nixpkgs/nixos/modules/tasks/auto-upgrade.nix6
-rw-r--r--nixpkgs/nixos/modules/tasks/bcache.nix2
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems.nix200
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix17
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix4
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/envfs.nix60
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/erofs.nix21
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/ext.nix3
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix5
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/jfs.nix2
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/vfat.nix2
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/zfs.nix85
-rw-r--r--nixpkgs/nixos/modules/tasks/lvm.nix16
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix12
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix304
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces.nix105
-rw-r--r--nixpkgs/nixos/modules/tasks/powertop.nix2
-rw-r--r--nixpkgs/nixos/modules/tasks/snapraid.nix2
-rw-r--r--nixpkgs/nixos/modules/tasks/stratis.nix18
-rw-r--r--nixpkgs/nixos/modules/tasks/swraid.nix2
-rw-r--r--nixpkgs/nixos/modules/testing/minimal-kernel.nix28
-rw-r--r--nixpkgs/nixos/modules/testing/service-runner.nix2
-rw-r--r--nixpkgs/nixos/modules/testing/test-instrumentation.nix20
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix98
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-image.nix103
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-options.nix23
-rw-r--r--nixpkgs/nixos/modules/virtualisation/anbox.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/appvm.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch17
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-agent.nix216
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-common.nix7
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-image.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/brightbox-image.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/container-config.nix13
-rw-r--r--nixpkgs/nixos/modules/virtualisation/containerd.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/containers.nix18
-rw-r--r--nixpkgs/nixos/modules/virtualisation/cri-o.nix25
-rw-r--r--nixpkgs/nixos/modules/virtualisation/digital-ocean-config.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/digital-ocean-init.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker.nix6
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-data.nix1
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix77
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.sh66
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ecs-agent.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/google-compute-config.nix20
-rw-r--r--nixpkgs/nixos/modules/virtualisation/google-compute-image.nix6
-rw-r--r--nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/hyperv-image.nix6
-rw-r--r--nixpkgs/nixos/modules/virtualisation/kvmgt.nix14
-rw-r--r--nixpkgs/nixos/modules/virtualisation/libvirtd.nix42
-rw-r--r--nixpkgs/nixos/modules/virtualisation/linode-config.nix75
-rw-r--r--nixpkgs/nixos/modules/virtualisation/linode-image.nix66
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxc-container.nix76
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxc.nix5
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxcfs.nix8
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxd.nix24
-rw-r--r--nixpkgs/nixos/modules/virtualisation/multipass.nix61
-rw-r--r--nixpkgs/nixos/modules/virtualisation/nixos-containers.nix63
-rw-r--r--nixpkgs/nixos/modules/virtualisation/oci-containers.nix31
-rw-r--r--nixpkgs/nixos/modules/virtualisation/openstack-config.nix4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/openstack-options.nix10
-rw-r--r--nixpkgs/nixos/modules/virtualisation/parallels-guest.nix3
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/default.nix110
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix36
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix6
-rw-r--r--nixpkgs/nixos/modules/virtualisation/proxmox-image.nix149
-rw-r--r--nixpkgs/nixos/modules/virtualisation/proxmox-lxc.nix6
-rw-r--r--nixpkgs/nixos/modules/virtualisation/qemu-vm.nix655
-rw-r--r--nixpkgs/nixos/modules/virtualisation/rosetta.nix81
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix54
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix63
-rw-r--r--nixpkgs/nixos/modules/virtualisation/vmware-guest.nix5
-rw-r--r--nixpkgs/nixos/modules/virtualisation/vmware-host.nix28
-rw-r--r--nixpkgs/nixos/modules/virtualisation/vmware-image.nix10
-rw-r--r--nixpkgs/nixos/modules/virtualisation/waydroid.nix8
-rw-r--r--nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/xen-dom0.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/xen-domU.nix1
-rw-r--r--nixpkgs/nixos/release-combined.nix23
-rw-r--r--nixpkgs/nixos/release-small.nix97
-rw-r--r--nixpkgs/nixos/release.nix39
-rw-r--r--nixpkgs/nixos/tests/3proxy.nix10
-rw-r--r--nixpkgs/nixos/tests/aaaaxy.nix29
-rw-r--r--nixpkgs/nixos/tests/acme-dns.nix50
-rw-r--r--nixpkgs/nixos/tests/acme.nix140
-rw-r--r--nixpkgs/nixos/tests/adguardhome.nix22
-rw-r--r--nixpkgs/nixos/tests/aesmd.nix110
-rw-r--r--nixpkgs/nixos/tests/akkoma.nix121
-rw-r--r--nixpkgs/nixos/tests/alice-lg.nix44
-rw-r--r--nixpkgs/nixos/tests/all-tests.nix303
-rw-r--r--nixpkgs/nixos/tests/alps.nix108
-rw-r--r--nixpkgs/nixos/tests/apache_datasketches.nix29
-rw-r--r--nixpkgs/nixos/tests/apcupsd.nix41
-rw-r--r--nixpkgs/nixos/tests/apfs.nix21
-rw-r--r--nixpkgs/nixos/tests/apparmor.nix17
-rw-r--r--nixpkgs/nixos/tests/atop.nix2
-rw-r--r--nixpkgs/nixos/tests/atuin.nix64
-rw-r--r--nixpkgs/nixos/tests/authelia.nix169
-rw-r--r--nixpkgs/nixos/tests/bazarr.nix6
-rw-r--r--nixpkgs/nixos/tests/binary-cache.nix62
-rw-r--r--nixpkgs/nixos/tests/birdwatcher.nix94
-rw-r--r--nixpkgs/nixos/tests/boot-stage1.nix6
-rw-r--r--nixpkgs/nixos/tests/bootspec.nix172
-rw-r--r--nixpkgs/nixos/tests/borgbackup.nix26
-rw-r--r--nixpkgs/nixos/tests/bpf.nix7
-rw-r--r--nixpkgs/nixos/tests/btrbk-doas.nix114
-rw-r--r--nixpkgs/nixos/tests/btrbk-section-order.nix51
-rw-r--r--nixpkgs/nixos/tests/btrbk.nix6
-rw-r--r--nixpkgs/nixos/tests/budgie.nix54
-rw-r--r--nixpkgs/nixos/tests/buildbot.nix4
-rw-r--r--nixpkgs/nixos/tests/cadvisor.nix8
-rw-r--r--nixpkgs/nixos/tests/cage.nix6
-rw-r--r--nixpkgs/nixos/tests/cagebreak.nix1
-rw-r--r--nixpkgs/nixos/tests/calibre-web.nix3
-rw-r--r--nixpkgs/nixos/tests/ceph-single-node.nix11
-rw-r--r--nixpkgs/nixos/tests/cgit.nix73
-rw-r--r--nixpkgs/nixos/tests/chromium.nix11
-rw-r--r--nixpkgs/nixos/tests/chrony-ptp.nix28
-rw-r--r--nixpkgs/nixos/tests/cinnamon.nix66
-rw-r--r--nixpkgs/nixos/tests/clickhouse.nix2
-rw-r--r--nixpkgs/nixos/tests/cloud-init-hostname.nix46
-rw-r--r--nixpkgs/nixos/tests/cloud-init.nix19
-rw-r--r--nixpkgs/nixos/tests/cloudlog.nix18
-rw-r--r--nixpkgs/nixos/tests/cockpit.nix135
-rw-r--r--nixpkgs/nixos/tests/cockroachdb.nix2
-rw-r--r--nixpkgs/nixos/tests/code-server.nix22
-rw-r--r--nixpkgs/nixos/tests/coder.nix24
-rw-r--r--nixpkgs/nixos/tests/common/acme/client/default.nix4
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem32
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem50
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/ca.cert.pem34
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/ca.key.pem50
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/default.nix26
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/generate-certs.nix8
-rw-r--r--nixpkgs/nixos/tests/common/auto.nix25
-rw-r--r--nixpkgs/nixos/tests/common/ec2.nix11
-rw-r--r--nixpkgs/nixos/tests/common/resolver.nix6
-rw-r--r--nixpkgs/nixos/tests/connman.nix77
-rw-r--r--nixpkgs/nixos/tests/consul-template.nix36
-rw-r--r--nixpkgs/nixos/tests/consul.nix30
-rw-r--r--nixpkgs/nixos/tests/containers-imperative.nix4
-rw-r--r--nixpkgs/nixos/tests/containers-unified-hierarchy.nix21
-rw-r--r--nixpkgs/nixos/tests/convos.nix6
-rw-r--r--nixpkgs/nixos/tests/corerad.nix1
-rw-r--r--nixpkgs/nixos/tests/coturn.nix6
-rw-r--r--nixpkgs/nixos/tests/couchdb.nix14
-rw-r--r--nixpkgs/nixos/tests/cri-o.nix2
-rw-r--r--nixpkgs/nixos/tests/cups-pdf.nix40
-rw-r--r--nixpkgs/nixos/tests/custom-ca.nix2
-rw-r--r--nixpkgs/nixos/tests/darling.nix44
-rw-r--r--nixpkgs/nixos/tests/deepin.nix57
-rw-r--r--nixpkgs/nixos/tests/deluge.nix4
-rw-r--r--nixpkgs/nixos/tests/dhparams.nix138
-rw-r--r--nixpkgs/nixos/tests/discourse.nix8
-rw-r--r--nixpkgs/nixos/tests/dnscrypt-proxy2.nix8
-rw-r--r--nixpkgs/nixos/tests/doas.nix4
-rw-r--r--nixpkgs/nixos/tests/docker-tools.nix128
-rw-r--r--nixpkgs/nixos/tests/docker.nix1
-rw-r--r--nixpkgs/nixos/tests/doh-proxy-rust.nix4
-rw-r--r--nixpkgs/nixos/tests/dokuwiki.nix174
-rw-r--r--nixpkgs/nixos/tests/dolibarr.nix59
-rw-r--r--nixpkgs/nixos/tests/domination.nix2
-rw-r--r--nixpkgs/nixos/tests/early-mount-options.nix19
-rw-r--r--nixpkgs/nixos/tests/ec2.nix2
-rw-r--r--nixpkgs/nixos/tests/elk.nix17
-rw-r--r--nixpkgs/nixos/tests/endlessh-go.nix58
-rw-r--r--nixpkgs/nixos/tests/endlessh.nix43
-rw-r--r--nixpkgs/nixos/tests/enlightenment.nix2
-rw-r--r--nixpkgs/nixos/tests/envfs.nix42
-rw-r--r--nixpkgs/nixos/tests/envoy.nix35
-rw-r--r--nixpkgs/nixos/tests/esphome.nix40
-rw-r--r--nixpkgs/nixos/tests/etcd-cluster.nix29
-rw-r--r--nixpkgs/nixos/tests/etcd.nix2
-rw-r--r--nixpkgs/nixos/tests/evcc.nix96
-rw-r--r--nixpkgs/nixos/tests/fcitx/config12
-rw-r--r--nixpkgs/nixos/tests/fcitx/default.nix142
-rw-r--r--nixpkgs/nixos/tests/fcitx/profile4
-rw-r--r--nixpkgs/nixos/tests/fcitx5/config11
-rw-r--r--nixpkgs/nixos/tests/fcitx5/default.nix138
-rw-r--r--nixpkgs/nixos/tests/fcitx5/profile27
-rw-r--r--nixpkgs/nixos/tests/firewall.nix13
-rw-r--r--nixpkgs/nixos/tests/fluidd.nix4
-rw-r--r--nixpkgs/nixos/tests/freenet.nix19
-rw-r--r--nixpkgs/nixos/tests/freshrss-pgsql.nix48
-rw-r--r--nixpkgs/nixos/tests/freshrss-sqlite.nix20
-rw-r--r--nixpkgs/nixos/tests/frigate.nix60
-rw-r--r--nixpkgs/nixos/tests/fsck.nix14
-rw-r--r--nixpkgs/nixos/tests/fscrypt.nix50
-rw-r--r--nixpkgs/nixos/tests/ft2-clone.nix4
-rw-r--r--nixpkgs/nixos/tests/garage/basic.nix98
-rw-r--r--nixpkgs/nixos/tests/garage/default.nix53
-rw-r--r--nixpkgs/nixos/tests/garage/with-3node-replication.nix121
-rw-r--r--nixpkgs/nixos/tests/gemstash.nix51
-rw-r--r--nixpkgs/nixos/tests/geth.nix8
-rw-r--r--nixpkgs/nixos/tests/ghostunnel.nix1
-rw-r--r--nixpkgs/nixos/tests/gitea.nix38
-rw-r--r--nixpkgs/nixos/tests/github-runner.nix37
-rw-r--r--nixpkgs/nixos/tests/gitlab.nix51
-rw-r--r--nixpkgs/nixos/tests/gnome-flashback.nix50
-rw-r--r--nixpkgs/nixos/tests/gnome-xorg.nix23
-rw-r--r--nixpkgs/nixos/tests/gnome.nix32
-rw-r--r--nixpkgs/nixos/tests/gnupg.nix118
-rw-r--r--nixpkgs/nixos/tests/gollum.nix14
-rw-r--r--nixpkgs/nixos/tests/gonic.nix18
-rw-r--r--nixpkgs/nixos/tests/google-oslogin/server.nix4
-rwxr-xr-xnixpkgs/nixos/tests/google-oslogin/server.py10
-rw-r--r--nixpkgs/nixos/tests/gotify-server.nix2
-rw-r--r--nixpkgs/nixos/tests/grafana/basic.nix (renamed from nixpkgs/nixos/tests/grafana.nix)71
-rw-r--r--nixpkgs/nixos/tests/grafana/default.nix9
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/contact-points.yaml9
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/dashboards.yaml6
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/datasources.yaml7
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/default.nix257
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml4
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/policies.yaml4
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/rules.yaml36
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/templates.yaml5
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/test_dashboard.json47
-rw-r--r--nixpkgs/nixos/tests/graphite.nix2
-rw-r--r--nixpkgs/nixos/tests/graylog.nix1
-rw-r--r--nixpkgs/nixos/tests/grocy.nix26
-rw-r--r--nixpkgs/nixos/tests/hadoop/hbase.nix25
-rw-r--r--nixpkgs/nixos/tests/hadoop/hdfs.nix7
-rw-r--r--nixpkgs/nixos/tests/haproxy.nix1
-rw-r--r--nixpkgs/nixos/tests/hardened.nix1
-rw-r--r--nixpkgs/nixos/tests/harmonia.nix37
-rw-r--r--nixpkgs/nixos/tests/headscale.nix82
-rw-r--r--nixpkgs/nixos/tests/hibernate.nix11
-rw-r--r--nixpkgs/nixos/tests/hockeypuck.nix2
-rw-r--r--nixpkgs/nixos/tests/home-assistant.nix114
-rw-r--r--nixpkgs/nixos/tests/hostname.nix86
-rw-r--r--nixpkgs/nixos/tests/hydra/common.nix2
-rw-r--r--nixpkgs/nixos/tests/iftop.nix4
-rw-r--r--nixpkgs/nixos/tests/ihatemoney/default.nix71
-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/image-contents.nix25
-rw-r--r--nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix102
-rw-r--r--nixpkgs/nixos/tests/initrd-network-openvpn/default.nix21
-rw-r--r--nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn3
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/default.nix4
-rw-r--r--nixpkgs/nixos/tests/initrd-secrets-changing.nix57
-rw-r--r--nixpkgs/nixos/tests/installed-tests/default.nix8
-rw-r--r--nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix2
-rw-r--r--nixpkgs/nixos/tests/installed-tests/flatpak.nix2
-rw-r--r--nixpkgs/nixos/tests/installed-tests/fwupd.nix2
-rw-r--r--nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix2
-rw-r--r--nixpkgs/nixos/tests/installed-tests/ibus.nix1
-rw-r--r--nixpkgs/nixos/tests/installed-tests/json-glib.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/librsvg.nix9
-rw-r--r--nixpkgs/nixos/tests/installed-tests/pipewire.nix12
-rw-r--r--nixpkgs/nixos/tests/installer-systemd-stage-1.nix8
-rw-r--r--nixpkgs/nixos/tests/installer.nix223
-rw-r--r--nixpkgs/nixos/tests/invoiceplane.nix16
-rw-r--r--nixpkgs/nixos/tests/ipfs.nix61
-rw-r--r--nixpkgs/nixos/tests/isso.nix2
-rw-r--r--nixpkgs/nixos/tests/jackett.nix4
-rw-r--r--nixpkgs/nixos/tests/jenkins.nix2
-rw-r--r--nixpkgs/nixos/tests/jibri.nix3
-rw-r--r--nixpkgs/nixos/tests/jirafeau.nix4
-rw-r--r--nixpkgs/nixos/tests/k3s/default.nix8
-rw-r--r--nixpkgs/nixos/tests/k3s/multi-node.nix66
-rw-r--r--nixpkgs/nixos/tests/k3s/single-node.nix21
-rw-r--r--nixpkgs/nixos/tests/kafka.nix17
-rw-r--r--nixpkgs/nixos/tests/kanidm.nix54
-rw-r--r--nixpkgs/nixos/tests/karma.nix84
-rw-r--r--nixpkgs/nixos/tests/kavita.nix36
-rw-r--r--nixpkgs/nixos/tests/kea.nix120
-rw-r--r--nixpkgs/nixos/tests/keepassxc.nix10
-rw-r--r--nixpkgs/nixos/tests/kerberos/mit.nix2
-rw-r--r--nixpkgs/nixos/tests/kernel-generic.nix2
-rw-r--r--nixpkgs/nixos/tests/keycloak.nix17
-rw-r--r--nixpkgs/nixos/tests/keyd.nix82
-rw-r--r--nixpkgs/nixos/tests/keymap.nix12
-rw-r--r--nixpkgs/nixos/tests/knot.nix48
-rw-r--r--nixpkgs/nixos/tests/komga.nix4
-rw-r--r--nixpkgs/nixos/tests/krb5/example-config.nix2
-rw-r--r--nixpkgs/nixos/tests/kthxbye.nix110
-rw-r--r--nixpkgs/nixos/tests/kubernetes/base.nix4
-rw-r--r--nixpkgs/nixos/tests/kubernetes/default.nix2
-rw-r--r--nixpkgs/nixos/tests/kubernetes/dns.nix2
-rw-r--r--nixpkgs/nixos/tests/kubernetes/e2e.nix40
-rw-r--r--nixpkgs/nixos/tests/kubo.nix85
-rw-r--r--nixpkgs/nixos/tests/ladybird.nix30
-rw-r--r--nixpkgs/nixos/tests/languagetool.nix19
-rw-r--r--nixpkgs/nixos/tests/legit.nix54
-rw-r--r--nixpkgs/nixos/tests/lemmy.nix89
-rw-r--r--nixpkgs/nixos/tests/libreddit.nix4
-rw-r--r--nixpkgs/nixos/tests/libreswan.nix2
-rw-r--r--nixpkgs/nixos/tests/libvirtd.nix61
-rw-r--r--nixpkgs/nixos/tests/lidarr.nix4
-rw-r--r--nixpkgs/nixos/tests/listmonk.nix69
-rw-r--r--nixpkgs/nixos/tests/lldap.nix26
-rw-r--r--nixpkgs/nixos/tests/login.nix13
-rw-r--r--nixpkgs/nixos/tests/logrotate.nix31
-rw-r--r--nixpkgs/nixos/tests/lorri/default.nix2
-rw-r--r--nixpkgs/nixos/tests/luks.nix71
-rw-r--r--nixpkgs/nixos/tests/lvm2/default.nix2
-rw-r--r--nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix10
-rw-r--r--nixpkgs/nixos/tests/lvm2/thinpool.nix8
-rw-r--r--nixpkgs/nixos/tests/lxd-image-server.nix4
-rw-r--r--nixpkgs/nixos/tests/lxd.nix6
-rw-r--r--nixpkgs/nixos/tests/maddy/default.nix6
-rw-r--r--nixpkgs/nixos/tests/maddy/tls.nix94
-rw-r--r--nixpkgs/nixos/tests/maddy/unencrypted.nix (renamed from nixpkgs/nixos/tests/maddy.nix)14
-rw-r--r--nixpkgs/nixos/tests/mailman.nix67
-rw-r--r--nixpkgs/nixos/tests/make-test-python.nix2
-rw-r--r--nixpkgs/nixos/tests/mate.nix58
-rw-r--r--nixpkgs/nixos/tests/matomo.nix2
-rw-r--r--nixpkgs/nixos/tests/matrix/conduit.nix2
-rw-r--r--nixpkgs/nixos/tests/matrix/mjolnir.nix7
-rw-r--r--nixpkgs/nixos/tests/matrix/synapse.nix2
-rw-r--r--nixpkgs/nixos/tests/mattermost.nix16
-rw-r--r--nixpkgs/nixos/tests/mediatomb.nix101
-rw-r--r--nixpkgs/nixos/tests/mediawiki.nix95
-rw-r--r--nixpkgs/nixos/tests/meilisearch.nix10
-rw-r--r--nixpkgs/nixos/tests/merecat.nix28
-rw-r--r--nixpkgs/nixos/tests/mindustry.nix28
-rw-r--r--nixpkgs/nixos/tests/minecraft-server.nix2
-rw-r--r--nixpkgs/nixos/tests/minidlna.nix31
-rw-r--r--nixpkgs/nixos/tests/miniflux.nix3
-rw-r--r--nixpkgs/nixos/tests/minio.nix84
-rw-r--r--nixpkgs/nixos/tests/miriway.nix125
-rw-r--r--nixpkgs/nixos/tests/misc.nix15
-rw-r--r--nixpkgs/nixos/tests/mongodb.nix8
-rw-r--r--nixpkgs/nixos/tests/moodle.nix2
-rw-r--r--nixpkgs/nixos/tests/mosquitto.nix13
-rw-r--r--nixpkgs/nixos/tests/mpv.nix4
-rw-r--r--nixpkgs/nixos/tests/multipass.nix37
-rw-r--r--nixpkgs/nixos/tests/musescore.nix56
-rw-r--r--nixpkgs/nixos/tests/mysql/common.nix7
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql-replication.nix4
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql.nix2
-rw-r--r--nixpkgs/nixos/tests/n8n.nix9
-rw-r--r--nixpkgs/nixos/tests/nar-serve.nix2
-rw-r--r--nixpkgs/nixos/tests/nat.nix23
-rw-r--r--nixpkgs/nixos/tests/nebula.nix207
-rw-r--r--nixpkgs/nixos/tests/netbird.nix21
-rw-r--r--nixpkgs/nixos/tests/netdata.nix2
-rw-r--r--nixpkgs/nixos/tests/networking-proxy.nix2
-rw-r--r--nixpkgs/nixos/tests/networking.nix178
-rw-r--r--nixpkgs/nixos/tests/nextcloud/basic.nix8
-rw-r--r--nixpkgs/nixos/tests/nextcloud/default.nix6
-rw-r--r--nixpkgs/nixos/tests/nextcloud/openssl-sse.nix109
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix48
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix10
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix39
-rw-r--r--nixpkgs/nixos/tests/nfs/kerberos.nix2
-rw-r--r--nixpkgs/nixos/tests/nfs/simple.nix3
-rw-r--r--nixpkgs/nixos/tests/nginx-globalredirect.nix24
-rw-r--r--nixpkgs/nixos/tests/nginx-http3.nix14
-rw-r--r--nixpkgs/nixos/tests/nginx-modsecurity.nix2
-rw-r--r--nixpkgs/nixos/tests/nginx-njs.nix27
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem20
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem27
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem20
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem27
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix144
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix30
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix14
-rw-r--r--nixpkgs/nixos/tests/nginx.nix26
-rw-r--r--nixpkgs/nixos/tests/nix-ld.nix5
-rw-r--r--nixpkgs/nixos/tests/nixops/default.nix9
-rw-r--r--nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix120
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix (renamed from nixpkgs/nixos/tests/extra-python-packages.nix)2
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/node-name.nix33
-rw-r--r--nixpkgs/nixos/tests/non-default-filesystems.nix132
-rw-r--r--nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix30
-rw-r--r--nixpkgs/nixos/tests/noto-fonts.nix4
-rw-r--r--nixpkgs/nixos/tests/nscd.nix142
-rw-r--r--nixpkgs/nixos/tests/ntfy-sh.nix26
-rw-r--r--nixpkgs/nixos/tests/nzbget.nix2
-rw-r--r--nixpkgs/nixos/tests/nzbhydra2.nix5
-rw-r--r--nixpkgs/nixos/tests/oci-containers.nix5
-rw-r--r--nixpkgs/nixos/tests/octoprint.nix61
-rw-r--r--nixpkgs/nixos/tests/odoo.nix6
-rw-r--r--nixpkgs/nixos/tests/ombi.nix4
-rw-r--r--nixpkgs/nixos/tests/openldap.nix2
-rw-r--r--nixpkgs/nixos/tests/opensearch.nix52
-rw-r--r--nixpkgs/nixos/tests/openvscode-server.nix22
-rw-r--r--nixpkgs/nixos/tests/orangefs.nix2
-rw-r--r--nixpkgs/nixos/tests/os-prober.nix2
-rw-r--r--nixpkgs/nixos/tests/outline.nix54
-rw-r--r--nixpkgs/nixos/tests/pam/pam-file-contents.nix1
-rw-r--r--nixpkgs/nixos/tests/pam/test_chfn.py2
-rw-r--r--nixpkgs/nixos/tests/pam/zfs-key.nix83
-rw-r--r--nixpkgs/nixos/tests/pantheon.nix20
-rw-r--r--nixpkgs/nixos/tests/paperless.nix12
-rw-r--r--nixpkgs/nixos/tests/parsedmarc/default.nix5
-rw-r--r--nixpkgs/nixos/tests/pass-secret-service.nix2
-rw-r--r--nixpkgs/nixos/tests/patroni.nix2
-rw-r--r--nixpkgs/nixos/tests/peroxide.nix16
-rw-r--r--nixpkgs/nixos/tests/pgadmin4-standalone.nix43
-rw-r--r--nixpkgs/nixos/tests/pgadmin4.nix187
-rw-r--r--nixpkgs/nixos/tests/phosh.nix70
-rw-r--r--nixpkgs/nixos/tests/photoprism.nix23
-rw-r--r--nixpkgs/nixos/tests/php/pcre.nix16
-rw-r--r--nixpkgs/nixos/tests/pinnwand.nix57
-rw-r--r--nixpkgs/nixos/tests/plasma-bigscreen.nix38
-rw-r--r--nixpkgs/nixos/tests/please.nix66
-rw-r--r--nixpkgs/nixos/tests/pleroma.nix4
-rw-r--r--nixpkgs/nixos/tests/podman/default.nix181
-rw-r--r--nixpkgs/nixos/tests/podman/dnsname.nix42
-rw-r--r--nixpkgs/nixos/tests/podman/tls-ghostunnel.nix3
-rw-r--r--nixpkgs/nixos/tests/polaris.nix4
-rw-r--r--nixpkgs/nixos/tests/pomerium.nix7
-rw-r--r--nixpkgs/nixos/tests/portunus.nix18
-rw-r--r--nixpkgs/nixos/tests/postgresql-jit.nix48
-rw-r--r--nixpkgs/nixos/tests/postgresql-wal-receiver.nix2
-rw-r--r--nixpkgs/nixos/tests/postgresql.nix93
-rw-r--r--nixpkgs/nixos/tests/power-profiles-daemon.nix1
-rw-r--r--nixpkgs/nixos/tests/powerdns-admin.nix24
-rw-r--r--nixpkgs/nixos/tests/pppd.nix6
-rw-r--r--nixpkgs/nixos/tests/predictable-interface-names.nix39
-rw-r--r--nixpkgs/nixos/tests/printing.nix186
-rw-r--r--nixpkgs/nixos/tests/prometheus-exporters.nix215
-rw-r--r--nixpkgs/nixos/tests/promscale.nix60
-rw-r--r--nixpkgs/nixos/tests/prowlarr.nix4
-rw-r--r--nixpkgs/nixos/tests/public-inbox.nix8
-rw-r--r--nixpkgs/nixos/tests/pufferpanel.nix74
-rw-r--r--nixpkgs/nixos/tests/pulseaudio.nix25
-rw-r--r--nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix36
-rw-r--r--nixpkgs/nixos/tests/quake3.nix95
-rw-r--r--nixpkgs/nixos/tests/rabbitmq.nix36
-rw-r--r--nixpkgs/nixos/tests/radarr.nix4
-rw-r--r--nixpkgs/nixos/tests/readarr.nix14
-rw-r--r--nixpkgs/nixos/tests/redis.nix10
-rw-r--r--nixpkgs/nixos/tests/resolv.nix46
-rw-r--r--nixpkgs/nixos/tests/restic.nix115
-rw-r--r--nixpkgs/nixos/tests/retroarch.nix4
-rw-r--r--nixpkgs/nixos/tests/rshim.nix25
-rw-r--r--nixpkgs/nixos/tests/sanoid.nix12
-rw-r--r--nixpkgs/nixos/tests/schleuder.nix4
-rw-r--r--nixpkgs/nixos/tests/seafile.nix12
-rw-r--r--nixpkgs/nixos/tests/sftpgo.nix384
-rw-r--r--nixpkgs/nixos/tests/sgtpuzzles.nix34
-rw-r--r--nixpkgs/nixos/tests/shadow.nix53
-rw-r--r--nixpkgs/nixos/tests/shadowsocks/common.nix1
-rw-r--r--nixpkgs/nixos/tests/signal-desktop.nix2
-rw-r--r--nixpkgs/nixos/tests/smokeping.nix2
-rw-r--r--nixpkgs/nixos/tests/soapui.nix2
-rw-r--r--nixpkgs/nixos/tests/solr.nix56
-rw-r--r--nixpkgs/nixos/tests/sonarr.nix4
-rw-r--r--nixpkgs/nixos/tests/sourcehut.nix8
-rw-r--r--nixpkgs/nixos/tests/spark/default.nix1
-rw-r--r--nixpkgs/nixos/tests/specialisation.nix43
-rw-r--r--nixpkgs/nixos/tests/sqlite3-to-mysql.nix65
-rw-r--r--nixpkgs/nixos/tests/sssd-ldap.nix87
-rw-r--r--nixpkgs/nixos/tests/sssd.nix1
-rw-r--r--nixpkgs/nixos/tests/stratis/default.nix8
-rw-r--r--nixpkgs/nixos/tests/stratis/encryption.nix32
-rw-r--r--nixpkgs/nixos/tests/stratis/simple.nix39
-rw-r--r--nixpkgs/nixos/tests/sudo.nix8
-rw-r--r--nixpkgs/nixos/tests/swap-file-btrfs.nix50
-rw-r--r--nixpkgs/nixos/tests/swap-partition.nix2
-rw-r--r--nixpkgs/nixos/tests/swap-random-encryption.nix80
-rw-r--r--nixpkgs/nixos/tests/switch-test.nix39
-rw-r--r--nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch25
-rw-r--r--nixpkgs/nixos/tests/systemd-boot.nix43
-rw-r--r--nixpkgs/nixos/tests/systemd-bpf.nix42
-rw-r--r--nixpkgs/nixos/tests/systemd-confinement.nix2
-rw-r--r--nixpkgs/nixos/tests/systemd-credentials-tpm2.nix124
-rw-r--r--nixpkgs/nixos/tests/systemd-homed.nix99
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix10
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix47
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix8
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-password.nix17
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix74
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-modprobe.nix17
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix84
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-networkd.nix75
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-simple.nix6
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-swraid.nix8
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-vconsole.nix33
-rw-r--r--nixpkgs/nixos/tests/systemd-machinectl.nix17
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix129
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-vrf.nix114
-rw-r--r--nixpkgs/nixos/tests/systemd-no-tainted.nix14
-rw-r--r--nixpkgs/nixos/tests/systemd-oomd.nix54
-rw-r--r--nixpkgs/nixos/tests/systemd-portabled.nix51
-rw-r--r--nixpkgs/nixos/tests/systemd-repart.nix192
-rw-r--r--nixpkgs/nixos/tests/systemd-shutdown.nix1
-rw-r--r--nixpkgs/nixos/tests/systemd-timesyncd.nix4
-rw-r--r--nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix35
-rw-r--r--nixpkgs/nixos/tests/systemd-userdbd.nix32
-rw-r--r--nixpkgs/nixos/tests/systemd.nix6
-rw-r--r--nixpkgs/nixos/tests/tandoor-recipes.nix43
-rw-r--r--nixpkgs/nixos/tests/taskserver.nix26
-rw-r--r--nixpkgs/nixos/tests/tayga.nix235
-rw-r--r--nixpkgs/nixos/tests/teleport.nix82
-rw-r--r--nixpkgs/nixos/tests/terminal-emulators.nix8
-rw-r--r--nixpkgs/nixos/tests/thelounge.nix2
-rw-r--r--nixpkgs/nixos/tests/timescaledb.nix93
-rw-r--r--nixpkgs/nixos/tests/tmate-ssh-server.nix73
-rw-r--r--nixpkgs/nixos/tests/tor.nix8
-rw-r--r--nixpkgs/nixos/tests/tracee.nix64
-rw-r--r--nixpkgs/nixos/tests/traefik.nix5
-rw-r--r--nixpkgs/nixos/tests/trafficserver.nix1
-rw-r--r--nixpkgs/nixos/tests/turbovnc-headless-server.nix2
-rw-r--r--nixpkgs/nixos/tests/tuxguitar.nix2
-rw-r--r--nixpkgs/nixos/tests/txredisapi.nix2
-rw-r--r--nixpkgs/nixos/tests/ulogd.nix82
-rw-r--r--nixpkgs/nixos/tests/unbound.nix4
-rw-r--r--nixpkgs/nixos/tests/unifi.nix2
-rw-r--r--nixpkgs/nixos/tests/upnp.nix4
-rw-r--r--nixpkgs/nixos/tests/uptime-kuma.nix17
-rw-r--r--nixpkgs/nixos/tests/v2ray.nix12
-rw-r--r--nixpkgs/nixos/tests/varnish.nix55
-rw-r--r--nixpkgs/nixos/tests/vault-agent.nix52
-rw-r--r--nixpkgs/nixos/tests/vaultwarden.nix45
-rw-r--r--nixpkgs/nixos/tests/vector.nix4
-rw-r--r--nixpkgs/nixos/tests/vengi-tools.nix4
-rw-r--r--nixpkgs/nixos/tests/vikunja.nix5
-rw-r--r--nixpkgs/nixos/tests/virtualbox.nix6
-rw-r--r--nixpkgs/nixos/tests/vscodium.nix19
-rw-r--r--nixpkgs/nixos/tests/warzone2100.nix26
-rw-r--r--nixpkgs/nixos/tests/web-apps/healthchecks.nix6
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon.nix170
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/default.nix9
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/remote-postgresql.nix160
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/script.nix54
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/standard.nix92
-rw-r--r--nixpkgs/nixos/tests/web-apps/monica.nix33
-rw-r--r--nixpkgs/nixos/tests/web-apps/netbox.nix297
-rw-r--r--nixpkgs/nixos/tests/web-apps/peering-manager.nix40
-rw-r--r--nixpkgs/nixos/tests/web-apps/peertube.nix7
-rw-r--r--nixpkgs/nixos/tests/web-apps/pixelfed/default.nix8
-rw-r--r--nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix38
-rw-r--r--nixpkgs/nixos/tests/web-apps/snipe-it.nix101
-rw-r--r--nixpkgs/nixos/tests/web-apps/writefreely.nix44
-rw-r--r--nixpkgs/nixos/tests/web-servers/agate.nix46
-rw-r--r--nixpkgs/nixos/tests/web-servers/stargazer.nix31
-rw-r--r--nixpkgs/nixos/tests/webhook.nix65
-rw-r--r--nixpkgs/nixos/tests/wiki-js.nix2
-rw-r--r--nixpkgs/nixos/tests/wine.nix5
-rw-r--r--nixpkgs/nixos/tests/wireguard/basic.nix3
-rw-r--r--nixpkgs/nixos/tests/wireguard/default.nix3
-rw-r--r--nixpkgs/nixos/tests/wireguard/generated.nix3
-rw-r--r--nixpkgs/nixos/tests/wireguard/namespaces.nix5
-rw-r--r--nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix3
-rw-r--r--nixpkgs/nixos/tests/wireguard/wg-quick.nix74
-rw-r--r--nixpkgs/nixos/tests/wordpress.nix33
-rw-r--r--nixpkgs/nixos/tests/wrappers.nix79
-rw-r--r--nixpkgs/nixos/tests/xautolock.nix4
-rw-r--r--nixpkgs/nixos/tests/xfce.nix2
-rw-r--r--nixpkgs/nixos/tests/xmpp/prosody.nix3
-rw-r--r--nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix8
-rw-r--r--nixpkgs/nixos/tests/xpadneo.nix18
-rw-r--r--nixpkgs/nixos/tests/xss-lock.nix8
-rw-r--r--nixpkgs/nixos/tests/yabar.nix10
-rw-r--r--nixpkgs/nixos/tests/yggdrasil.nix17
-rw-r--r--nixpkgs/nixos/tests/zammad.nix2
-rw-r--r--nixpkgs/nixos/tests/zfs.nix199
-rw-r--r--nixpkgs/nixos/tests/zigbee2mqtt.nix2
-rw-r--r--nixpkgs/nixos/tests/zram-generator.nix42
-rw-r--r--nixpkgs/nixos/tests/zrepl.nix6
2232 files changed, 71044 insertions, 59626 deletions
diff --git a/nixpkgs/nixos/doc/manual/.gitignore b/nixpkgs/nixos/doc/manual/.gitignore
deleted file mode 100644
index 879282624217..000000000000
--- a/nixpkgs/nixos/doc/manual/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-generated
-manual-combined.xml
diff --git a/nixpkgs/nixos/doc/manual/Makefile b/nixpkgs/nixos/doc/manual/Makefile
deleted file mode 100644
index b2b6481b20c7..000000000000
--- a/nixpkgs/nixos/doc/manual/Makefile
+++ /dev/null
@@ -1,30 +0,0 @@
-.PHONY: all
-all: manual-combined.xml
-
-.PHONY: debug
-debug: generated manual-combined.xml
-
-manual-combined.xml: generated *.xml **/*.xml
-	rm -f ./manual-combined.xml
-	nix-shell --pure -Q --packages xmloscopy \
-		--run "xmloscopy --docbook5 ./manual.xml ./manual-combined.xml"
-
-.PHONY: format
-format:
-	nix-shell --pure -Q --packages xmlformat \
-		--run "find ../../ -iname '*.xml' -type f -print0 | xargs -0 -I{} -n1 \
-		xmlformat --config-file '../xmlformat.conf' -i {}"
-
-.PHONY: fix-misc-xml
-fix-misc-xml:
-	find . -iname '*.xml' -type f \
-		-exec ../varlistentry-fixer.rb {} ';'
-
-.PHONY: clean
-clean:
-	rm -f manual-combined.xml generated
-
-generated:
-	nix-build ../../release.nix \
-		--attr manualGeneratedSources.x86_64-linux \
-		--out-link ./generated
diff --git a/nixpkgs/nixos/doc/manual/administration/containers.chapter.md b/nixpkgs/nixos/doc/manual/administration/containers.chapter.md
index ea51f91f698f..50493b562b54 100644
--- a/nixpkgs/nixos/doc/manual/administration/containers.chapter.md
+++ b/nixpkgs/nixos/doc/manual/administration/containers.chapter.md
@@ -21,8 +21,8 @@ which is often not what you want. By contrast, in the imperative
 approach, containers are configured and updated independently from the
 host system.
 
-```{=docbook}
-<xi:include href="imperative-containers.section.xml" />
-<xi:include href="declarative-containers.section.xml" />
-<xi:include href="container-networking.section.xml" />
+```{=include=} sections
+imperative-containers.section.md
+declarative-containers.section.md
+container-networking.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md b/nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md
index 00fd244bb91f..eaa50d3c663d 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_10;
+      services.postgresql.package = pkgs.postgresql_14;
       };
   };
 ```
diff --git a/nixpkgs/nixos/doc/manual/administration/running.md b/nixpkgs/nixos/doc/manual/administration/running.md
new file mode 100644
index 000000000000..48e8c7c6668b
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/administration/running.md
@@ -0,0 +1,14 @@
+# Administration {#ch-running}
+
+This chapter describes various aspects of managing a running NixOS system, such as how to use the {command}`systemd` service manager.
+
+```{=include=} chapters
+service-mgmt.chapter.md
+rebooting.chapter.md
+user-sessions.chapter.md
+control-groups.chapter.md
+logging.chapter.md
+cleaning-store.chapter.md
+containers.chapter.md
+troubleshooting.chapter.md
+```
diff --git a/nixpkgs/nixos/doc/manual/administration/running.xml b/nixpkgs/nixos/doc/manual/administration/running.xml
deleted file mode 100644
index d9fcc1aee263..000000000000
--- a/nixpkgs/nixos/doc/manual/administration/running.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<part xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="ch-running">
- <title>Administration</title>
- <partintro xml:id="ch-running-intro">
-  <para>
-   This chapter describes various aspects of managing a running NixOS system,
-   such as how to use the <command>systemd</command> service manager.
-  </para>
- </partintro>
- <xi:include href="../from_md/administration/service-mgmt.chapter.xml" />
- <xi:include href="../from_md/administration/rebooting.chapter.xml" />
- <xi:include href="../from_md/administration/user-sessions.chapter.xml" />
- <xi:include href="../from_md/administration/control-groups.chapter.xml" />
- <xi:include href="../from_md/administration/logging.chapter.xml" />
- <xi:include href="../from_md/administration/cleaning-store.chapter.xml" />
- <xi:include href="../from_md/administration/containers.chapter.xml" />
- <xi:include href="../from_md/administration/troubleshooting.chapter.xml" />
-</part>
diff --git a/nixpkgs/nixos/doc/manual/administration/service-mgmt.chapter.md b/nixpkgs/nixos/doc/manual/administration/service-mgmt.chapter.md
index bb0f9b62e913..674c73741680 100644
--- a/nixpkgs/nixos/doc/manual/administration/service-mgmt.chapter.md
+++ b/nixpkgs/nixos/doc/manual/administration/service-mgmt.chapter.md
@@ -75,7 +75,7 @@ necessary).
 
 Packages in Nixpkgs sometimes provide systemd units with them, usually
 in e.g `#pkg-out#/lib/systemd/`. Putting such a package in
-`environment.systemPackages` doesn\'t make the service available to
+`environment.systemPackages` doesn't make the service available to
 users or the system.
 
 In order to enable a systemd *system* service with provided upstream
@@ -87,9 +87,9 @@ systemd.packages = [ pkgs.packagekit ];
 
 Usually NixOS modules written by the community do the above, plus take
 care of other details. If a module was written for a service you are
-interested in, you\'d probably need only to use
+interested in, you'd probably need only to use
 `services.#name#.enable = true;`. These services are defined in
-Nixpkgs\' [ `nixos/modules/` directory
+Nixpkgs' [ `nixos/modules/` directory
 ](https://github.com/NixOS/nixpkgs/tree/master/nixos/modules). In case
 the service is simple enough, the above method should work, and start
 the service on boot.
@@ -98,8 +98,8 @@ the service on boot.
 differently. Given a package that has a systemd unit file at
 `#pkg-out#/lib/systemd/user/`, using [](#opt-systemd.packages) will
 make you able to start the service via `systemctl --user start`, but it
-won\'t start automatically on login. However, You can imperatively
-enable it by adding the package\'s attribute to
+won't start automatically on login. However, You can imperatively
+enable it by adding the package's attribute to
 [](#opt-systemd.packages) and then do this (e.g):
 
 ```ShellSession
@@ -113,7 +113,7 @@ If you are interested in a timer file, use `timers.target.wants` instead
 of `default.target.wants` in the 1st and 2nd command.
 
 Using `systemctl --user enable syncthing.service` instead of the above,
-will work, but it\'ll use the absolute path of `syncthing.service` for
+will work, but it'll use the absolute path of `syncthing.service` for
 the symlink, and this path is in `/nix/store/.../lib/systemd/user/`.
 Hence [garbage collection](#sec-nix-gc) will remove that file and you
 will wind up with a broken symlink in your systemd configuration, which
diff --git a/nixpkgs/nixos/doc/manual/administration/troubleshooting.chapter.md b/nixpkgs/nixos/doc/manual/administration/troubleshooting.chapter.md
index 548456eaf6d6..1253607f8efc 100644
--- a/nixpkgs/nixos/doc/manual/administration/troubleshooting.chapter.md
+++ b/nixpkgs/nixos/doc/manual/administration/troubleshooting.chapter.md
@@ -3,10 +3,10 @@
 This chapter describes solutions to common problems you might encounter
 when you manage your NixOS system.
 
-```{=docbook}
-<xi:include href="boot-problems.section.xml" />
-<xi:include href="maintenance-mode.section.xml" />
-<xi:include href="rollback.section.xml" />
-<xi:include href="store-corruption.section.xml" />
-<xi:include href="network-problems.section.xml" />
+```{=include=} sections
+boot-problems.section.md
+maintenance-mode.section.md
+rollback.section.md
+store-corruption.section.md
+network-problems.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/adding-custom-packages.section.md b/nixpkgs/nixos/doc/manual/configuration/adding-custom-packages.section.md
index 9219396722f0..89d329550613 100644
--- a/nixpkgs/nixos/doc/manual/configuration/adding-custom-packages.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/adding-custom-packages.section.md
@@ -94,6 +94,6 @@ environment.systemPackages = [ pkgs.appimage-run ];
 Then instead of running the AppImage "as-is", run `appimage-run foo.appimage`.
 
 To make other pre-built executables work on NixOS, you need to package them
-with Nix and special helpers like `autoPatchelfHook` or `buildFHSUserEnv`. See
+with Nix and special helpers like `autoPatchelfHook` or `buildFHSEnv`. See
 the [Nixpkgs manual](https://nixos.org/nixpkgs/manual) for details. This
 is complex and often doing a source build is easier.
diff --git a/nixpkgs/nixos/doc/manual/configuration/config-file.section.md b/nixpkgs/nixos/doc/manual/configuration/config-file.section.md
index f21ba113bf8c..b010026c5828 100644
--- a/nixpkgs/nixos/doc/manual/configuration/config-file.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/config-file.section.md
@@ -166,10 +166,10 @@ Packages
         pkgs.emacs
       ];
 
-    services.postgresql.package = pkgs.postgresql_10;
+    services.postgresql.package = pkgs.postgresql_14;
     ```
 
     The latter option definition changes the default PostgreSQL package
-    used by NixOS's PostgreSQL service to 10.x. For more information on
+    used by NixOS's PostgreSQL service to 14.x. For more information on
     packages, including how to add new ones, see
     [](#sec-custom-packages).
diff --git a/nixpkgs/nixos/doc/manual/configuration/config-syntax.chapter.md b/nixpkgs/nixos/doc/manual/configuration/config-syntax.chapter.md
index 56d093c0f6e8..9e606b2b82af 100644
--- a/nixpkgs/nixos/doc/manual/configuration/config-syntax.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/config-syntax.chapter.md
@@ -11,9 +11,8 @@ manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions), but
 here we give a short overview of the most important constructs useful in
 NixOS configuration files.
 
-```{=docbook}
-<xi:include href="config-file.section.xml" />
-<xi:include href="abstractions.section.xml" />
-<xi:include href="modularity.section.xml" />
-<xi:include href="summary.section.xml" />
+```{=include=} sections
+config-file.section.md
+abstractions.section.md
+modularity.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/configuration.md b/nixpkgs/nixos/doc/manual/configuration/configuration.md
new file mode 100644
index 000000000000..4c966f3325b9
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/configuration/configuration.md
@@ -0,0 +1,27 @@
+# Configuration {#ch-configuration}
+
+This chapter describes how to configure various aspects of a NixOS machine through the configuration file {file}`/etc/nixos/configuration.nix`. As described in [](#sec-changing-config), changes to this file only take effect after you run {command}`nixos-rebuild`.
+
+```{=include=} chapters
+config-syntax.chapter.md
+package-mgmt.chapter.md
+user-mgmt.chapter.md
+file-systems.chapter.md
+x-windows.chapter.md
+wayland.chapter.md
+gpu-accel.chapter.md
+xfce.chapter.md
+networking.chapter.md
+linux-kernel.chapter.md
+subversion.chapter.md
+```
+
+```{=include=} chapters
+@MODULE_CHAPTERS@
+```
+
+```{=include=} chapters
+profiles.chapter.md
+kubernetes.chapter.md
+```
+<!-- Apache; libvirtd virtualisation -->
diff --git a/nixpkgs/nixos/doc/manual/configuration/configuration.xml b/nixpkgs/nixos/doc/manual/configuration/configuration.xml
deleted file mode 100644
index b04316cfa48e..000000000000
--- a/nixpkgs/nixos/doc/manual/configuration/configuration.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<part xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="ch-configuration">
- <title>Configuration</title>
- <partintro xml:id="ch-configuration-intro">
-  <para>
-   This chapter describes how to configure various aspects of a NixOS machine
-   through the configuration file
-   <filename>/etc/nixos/configuration.nix</filename>. As described in
-   <xref linkend="sec-changing-config" />, changes to this file only take
-   effect after you run <command>nixos-rebuild</command>.
-  </para>
- </partintro>
- <xi:include href="../from_md/configuration/config-syntax.chapter.xml" />
- <xi:include href="../from_md/configuration/package-mgmt.chapter.xml" />
- <xi:include href="../from_md/configuration/user-mgmt.chapter.xml" />
- <xi:include href="../from_md/configuration/file-systems.chapter.xml" />
- <xi:include href="../from_md/configuration/x-windows.chapter.xml" />
- <xi:include href="../from_md/configuration/wayland.chapter.xml" />
- <xi:include href="../from_md/configuration/gpu-accel.chapter.xml" />
- <xi:include href="../from_md/configuration/xfce.chapter.xml" />
- <xi:include href="../from_md/configuration/networking.chapter.xml" />
- <xi:include href="../from_md/configuration/linux-kernel.chapter.xml" />
- <xi:include href="../from_md/configuration/subversion.chapter.xml" />
- <xi:include href="../generated/modules.xml" xpointer="xpointer(//section[@id='modules']/*)" />
- <xi:include href="../from_md/configuration/profiles.chapter.xml" />
- <xi:include href="../from_md/configuration/kubernetes.chapter.xml" />
-<!-- Apache; libvirtd virtualisation -->
-</part>
diff --git a/nixpkgs/nixos/doc/manual/configuration/customizing-packages.section.md b/nixpkgs/nixos/doc/manual/configuration/customizing-packages.section.md
index bceeeb2d7a16..709a07b09cea 100644
--- a/nixpkgs/nixos/doc/manual/configuration/customizing-packages.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/customizing-packages.section.md
@@ -12,6 +12,29 @@ Unfortunately, Nixpkgs currently lacks a way to query available
 configuration options.
 :::
 
+::: {.note}
+Alternatively, many packages come with extensions one might add.
+Examples include:
+- [`passExtensions.pass-otp`](https://search.nixos.org/packages/query=passExtensions.pass-otp)
+- [`python310Packages.requests`](https://search.nixos.org/packages/query=python310Packages.requests)
+
+You can use them like this:
+```nix
+environment.systemPackages = with pkgs; [
+  sl
+  (pass.withExtensions (subpkgs: with subpkgs; [
+    pass-audit
+    pass-otp
+    pass-genphrase
+  ]))
+  (python3.withPackages (subpkgs: with subpkgs; [
+      requests
+  ]))
+  cowsay
+];
+```
+:::
+
 Apart from high-level options, it's possible to tweak a package in
 almost arbitrary ways, such as changing or disabling dependencies of a
 package. For instance, the Emacs package in Nixpkgs by default has a
diff --git a/nixpkgs/nixos/doc/manual/configuration/declarative-packages.section.md b/nixpkgs/nixos/doc/manual/configuration/declarative-packages.section.md
index 337cdf8472e4..02eaa56192e4 100644
--- a/nixpkgs/nixos/doc/manual/configuration/declarative-packages.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/declarative-packages.section.md
@@ -40,7 +40,7 @@ configuration use `pkgs` prefix (variable).
 To "uninstall" a package, simply remove it from
 [](#opt-environment.systemPackages) and run `nixos-rebuild switch`.
 
-```{=docbook}
-<xi:include href="customizing-packages.section.xml" />
-<xi:include href="adding-custom-packages.section.xml" />
+```{=include=} sections
+customizing-packages.section.md
+adding-custom-packages.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/file-systems.chapter.md b/nixpkgs/nixos/doc/manual/configuration/file-systems.chapter.md
index 901e2e4f181b..aca978be064d 100644
--- a/nixpkgs/nixos/doc/manual/configuration/file-systems.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/file-systems.chapter.md
@@ -36,7 +36,7 @@ dropping you to the emergency shell. You can make a mount asynchronous
 and non-critical by adding `options = [ "nofail" ];`.
 :::
 
-```{=docbook}
-<xi:include href="luks-file-systems.section.xml" />
-<xi:include href="sshfs-file-systems.section.xml" />
+```{=include=} sections
+luks-file-systems.section.md
+sshfs-file-systems.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/gpu-accel.chapter.md b/nixpkgs/nixos/doc/manual/configuration/gpu-accel.chapter.md
index 835cbad55485..aa41e25e56f3 100644
--- a/nixpkgs/nixos/doc/manual/configuration/gpu-accel.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/gpu-accel.chapter.md
@@ -159,6 +159,40 @@ environment.variables.VK_ICD_FILENAMES =
   "/run/opengl-driver/share/vulkan/icd.d/radeon_icd.x86_64.json";
 ```
 
+## VA-API {#sec-gpu-accel-va-api}
+
+[VA-API (Video Acceleration API)](https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html)
+is an open-source library and API specification, which provides access to
+graphics hardware acceleration capabilities for video processing.
+
+VA-API drivers are loaded by `libva`. The version in nixpkgs is built to search
+the opengl driver path, so drivers can be installed in
+[](#opt-hardware.opengl.extraPackages).
+
+VA-API can be tested using:
+
+```ShellSession
+$ nix-shell -p libva-utils --run vainfo
+```
+
+### Intel {#sec-gpu-accel-va-api-intel}
+
+Modern Intel GPUs use the iHD driver, which can be installed with:
+
+```nix
+hardware.opengl.extraPackages = [
+  intel-media-driver
+];
+```
+
+Older Intel GPUs use the i965 driver, which can be installed with:
+
+```nix
+hardware.opengl.extraPackages = [
+  vaapiIntel
+];
+```
+
 ## Common issues {#sec-gpu-accel-common-issues}
 
 ### User permissions {#sec-gpu-accel-common-issues-permissions}
diff --git a/nixpkgs/nixos/doc/manual/configuration/kubernetes.chapter.md b/nixpkgs/nixos/doc/manual/configuration/kubernetes.chapter.md
index 93787577be9b..f39726090e43 100644
--- a/nixpkgs/nixos/doc/manual/configuration/kubernetes.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/kubernetes.chapter.md
@@ -17,7 +17,7 @@ services.kubernetes = {
 };
 ```
 
-Another way is to assign cluster roles (\"master\" and/or \"node\") to
+Another way is to assign cluster roles ("master" and/or "node") to
 the host. This enables apiserver, controllerManager, scheduler,
 addonManager, kube-proxy and etcd:
 
@@ -43,14 +43,6 @@ Note: Assigning either role will also default both
 and [](#opt-services.kubernetes.easyCerts)
 to true. This sets up flannel as CNI and activates automatic PKI bootstrapping.
 
-As of kubernetes 1.10.X it has been deprecated to open non-tls-enabled
-ports on kubernetes components. Thus, from NixOS 19.03 all plain HTTP
-ports have been disabled by default. While opening insecure ports is
-still possible, it is recommended not to bind these to other interfaces
-than loopback. To re-enable the insecure port on the apiserver, see options:
-[](#opt-services.kubernetes.apiserver.insecurePort) and
-[](#opt-services.kubernetes.apiserver.insecureBindAddress)
-
 ::: {.note}
 As of NixOS 19.03, it is mandatory to configure:
 [](#opt-services.kubernetes.masterAddress).
diff --git a/nixpkgs/nixos/doc/manual/configuration/linux-kernel.chapter.md b/nixpkgs/nixos/doc/manual/configuration/linux-kernel.chapter.md
index 1d06543d4f1e..f5bce99dd1bb 100644
--- a/nixpkgs/nixos/doc/manual/configuration/linux-kernel.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/linux-kernel.chapter.md
@@ -17,6 +17,16 @@ you may want to use one of the unversioned `pkgs.linuxPackages_*` aliases
 such as `pkgs.linuxPackages_latest`, that are kept up to date with new
 versions.
 
+Please note that the current convention in NixOS is to only keep actively
+maintained kernel versions on both unstable and the currently supported stable
+release(s) of NixOS. This means that a non-longterm kernel will be removed after it's
+abandoned by the kernel developers, even on stable NixOS versions. If you
+pin your kernel onto a non-longterm version, expect your evaluation to fail as
+soon as the version is out of maintenance.
+
+Longterm versions of kernels will be removed before the next stable NixOS that will
+exceed the maintenance period of the kernel version.
+
 The default Linux kernel configuration should be fine for most users.
 You can see the configuration of your current kernel with the following
 command:
@@ -72,61 +82,68 @@ boot.kernel.sysctl."net.ipv4.tcp_keepalive_time" = 120;
 sets the kernel's TCP keepalive time to 120 seconds. To see the
 available parameters, run `sysctl -a`.
 
-## Customize your kernel {#sec-linux-config-customizing}
+## Building a custom kernel {#sec-linux-config-customizing}
 
-The first step before compiling the kernel is to generate an appropriate
-`.config` configuration. Either you pass your own config via the
-`configfile` setting of `linuxKernel.manualConfig`:
+You can customize the default kernel configuration by overriding the arguments for your kernel package:
 
 ```nix
-custom-kernel = let base_kernel = linuxKernel.kernels.linux_4_9;
-  in super.linuxKernel.manualConfig {
-    inherit (super) stdenv hostPlatform;
-    inherit (base_kernel) src;
-    version = "${base_kernel.version}-custom";
-
-    configfile = /home/me/my_kernel_config;
-    allowImportFromDerivation = true;
-};
+pkgs.linux_latest.override {
+  ignoreConfigErrors = true;
+  autoModules = false;
+  kernelPreferBuiltin = true;
+  extraStructuredConfig = with lib.kernel; {
+    DEBUG_KERNEL = yes;
+    FRAME_POINTER = yes;
+    KGDB = yes;
+    KGDB_SERIAL_CONSOLE = yes;
+    DEBUG_INFO = yes;
+  };
+}
 ```
 
-You can edit the config with this snippet (by default `make
-   menuconfig` won\'t work out of the box on nixos):
+See `pkgs/os-specific/linux/kernel/generic.nix` for details on how these arguments
+affect the generated configuration. You can also build a custom version of Linux by calling
+`pkgs.buildLinux` directly, which requires the `src` and `version` arguments to be specified.
 
-```ShellSession
-nix-shell -E 'with import <nixpkgs> {}; kernelToOverride.overrideAttrs (o: {nativeBuildInputs=o.nativeBuildInputs ++ [ pkg-config ncurses ];})'
+To use your custom kernel package in your NixOS configuration, set
+
+```nix
+boot.kernelPackages = pkgs.linuxPackagesFor yourCustomKernel;
 ```
 
-or you can let nixpkgs generate the configuration. Nixpkgs generates it
-via answering the interactive kernel utility `make config`. The answers
-depend on parameters passed to
-`pkgs/os-specific/linux/kernel/generic.nix` (which you can influence by
-overriding `extraConfig, autoModules,
-   modDirVersion, preferBuiltin, extraConfig`).
+Note that this method will use the common configuration defined in `pkgs/os-specific/linux/kernel/common-config.nix`,
+which is suitable for a NixOS system.
+
+If you already have a generated configuration file, you can build a kernel that uses it with `pkgs.linuxManualConfig`:
 
 ```nix
-mptcp93.override ({
-  name="mptcp-local";
+let
+  baseKernel = pkgs.linux_latest;
+in pkgs.linuxManualConfig {
+  inherit (baseKernel) src modDirVersion;
+  version = "${baseKernel.version}-custom";
+  configfile = ./my_kernel_config;
+  allowImportFromDerivation = true;
+}
+```
 
-  ignoreConfigErrors = true;
-  autoModules = false;
-  kernelPreferBuiltin = true;
+::: {.note}
+The build will fail if `modDirVersion` does not match the source's `kernel.release` file,
+so `modDirVersion` should remain tied to `src`.
+:::
 
-  enableParallelBuilding = true;
+To edit the `.config` file for Linux X.Y, proceed as follows:
 
-  extraConfig = ''
-    DEBUG_KERNEL y
-    FRAME_POINTER y
-    KGDB y
-    KGDB_SERIAL_CONSOLE y
-    DEBUG_INFO y
-  '';
-});
+```ShellSession
+$ nix-shell '<nixpkgs>' -A linuxKernel.kernels.linux_X_Y.configEnv
+$ unpackPhase
+$ cd linux-*
+$ make nconfig
 ```
 
 ## Developing kernel modules {#sec-linux-config-developing-modules}
 
-When developing kernel modules it\'s often convenient to run
+When developing kernel modules it's often convenient to run
 edit-compile-run loop as quickly as possible. See below snippet as an
 example of developing `mellanox` drivers.
 
@@ -138,3 +155,26 @@ $ cd linux-*
 $ make -C $dev/lib/modules/*/build M=$(pwd)/drivers/net/ethernet/mellanox modules
 # insmod ./drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.ko
 ```
+
+## ZFS {#sec-linux-zfs}
+
+It's a common issue that the latest stable version of ZFS doesn't support the latest
+available Linux kernel. It is recommended to use the latest available LTS that's compatible
+with ZFS. Usually this is the default kernel provided by nixpkgs (i.e. `pkgs.linuxPackages`).
+
+Alternatively, it's possible to pin the system to the latest available kernel
+version *that is supported by ZFS* like this:
+
+```nix
+{
+  boot.kernelPackages = pkgs.zfs.latestCompatibleLinuxPackages;
+}
+```
+
+Please note that the version this attribute points to isn't monotonic because the latest kernel
+version only refers to kernel versions supported by the Linux developers. In other words,
+the latest kernel version that ZFS is compatible with may decrease over time.
+
+An example: the latest version ZFS is compatible with is 5.19 which is a non-longterm version. When 5.19
+is out of maintenance, the latest supported kernel version is 5.15 because it's longterm and the versions
+5.16, 5.17 and 5.18 are already out of maintenance because they're non-longterm.
diff --git a/nixpkgs/nixos/doc/manual/configuration/modularity.section.md b/nixpkgs/nixos/doc/manual/configuration/modularity.section.md
index 3395ace20c4f..2eff15387987 100644
--- a/nixpkgs/nixos/doc/manual/configuration/modularity.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/modularity.section.md
@@ -67,7 +67,13 @@ When using multiple modules, you may need to access configuration values
 defined in other modules. This is what the `config` function argument is
 for: it contains the complete, merged system configuration. That is,
 `config` is the result of combining the configurations returned by every
-module [^1] . For example, here is a module that adds some packages to
+module. (If you're wondering how it's possible that the (indirect) *result*
+of a function is passed as an *input* to that same function: that's
+because Nix is a "lazy" language --- it only computes values when
+they are needed. This works as long as no individual configuration
+value depends on itself.)
+
+For example, here is a module that adds some packages to
 [](#opt-environment.systemPackages) only if
 [](#opt-services.xserver.enable) is set to `true` somewhere else:
 
@@ -125,9 +131,3 @@ in
 
 { imports = [ (netConfig "nixos.localdomain") ]; }
 ```
-
-[^1]: If you're wondering how it's possible that the (indirect) *result*
-    of a function is passed as an *input* to that same function: that's
-    because Nix is a "lazy" language --- it only computes values when
-    they are needed. This works as long as no individual configuration
-    value depends on itself.
diff --git a/nixpkgs/nixos/doc/manual/configuration/networking.chapter.md b/nixpkgs/nixos/doc/manual/configuration/networking.chapter.md
index 529dc0610bda..abbd9766f173 100644
--- a/nixpkgs/nixos/doc/manual/configuration/networking.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/networking.chapter.md
@@ -3,14 +3,14 @@
 This section describes how to configure networking components
 on your NixOS machine.
 
-```{=docbook}
-<xi:include href="network-manager.section.xml" />
-<xi:include href="ssh.section.xml" />
-<xi:include href="ipv4-config.section.xml" />
-<xi:include href="ipv6-config.section.xml" />
-<xi:include href="firewall.section.xml" />
-<xi:include href="wireless.section.xml" />
-<xi:include href="ad-hoc-network-config.section.xml" />
-<xi:include href="renaming-interfaces.section.xml" />
+```{=include=} sections
+network-manager.section.md
+ssh.section.md
+ipv4-config.section.md
+ipv6-config.section.md
+firewall.section.md
+wireless.section.md
+ad-hoc-network-config.section.md
+renaming-interfaces.section.md
 ```
 <!-- TODO: OpenVPN, NAT -->
diff --git a/nixpkgs/nixos/doc/manual/configuration/package-mgmt.chapter.md b/nixpkgs/nixos/doc/manual/configuration/package-mgmt.chapter.md
index a6c414be59a9..1148bbe84740 100644
--- a/nixpkgs/nixos/doc/manual/configuration/package-mgmt.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/package-mgmt.chapter.md
@@ -12,7 +12,7 @@ NixOS has two distinct styles of package management:
     `nix-env` command. This style allows mixing packages from different
     Nixpkgs versions. It's the only choice for non-root users.
 
-```{=docbook}
-<xi:include href="declarative-packages.section.xml" />
-<xi:include href="ad-hoc-packages.section.xml" />
+```{=include=} sections
+declarative-packages.section.md
+ad-hoc-packages.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/profiles.chapter.md b/nixpkgs/nixos/doc/manual/configuration/profiles.chapter.md
index b4ae1b7d3faa..9f1f48f742ac 100644
--- a/nixpkgs/nixos/doc/manual/configuration/profiles.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/profiles.chapter.md
@@ -2,7 +2,7 @@
 
 In some cases, it may be desirable to take advantage of commonly-used,
 predefined configurations provided by nixpkgs, but different from those
-that come as default. This is a role fulfilled by NixOS\'s Profiles,
+that come as default. This is a role fulfilled by NixOS's Profiles,
 which come as files living in `<nixpkgs/nixos/modules/profiles>`. That
 is to say, expected usage is to add them to the imports list of your
 `/etc/configuration.nix` as such:
@@ -19,16 +19,16 @@ install media, many are actually intended to be used in real installs.
 What follows is a brief explanation on the purpose and use-case for each
 profile. Detailing each option configured by each one is out of scope.
 
-```{=docbook}
-<xi:include href="profiles/all-hardware.section.xml" />
-<xi:include href="profiles/base.section.xml" />
-<xi:include href="profiles/clone-config.section.xml" />
-<xi:include href="profiles/demo.section.xml" />
-<xi:include href="profiles/docker-container.section.xml" />
-<xi:include href="profiles/graphical.section.xml" />
-<xi:include href="profiles/hardened.section.xml" />
-<xi:include href="profiles/headless.section.xml" />
-<xi:include href="profiles/installation-device.section.xml" />
-<xi:include href="profiles/minimal.section.xml" />
-<xi:include href="profiles/qemu-guest.section.xml" />
+```{=include=} sections
+profiles/all-hardware.section.md
+profiles/base.section.md
+profiles/clone-config.section.md
+profiles/demo.section.md
+profiles/docker-container.section.md
+profiles/graphical.section.md
+profiles/hardened.section.md
+profiles/headless.section.md
+profiles/installation-device.section.md
+profiles/minimal.section.md
+profiles/qemu-guest.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/profiles/hardened.section.md b/nixpkgs/nixos/doc/manual/configuration/profiles/hardened.section.md
index 9fb5e18c384a..2e9bb196c054 100644
--- a/nixpkgs/nixos/doc/manual/configuration/profiles/hardened.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/profiles/hardened.section.md
@@ -7,7 +7,7 @@ This includes a hardened kernel, and limiting the system information
 available to processes through the `/sys` and
 `/proc` filesystems. It also disables the User Namespaces
 feature of the kernel, which stops Nix from being able to build anything
-(this particular setting can be overriden via
+(this particular setting can be overridden via
 [](#opt-security.allowUserNamespaces)). See the
 [profile source](https://github.com/nixos/nixpkgs/tree/master/nixos/modules/profiles/hardened.nix)
 for further detail on which settings are altered.
diff --git a/nixpkgs/nixos/doc/manual/configuration/ssh.section.md b/nixpkgs/nixos/doc/manual/configuration/ssh.section.md
index cba81eb43f49..9e239a848178 100644
--- a/nixpkgs/nixos/doc/manual/configuration/ssh.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/ssh.section.md
@@ -8,7 +8,7 @@ services.openssh.enable = true;
 
 By default, root logins using a password are disallowed. They can be
 disabled entirely by setting
-[](#opt-services.openssh.permitRootLogin) to `"no"`.
+[](#opt-services.openssh.settings.PermitRootLogin) to `"no"`.
 
 You can declaratively specify authorised RSA/DSA public keys for a user
 as follows:
diff --git a/nixpkgs/nixos/doc/manual/configuration/sshfs-file-systems.section.md b/nixpkgs/nixos/doc/manual/configuration/sshfs-file-systems.section.md
index 4dd1b203a249..d8c9dea6c337 100644
--- a/nixpkgs/nixos/doc/manual/configuration/sshfs-file-systems.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/sshfs-file-systems.section.md
@@ -8,7 +8,7 @@ It means that if you have SSH access to a machine, no additional setup is needed
 
 ## Interactive mounting {#sec-sshfs-interactive}
 
-In NixOS, SSHFS is packaged as <package>sshfs</package>.
+In NixOS, SSHFS is packaged as `sshfs`.
 Once installed, mounting a directory interactively is simple as running:
 ```ShellSession
 $ sshfs my-user@example.com:/my-dir /mnt/my-dir
diff --git a/nixpkgs/nixos/doc/manual/configuration/summary.section.md b/nixpkgs/nixos/doc/manual/configuration/summary.section.md
deleted file mode 100644
index 8abbbe257fd9..000000000000
--- a/nixpkgs/nixos/doc/manual/configuration/summary.section.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Syntax Summary {#sec-nix-syntax-summary}
-
-Below is a summary of the most important syntactic constructs in the Nix
-expression language. It's not complete. In particular, there are many
-other built-in functions. See the [Nix
-manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions) for
-the rest.
-
-| Example                                       | Description                                                                                                        |
-|-----------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
-| *Basic values*                                |                                                                                                                    |
-| `"Hello world"`                               | A string                                                                                                           |
-| `"${pkgs.bash}/bin/sh"`                       | A string containing an expression (expands to `"/nix/store/hash-bash-version/bin/sh"`)                             |
-| `true`, `false`                               | Booleans                                                                                                           |
-| `123`                                         | An integer                                                                                                         |
-| `./foo.png`                                   | A path (relative to the containing Nix expression)                                                                 |
-| *Compound values*                             |                                                                                                                    |
-| `{ x = 1; y = 2; }`                           | A set with attributes named `x` and `y`                                                                            |
-| `{ foo.bar = 1; }`                            | A nested set, equivalent to `{ foo = { bar = 1; }; }`                                                              |
-| `rec { x = "foo"; y = x + "bar"; }`           | A recursive set, equivalent to `{ x = "foo"; y = "foobar"; }`                                                      |
-| `[ "foo" "bar" ]`                             | A list with two elements                                                                                           |
-| *Operators*                                   |                                                                                                                    |
-| `"foo" + "bar"`                               | String concatenation                                                                                               |
-| `1 + 2`                                       | Integer addition                                                                                                   |
-| `"foo" == "f" + "oo"`                         | Equality test (evaluates to `true`)                                                                                |
-| `"foo" != "bar"`                              | Inequality test (evaluates to `true`)                                                                              |
-| `!true`                                       | Boolean negation                                                                                                   |
-| `{ x = 1; y = 2; }.x`                         | Attribute selection (evaluates to `1`)                                                                             |
-| `{ x = 1; y = 2; }.z or 3`                    | Attribute selection with default (evaluates to `3`)                                                                |
-| `{ x = 1; y = 2; } // { z = 3; }`             | Merge two sets (attributes in the right-hand set taking precedence)                                                |
-| *Control structures*                          |                                                                                                                    |
-| `if 1 + 1 == 2 then "yes!" else "no!"`        | Conditional expression                                                                                             |
-| `assert 1 + 1 == 2; "yes!"`                   | Assertion check (evaluates to `"yes!"`). See [](#sec-assertions) for using assertions in modules                   |
-| `let x = "foo"; y = "bar"; in x + y`          | Variable definition                                                                                                |
-| `with pkgs.lib; head [ 1 2 3 ]`               | Add all attributes from the given set to the scope (evaluates to `1`)                                              |
-| *Functions (lambdas)*                         |                                                                                                                    |
-| `x: x + 1`                                    | A function that expects an integer and returns it increased by 1                                                   |
-| `(x: x + 1) 100`                              | A function call (evaluates to 101)                                                                                 |
-| `let inc = x: x + 1; in inc (inc (inc 100))`  | A function bound to a variable and subsequently called by name (evaluates to 103)                                  |
-| `{ x, y }: x + y`                             | A function that expects a set with required attributes `x` and `y` and concatenates them                           |
-| `{ x, y ? "bar" }: x + y`                     | A function that expects a set with required attribute `x` and optional `y`, using `"bar"` as default value for `y` |
-| `{ x, y, ... }: x + y`                        | A function that expects a set with required attributes `x` and `y` and ignores any other attributes                |
-| `{ x, y } @ args: x + y`                      | A function that expects a set with required attributes `x` and `y`, and binds the whole set to `args`              |
-| *Built-in functions*                          |                                                                                                                    |
-| `import ./foo.nix`                            | Load and return Nix expression in given file                                                                       |
-| `map (x: x + x) [ 1 2 3 ]`                    | Apply a function to every element of a list (evaluates to `[ 2 4 6 ]`)                                             |
diff --git a/nixpkgs/nixos/doc/manual/configuration/user-mgmt.chapter.md b/nixpkgs/nixos/doc/manual/configuration/user-mgmt.chapter.md
index 37990664a8f1..b35b38f6e964 100644
--- a/nixpkgs/nixos/doc/manual/configuration/user-mgmt.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/user-mgmt.chapter.md
@@ -30,10 +30,9 @@ to your NixOS configuration. For instance, if you remove a user from
 [](#opt-users.users) and run nixos-rebuild, the user
 account will cease to exist. Also, imperative commands for managing users and
 groups, such as useradd, are no longer available. Passwords may still be
-assigned by setting the user\'s
+assigned by setting the user's
 [hashedPassword](#opt-users.users._name_.hashedPassword) option. A
-hashed password can be generated using `mkpasswd -m
-  sha-512`.
+hashed password can be generated using `mkpasswd`.
 
 A user ID (uid) is assigned automatically. You can also specify a uid
 manually by adding
diff --git a/nixpkgs/nixos/doc/manual/configuration/wayland.chapter.md b/nixpkgs/nixos/doc/manual/configuration/wayland.chapter.md
index a3a46aa3da6f..0f195bd66567 100644
--- a/nixpkgs/nixos/doc/manual/configuration/wayland.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/wayland.chapter.md
@@ -4,7 +4,7 @@ While X11 (see [](#sec-x11)) is still the primary display technology
 on NixOS, Wayland support is steadily improving. Where X11 separates the
 X Server and the window manager, on Wayland those are combined: a
 Wayland Compositor is like an X11 window manager, but also embeds the
-Wayland \'Server\' functionality. This means it is sufficient to install
+Wayland 'Server' functionality. This means it is sufficient to install
 a Wayland Compositor such as sway without separately enabling a Wayland
 server:
 
diff --git a/nixpkgs/nixos/doc/manual/configuration/wireless.section.md b/nixpkgs/nixos/doc/manual/configuration/wireless.section.md
index 6b223d843ac5..3299d2d7ecb8 100644
--- a/nixpkgs/nixos/doc/manual/configuration/wireless.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/wireless.section.md
@@ -50,7 +50,7 @@ networking.wireless.networks = {
   echelon = {
     pskRaw = "dca6d6ed41f4ab5a984c9f55f6f66d4efdc720ebf66959810f4329bb391c5435";
   };
-}
+};
 ```
 
 or you can use it to directly generate the `wpa_supplicant.conf`:
diff --git a/nixpkgs/nixos/doc/manual/configuration/x-windows.chapter.md b/nixpkgs/nixos/doc/manual/configuration/x-windows.chapter.md
index 2c80b786b267..bef35f448874 100644
--- a/nixpkgs/nixos/doc/manual/configuration/x-windows.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/x-windows.chapter.md
@@ -69,7 +69,7 @@ Wine, you should also set the following:
 hardware.opengl.driSupport32Bit = true;
 ```
 
-## Auto-login {#sec-x11-auto-login .unnumbered}
+## Auto-login {#sec-x11-auto-login}
 
 The x11 login screen can be skipped entirely, automatically logging you
 into your window manager and desktop environment when you boot your
@@ -81,7 +81,7 @@ second password to login can be redundant.
 
 To enable auto-login, you need to define your default window manager and
 desktop environment. If you wanted no desktop environment and i3 as your
-your window manager, you\'d define:
+your window manager, you'd define:
 
 ```nix
 services.xserver.displayManager.defaultSession = "none+i3";
@@ -96,7 +96,7 @@ services.xserver.displayManager.autoLogin.enable = true;
 services.xserver.displayManager.autoLogin.user = "alice";
 ```
 
-## Intel Graphics drivers {#sec-x11--graphics-cards-intel .unnumbered}
+## Intel Graphics drivers {#sec-x11--graphics-cards-intel}
 
 There are two choices for Intel Graphics drivers in X.org: `modesetting`
 (included in the xorg-server itself) and `intel` (provided by the
@@ -110,7 +110,7 @@ maintained but may perform worse in some cases (like in old chipsets).
 
 The second driver, `intel`, is specific to Intel GPUs, but not
 recommended by most distributions: it lacks several modern features (for
-example, it doesn\'t support Glamor) and the package hasn\'t been
+example, it doesn't support Glamor) and the package hasn't been
 officially updated since 2015.
 
 The results vary depending on the hardware, so you may have to try both
@@ -120,7 +120,6 @@ to set one. The recommended configuration for modern systems is:
 
 ```nix
 services.xserver.videoDrivers = [ "modesetting" ];
-services.xserver.useGlamor = true;
 ```
 
 If you experience screen tearing no matter what, this configuration was
@@ -137,7 +136,7 @@ services.xserver.deviceSection = ''
 Note that this will likely downgrade the performance compared to
 `modesetting` or `intel` with DRI 3 (default).
 
-## Proprietary NVIDIA drivers {#sec-x11-graphics-cards-nvidia .unnumbered}
+## Proprietary NVIDIA drivers {#sec-x11-graphics-cards-nvidia}
 
 NVIDIA provides a proprietary driver for its graphics cards that has
 better 3D performance than the X.org drivers. It is not enabled by
@@ -159,11 +158,11 @@ services.xserver.videoDrivers = [ "nvidiaLegacy304" ];
 You may need to reboot after enabling this driver to prevent a clash
 with other kernel modules.
 
-## Proprietary AMD drivers {#sec-x11--graphics-cards-amd .unnumbered}
+## Proprietary AMD drivers {#sec-x11--graphics-cards-amd}
 
 AMD provides a proprietary driver for its graphics cards that is not
 enabled by default because it's not Free Software, is often broken in
-nixpkgs and as of this writing doesn\'t offer more features or
+nixpkgs and as of this writing doesn't offer more features or
 performance. If you still want to use it anyway, you need to explicitly
 set:
 
@@ -174,7 +173,7 @@ services.xserver.videoDrivers = [ "amdgpu-pro" ];
 You will need to reboot after enabling this driver to prevent a clash
 with other kernel modules.
 
-## Touchpads {#sec-x11-touchpads .unnumbered}
+## Touchpads {#sec-x11-touchpads}
 
 Support for Synaptics touchpads (found in many laptops such as the Dell
 Latitude series) can be enabled as follows:
@@ -193,19 +192,19 @@ services.xserver.libinput.touchpad.tapping = false;
 Note: the use of `services.xserver.synaptics` is deprecated since NixOS
 17.09.
 
-## GTK/Qt themes {#sec-x11-gtk-and-qt-themes .unnumbered}
+## GTK/Qt themes {#sec-x11-gtk-and-qt-themes}
 
 GTK themes can be installed either to user profile or system-wide (via
 `environment.systemPackages`). To make Qt 5 applications look similar to
 GTK ones, you can use the following configuration:
 
 ```nix
-qt5.enable = true;
-qt5.platformTheme = "gtk2";
-qt5.style = "gtk2";
+qt.enable = true;
+qt.platformTheme = "gtk2";
+qt.style = "gtk2";
 ```
 
-## Custom XKB layouts {#custom-xkb-layouts .unnumbered}
+## Custom XKB layouts {#custom-xkb-layouts}
 
 It is possible to install custom [ XKB
 ](https://en.wikipedia.org/wiki/X_keyboard_extension) keyboard layouts
@@ -216,7 +215,7 @@ US layout, with an additional layer to type some greek symbols by
 pressing the right-alt key.
 
 Create a file called `us-greek` with the following content (under a
-directory called `symbols`; it\'s an XKB peculiarity that will help with
+directory called `symbols`; it's an XKB peculiarity that will help with
 testing):
 
 ```nix
@@ -250,7 +249,7 @@ The name (after `extraLayouts.`) should match the one given to the
 
 Applying this customization requires rebuilding several packages, and a
 broken XKB file can lead to the X session crashing at login. Therefore,
-you\'re strongly advised to **test your layout before applying it**:
+you're strongly advised to **test your layout before applying it**:
 
 ```ShellSession
 $ nix-shell -p xorg.xkbcomp
@@ -314,8 +313,8 @@ prefer to keep the layout definitions inside the NixOS configuration.
 
 Unfortunately, the Xorg server does not (currently) support setting a
 keymap directly but relies instead on XKB rules to select the matching
-components (keycodes, types, \...) of a layout. This means that
-components other than symbols won\'t be loaded by default. As a
+components (keycodes, types, ...) of a layout. This means that
+components other than symbols won't be loaded by default. As a
 workaround, you can set the keymap using `setxkbmap` at the start of the
 session with:
 
@@ -324,7 +323,7 @@ services.xserver.displayManager.sessionCommands = "setxkbmap -keycodes media";
 ```
 
 If you are manually starting the X server, you should set the argument
-`-xkbdir /etc/X11/xkb`, otherwise X won\'t find your layout files. For
+`-xkbdir /etc/X11/xkb`, otherwise X won't find your layout files. For
 example with `xinit` run
 
 ```ShellSession
diff --git a/nixpkgs/nixos/doc/manual/configuration/xfce.chapter.md b/nixpkgs/nixos/doc/manual/configuration/xfce.chapter.md
index ee60d465e3b3..a80be2b523e2 100644
--- a/nixpkgs/nixos/doc/manual/configuration/xfce.chapter.md
+++ b/nixpkgs/nixos/doc/manual/configuration/xfce.chapter.md
@@ -24,18 +24,18 @@ Some Xfce programs are not installed automatically. To install them
 manually (system wide), put them into your
 [](#opt-environment.systemPackages) from `pkgs.xfce`.
 
-## Thunar {#sec-xfce-thunar-plugins .unnumbered}
+## Thunar {#sec-xfce-thunar-plugins}
 
 Thunar (the Xfce file manager) is automatically enabled when Xfce is
 enabled. To enable Thunar without enabling Xfce, use the configuration
 option [](#opt-programs.thunar.enable) instead of simply adding
 `pkgs.xfce.thunar` to [](#opt-environment.systemPackages).
 
-If you\'d like to add extra plugins to Thunar, add them to
-[](#opt-programs.thunar.plugins). You shouldn\'t just add them to
+If you'd like to add extra plugins to Thunar, add them to
+[](#opt-programs.thunar.plugins). You shouldn't just add them to
 [](#opt-environment.systemPackages).
 
-## Troubleshooting {#sec-xfce-troubleshooting .unnumbered}
+## Troubleshooting {#sec-xfce-troubleshooting}
 
 Even after enabling udisks2, volume management might not work. Thunar
 and/or the desktop takes time to show up. Thunar will spit out this kind
@@ -46,7 +46,7 @@ Thunar:2410): GVFS-RemoteVolumeMonitor-WARNING **: remote volume monitor with db
 ```
 
 This is caused by some needed GNOME services not running. This is all
-fixed by enabling \"Launch GNOME services on startup\" in the Advanced
+fixed by enabling "Launch GNOME services on startup" in the Advanced
 tab of the Session and Startup settings panel. Alternatively, you can
 run this command to do the same thing.
 
@@ -54,4 +54,4 @@ run this command to do the same thing.
 $ xfconf-query -c xfce4-session -p /compat/LaunchGNOME -s true
 ```
 
-A log-out and re-log will be needed for this to take effect.
+It is necessary to log out and log in again for this to take effect.
diff --git a/nixpkgs/nixos/doc/manual/contributing-to-this-manual.chapter.md b/nixpkgs/nixos/doc/manual/contributing-to-this-manual.chapter.md
index 26813d1042d6..c306cc084cdb 100644
--- a/nixpkgs/nixos/doc/manual/contributing-to-this-manual.chapter.md
+++ b/nixpkgs/nixos/doc/manual/contributing-to-this-manual.chapter.md
@@ -1,13 +1,34 @@
 # Contributing to this manual {#chap-contributing}
 
-The DocBook and CommonMark sources of NixOS' manual are in the [nixos/doc/manual](https://github.com/NixOS/nixpkgs/tree/master/nixos/doc/manual) subdirectory of the [Nixpkgs](https://github.com/NixOS/nixpkgs) repository.
+The [DocBook] and CommonMark sources of the NixOS manual are in the [nixos/doc/manual](https://github.com/NixOS/nixpkgs/tree/master/nixos/doc/manual) subdirectory of the [Nixpkgs](https://github.com/NixOS/nixpkgs) repository.
 
 You can quickly check your edits with the following:
 
 ```ShellSession
 $ cd /path/to/nixpkgs
-$ ./nixos/doc/manual/md-to-db.sh
 $ nix-build nixos/release.nix -A manual.x86_64-linux
 ```
 
 If the build succeeds, the manual will be in `./result/share/doc/nixos/index.html`.
+
+**Contributing to the man pages**
+
+The man pages are written in [DocBook] which is XML.
+
+To see what your edits look like:
+
+```ShellSession
+$ cd /path/to/nixpkgs
+$ nix-build nixos/release.nix -A manpages.x86_64-linux
+```
+
+You can then read the man page you edited by running
+
+```ShellSession
+$ man --manpath=result/share/man nixos-rebuild # Replace nixos-rebuild with the command whose manual you edited
+```
+
+If you're on a different architecture that's supported by NixOS (check nixos/release.nix) then replace `x86_64-linux` with the architecture.
+`nix-build` will complain otherwise, but should also tell you which architecture you have + the supported ones.
+
+[DocBook]: https://en.wikipedia.org/wiki/DocBook
diff --git a/nixpkgs/nixos/doc/manual/default.nix b/nixpkgs/nixos/doc/manual/default.nix
index 1cd769b6a544..68132f302e42 100644
--- a/nixpkgs/nixos/doc/manual/default.nix
+++ b/nixpkgs/nixos/doc/manual/default.nix
@@ -6,18 +6,23 @@
 , extraSources ? []
 , baseOptionsJSON ? null
 , warningsAreErrors ? true
+, allowDocBook ? true
 , prefix ? ../../..
 }:
 
 with pkgs;
 
 let
+  inherit (lib) hasPrefix removePrefix;
+
   lib = pkgs.lib;
 
   docbook_xsl_ns = pkgs.docbook-xsl-ns.override {
     withManOptDedupPatch = true;
   };
 
+  manpageUrls = pkgs.path + "/doc/manpage-urls.json";
+
   # We need to strip references to /nix/store/* from options,
   # including any `extraSources` if some modules came from elsewhere,
   # or else the build will fail.
@@ -28,36 +33,40 @@ let
   stripAnyPrefixes = lib.flip (lib.foldr lib.removePrefix) prefixesToStrip;
 
   optionsDoc = buildPackages.nixosOptionsDoc {
-    inherit options revision baseOptionsJSON warningsAreErrors;
+    inherit options revision baseOptionsJSON warningsAreErrors allowDocBook;
     transformOptions = opt: opt // {
       # Clean up declaration sites to not refer to the NixOS source tree.
       declarations = map stripAnyPrefixes opt.declarations;
     };
   };
 
-  sources = lib.sourceFilesBySuffices ./. [".xml"];
-
-  modulesDoc = builtins.toFile "modules.xml" ''
-    <section xmlns:xi="http://www.w3.org/2001/XInclude" id="modules">
-    ${(lib.concatMapStrings (path: ''
-      <xi:include href="${path}" />
-    '') (lib.catAttrs "value" config.meta.doc))}
-    </section>
-  '';
-
-  generatedSources = runCommand "generated-docbook" {} ''
-    mkdir $out
-    ln -s ${modulesDoc} $out/modules.xml
-    ln -s ${optionsDoc.optionsDocBook} $out/options-db.xml
-    printf "%s" "${version}" > $out/version
-  '';
-
-  copySources =
-    ''
-      cp -prd $sources/* . # */
-      ln -s ${generatedSources} ./generated
-      chmod -R u+w .
-    '';
+  nixos-lib = import ../../lib { };
+
+  testOptionsDoc = let
+      eval = nixos-lib.evalTest {
+        # Avoid evaluating a NixOS config prototype.
+        config.node.type = lib.types.deferredModule;
+        options._module.args = lib.mkOption { internal = true; };
+      };
+    in buildPackages.nixosOptionsDoc {
+      inherit (eval) options;
+      inherit revision;
+      transformOptions = opt: opt // {
+        # Clean up declaration sites to not refer to the NixOS source tree.
+        declarations =
+          map
+            (decl:
+              if hasPrefix (toString ../../..) (toString decl)
+              then
+                let subpath = removePrefix "/" (removePrefix (toString ../../..) (toString decl));
+                in { url = "https://github.com/NixOS/nixpkgs/blob/master/${subpath}"; name = subpath; }
+              else decl)
+            opt.declarations;
+      };
+      documentType = "none";
+      variablelistId = "test-options-list";
+      optionIdPrefix = "test-opt-";
+    };
 
   toc = builtins.toFile "toc.xml"
     ''
@@ -70,11 +79,14 @@ let
     '';
 
   manualXsltprocOptions = toString [
-    "--param section.autolabel 1"
-    "--param section.label.includes.component.label 1"
+    "--param chapter.autolabel 0"
+    "--param part.autolabel 0"
+    "--param preface.autolabel 0"
+    "--param reference.autolabel 0"
+    "--param section.autolabel 0"
     "--stringparam html.stylesheet 'style.css overrides.css highlightjs/mono-blue.css'"
     "--stringparam html.script './highlightjs/highlight.pack.js ./highlightjs/loader.js'"
-    "--param xref.with.number.and.title 1"
+    "--param xref.with.number.and.title 0"
     "--param toc.section.depth 0"
     "--param generate.consistent.ids 1"
     "--stringparam admon.style ''"
@@ -86,100 +98,117 @@ let
     "--stringparam chunk.toc ${toc}"
   ];
 
+  linterFunctions = ''
+    # outputs the context of an xmllint error output
+    # LEN lines around the failing line are printed
+    function context {
+      # length of context
+      local LEN=6
+      # lines to print before error line
+      local BEFORE=4
+
+      # xmllint output lines are:
+      # file.xml:1234: there was an error on line 1234
+      while IFS=':' read -r file line rest; do
+        echo
+        if [[ -n "$rest" ]]; then
+          echo "$file:$line:$rest"
+          local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1))
+          # number lines & filter context
+          nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p"
+        else
+          if [[ -n "$line" ]]; then
+            echo "$file:$line"
+          else
+            echo "$file"
+          fi
+        fi
+      done
+    }
+
+    function lintrng {
+      xmllint --debug --noout --nonet \
+        --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
+        "$1" \
+        2>&1 | context 1>&2
+        # ^ redirect assumes xmllint doesn’t print to stdout
+    }
+  '';
+
+  prepareManualFromMD = ''
+    cp -r --no-preserve=all $inputs/* .
+
+    substituteInPlace ./manual.md \
+      --replace '@NIXOS_VERSION@' "${version}"
+    substituteInPlace ./configuration/configuration.md \
+      --replace \
+          '@MODULE_CHAPTERS@' \
+          ${lib.escapeShellArg (lib.concatMapStringsSep "\n" (p: "${p.value}") config.meta.doc)}
+    substituteInPlace ./nixos-options.md \
+      --replace \
+        '@NIXOS_OPTIONS_JSON@' \
+        ${optionsDoc.optionsJSON}/share/doc/nixos/options.json
+    substituteInPlace ./development/writing-nixos-tests.section.md \
+      --replace \
+        '@NIXOS_TEST_OPTIONS_JSON@' \
+        ${testOptionsDoc.optionsJSON}/share/doc/nixos/options.json
+  '';
+
   manual-combined = runCommand "nixos-manual-combined"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+    { inputs = lib.sourceFilesBySuffices ./. [ ".xml" ".md" ];
+      nativeBuildInputs = [ pkgs.nixos-render-docs pkgs.libxml2.bin pkgs.libxslt.bin ];
       meta.description = "The NixOS manual as plain docbook XML";
     }
     ''
-      ${copySources}
-
-      xmllint --xinclude --output ./manual-combined.xml ./manual.xml
-      xmllint --xinclude --noxincludenode \
-         --output ./man-pages-combined.xml ./man-pages.xml
-
-      # outputs the context of an xmllint error output
-      # LEN lines around the failing line are printed
-      function context {
-        # length of context
-        local LEN=6
-        # lines to print before error line
-        local BEFORE=4
-
-        # xmllint output lines are:
-        # file.xml:1234: there was an error on line 1234
-        while IFS=':' read -r file line rest; do
-          echo
-          if [[ -n "$rest" ]]; then
-            echo "$file:$line:$rest"
-            local FROM=$(($line>$BEFORE ? $line - $BEFORE : 1))
-            # number lines & filter context
-            nl --body-numbering=a "$file" | sed -n "$FROM,+$LEN p"
-          else
-            if [[ -n "$line" ]]; then
-              echo "$file:$line"
-            else
-              echo "$file"
-            fi
-          fi
-        done
-      }
+      ${prepareManualFromMD}
 
-      function lintrng {
-        xmllint --debug --noout --nonet \
-          --relaxng ${docbook5}/xml/rng/docbook/docbook.rng \
-          "$1" \
-          2>&1 | context 1>&2
-          # ^ redirect assumes xmllint doesn’t print to stdout
-      }
+      nixos-render-docs -j $NIX_BUILD_CORES manual docbook \
+        --manpage-urls ${manpageUrls} \
+        --revision ${lib.escapeShellArg revision} \
+        ./manual.md \
+        ./manual-combined-pre.xml
+
+      xsltproc \
+        -o manual-combined.xml ${./../../lib/make-options-doc/postprocess-option-descriptions.xsl} \
+        manual-combined-pre.xml
+
+      ${linterFunctions}
 
       mkdir $out
       cp manual-combined.xml $out/
-      cp man-pages-combined.xml $out/
 
       lintrng $out/manual-combined.xml
-      lintrng $out/man-pages-combined.xml
     '';
 
-  olinkDB = runCommand "manual-olinkdb"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+  manpages-combined = runCommand "nixos-manpages-combined.xml"
+    { nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+      meta.description = "The NixOS manpages as plain docbook XML";
     }
     ''
-      xsltproc \
-        ${manualXsltprocOptions} \
-        --stringparam collect.xref.targets only \
-        --stringparam targets.filename "$out/manual.db" \
-        --nonet \
-        ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
-        ${manual-combined}/manual-combined.xml
+      mkdir generated
+      cp -prd ${./man-pages.xml} man-pages.xml
+      ln -s ${optionsDoc.optionsDocBook} generated/options-db.xml
+
+      xmllint --xinclude --noxincludenode --output $out ./man-pages.xml
+
+      ${linterFunctions}
 
-      cat > "$out/olinkdb.xml" <<EOF
-      <?xml version="1.0" encoding="utf-8"?>
-      <!DOCTYPE targetset SYSTEM
-        "file://${docbook_xsl_ns}/xml/xsl/docbook/common/targetdatabase.dtd" [
-        <!ENTITY manualtargets SYSTEM "file://$out/manual.db">
-      ]>
-      <targetset>
-        <targetsetinfo>
-            Allows for cross-referencing olinks between the manpages
-            and manual.
-        </targetsetinfo>
-
-        <document targetdoc="manual">&manualtargets;</document>
-      </targetset>
-      EOF
+      lintrng $out
     '';
 
 in rec {
-  inherit generatedSources;
-
-  inherit (optionsDoc) optionsJSON optionsNix optionsDocBook;
+  inherit (optionsDoc) optionsJSON optionsNix optionsDocBook optionsUsedDocbook;
 
   # Generate the NixOS manual.
   manualHTML = runCommand "nixos-manual-html"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+    { nativeBuildInputs =
+        if allowDocBook then [
+          buildPackages.libxml2.bin
+          buildPackages.libxslt.bin
+        ] else [
+          buildPackages.nixos-render-docs
+        ];
+      inputs = lib.optionals (! allowDocBook) (lib.sourceFilesBySuffices ./. [ ".md" ]);
       meta.description = "The NixOS manual in HTML format";
       allowedReferences = ["out"];
     }
@@ -187,24 +216,44 @@ in rec {
       # Generate the HTML manual.
       dst=$out/share/doc/nixos
       mkdir -p $dst
-      xsltproc \
-        ${manualXsltprocOptions} \
-        --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
-        --stringparam id.warnings "1" \
-        --nonet --output $dst/ \
-        ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
-        ${manual-combined}/manual-combined.xml \
-        |& tee xsltproc.out
-      grep "^ID recommended on" xsltproc.out &>/dev/null && echo "error: some IDs are missing" && false
-      rm xsltproc.out
-
-      mkdir -p $dst/images/callouts
-      cp ${docbook_xsl_ns}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/
 
       cp ${../../../doc/style.css} $dst/style.css
       cp ${../../../doc/overrides.css} $dst/overrides.css
       cp -r ${pkgs.documentation-highlighter} $dst/highlightjs
 
+      ${if allowDocBook then ''
+          xsltproc \
+            ${manualXsltprocOptions} \
+            --stringparam id.warnings "1" \
+            --nonet --output $dst/ \
+            ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
+            ${manual-combined}/manual-combined.xml \
+            |& tee xsltproc.out
+          grep "^ID recommended on" xsltproc.out &>/dev/null && echo "error: some IDs are missing" && false
+          rm xsltproc.out
+
+          mkdir -p $dst/images/callouts
+          cp ${docbook_xsl_ns}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/
+        '' else ''
+          ${prepareManualFromMD}
+
+          # TODO generator is set like this because the docbook/md manual compare workflow will
+          # trigger if it's different
+          nixos-render-docs -j $NIX_BUILD_CORES manual html \
+            --manpage-urls ${manpageUrls} \
+            --revision ${lib.escapeShellArg revision} \
+            --generator "DocBook XSL Stylesheets V${docbook_xsl_ns.version}" \
+            --stylesheet style.css \
+            --stylesheet overrides.css \
+            --stylesheet highlightjs/mono-blue.css \
+            --script ./highlightjs/highlight.pack.js \
+            --script ./highlightjs/loader.js \
+            --toc-depth 1 \
+            --chunk-toc-depth 1 \
+            ./manual.md \
+            $dst/index.html
+        ''}
+
       mkdir -p $out/nix-support
       echo "nix-build out $out" >> $out/nix-support/hydra-build-products
       echo "doc manual $dst" >> $out/nix-support/hydra-build-products
@@ -217,8 +266,7 @@ in rec {
   manualHTMLIndex = "${manualHTML}/share/doc/nixos/index.html";
 
   manualEpub = runCommand "nixos-manual-epub"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin buildPackages.zip ];
+    { nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin buildPackages.zip ];
     }
     ''
       # Generate the epub manual.
@@ -226,7 +274,6 @@ in rec {
 
       xsltproc \
         ${manualXsltprocOptions} \
-        --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
         --nonet --xinclude --output $dst/epub/ \
         ${docbook_xsl_ns}/xml/xsl/docbook/epub/docbook.xsl \
         ${manual-combined}/manual-combined.xml
@@ -247,22 +294,38 @@ in rec {
 
   # Generate the NixOS manpages.
   manpages = runCommand "nixos-manpages"
-    { inherit sources;
-      nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
+    { nativeBuildInputs = [
+        buildPackages.installShellFiles
+      ] ++ lib.optionals allowDocBook [
+        buildPackages.libxml2.bin
+        buildPackages.libxslt.bin
+      ] ++ lib.optionals (! allowDocBook) [
+        buildPackages.nixos-render-docs
+      ];
       allowedReferences = ["out"];
     }
     ''
       # Generate manpages.
-      mkdir -p $out/share/man
-      xsltproc --nonet \
-        --maxdepth 6000 \
-        --param man.output.in.separate.dir 1 \
-        --param man.output.base.dir "'$out/share/man/'" \
-        --param man.endnotes.are.numbered 0 \
-        --param man.break.after.slash 1 \
-        --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
-        ${docbook_xsl_ns}/xml/xsl/docbook/manpages/docbook.xsl \
-        ${manual-combined}/man-pages-combined.xml
+      mkdir -p $out/share/man/man8
+      installManPage ${./manpages}/*
+      ${if allowDocBook
+        then ''
+          xsltproc --nonet \
+            --maxdepth 6000 \
+            --param man.output.in.separate.dir 1 \
+            --param man.output.base.dir "'$out/share/man/'" \
+            --param man.endnotes.are.numbered 0 \
+            --param man.break.after.slash 1 \
+            ${docbook_xsl_ns}/xml/xsl/docbook/manpages/docbook.xsl \
+            ${manpages-combined}
+        ''
+        else ''
+          mkdir -p $out/share/man/man5
+          nixos-render-docs -j $NIX_BUILD_CORES options manpage \
+            --revision ${lib.escapeShellArg revision} \
+            ${optionsJSON}/share/doc/nixos/options.json \
+            $out/share/man/man5/configuration.nix.5
+        ''}
     '';
 
 }
diff --git a/nixpkgs/nixos/doc/manual/development/activation-script.section.md b/nixpkgs/nixos/doc/manual/development/activation-script.section.md
index 1aee252fddea..c339258c6dc4 100644
--- a/nixpkgs/nixos/doc/manual/development/activation-script.section.md
+++ b/nixpkgs/nixos/doc/manual/development/activation-script.section.md
@@ -34,7 +34,7 @@ read which is set to `dry-activate` when a dry activation is done.
 
 An activation script can write to special files instructing
 `switch-to-configuration` to restart/reload units. The script will take these
-requests into account and will incorperate the unit configuration as described
+requests into account and will incorporate the unit configuration as described
 above. This means that the activation script will "fake" a modified unit file
 and `switch-to-configuration` will act accordingly. By doing so, configuration
 like [systemd.services.\<name\>.restartIfChanged](#opt-systemd.services) is
@@ -49,7 +49,7 @@ dry activation being `/run/nixos/dry-activation-restart-list` and
 `/run/nixos/dry-activation-reload-list`. Those files can contain
 newline-separated lists of unit names where duplicates are being ignored. These
 files are not create automatically and activation scripts must take the
-possiblility into account that they have to create them first.
+possibility into account that they have to create them first.
 
 ## NixOS snippets {#sec-activation-script-nixos-snippets}
 
diff --git a/nixpkgs/nixos/doc/manual/development/bootspec.chapter.md b/nixpkgs/nixos/doc/manual/development/bootspec.chapter.md
new file mode 100644
index 000000000000..96c12f24e7f1
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/development/bootspec.chapter.md
@@ -0,0 +1,36 @@
+# Experimental feature: Bootspec {#sec-experimental-bootspec}
+
+Bootspec is a experimental feature, introduced in the [RFC-0125 proposal](https://github.com/NixOS/rfcs/pull/125), the reference implementation can be found [there](https://github.com/NixOS/nixpkgs/pull/172237) in order to standardize bootloader support
+and advanced boot workflows such as SecureBoot and potentially more.
+
+You can enable the creation of bootspec documents through [`boot.bootspec.enable = true`](options.html#opt-boot.bootspec.enable), which will prompt a warning until [RFC-0125](https://github.com/NixOS/rfcs/pull/125) is officially merged.
+
+## Schema {#sec-experimental-bootspec-schema}
+
+The bootspec schema is versioned and validated against [a CUE schema file](https://cuelang.org/) which should considered as the source of truth for your applications.
+
+You will find the current version [here](../../../modules/system/activation/bootspec.cue).
+
+## Extensions mechanism {#sec-experimental-bootspec-extensions}
+
+Bootspec cannot account for all usecases.
+
+For this purpose, Bootspec offers a generic extension facility [`boot.bootspec.extensions`](options.html#opt-boot.bootspec.extensions) which can be used to inject any data needed for your usecases.
+
+An example for SecureBoot is to get the Nix store path to `/etc/os-release` in order to bake it into a unified kernel image:
+
+```nix
+{ config, lib, ... }: {
+  boot.bootspec.extensions = {
+    "org.secureboot.osRelease" = config.environment.etc."os-release".source;
+  };
+}
+```
+
+To reduce incompatibility and prevent names from clashing between applications, it is **highly recommended** to use a unique namespace for your extensions.
+
+## External bootloaders {#sec-experimental-bootspec-external-bootloaders}
+
+It is possible to enable your own bootloader through [`boot.loader.external.installHook`](options.html#opt-boot.loader.external.installHook) which can wrap an existing bootloader.
+
+Currently, there is no good story to compose existing bootloaders to enrich their features, e.g. SecureBoot, etc. It will be necessary to reimplement or reuse existing parts.
diff --git a/nixpkgs/nixos/doc/manual/development/developing-the-test-driver.chapter.md b/nixpkgs/nixos/doc/manual/development/developing-the-test-driver.chapter.md
new file mode 100644
index 000000000000..d64574fa62aa
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/development/developing-the-test-driver.chapter.md
@@ -0,0 +1,45 @@
+
+# Developing the NixOS Test Driver {#chap-developing-the-test-driver}
+
+The NixOS test framework is a project of its own.
+
+It consists of roughly the following components:
+
+ - `nixos/lib/test-driver`: The Python framework that sets up the test and runs the [`testScript`](#test-opt-testScript)
+ - `nixos/lib/testing`: The Nix code responsible for the wiring, written using the (NixOS) Module System.
+
+These components are exposed publicly through:
+
+ - `nixos/lib/default.nix`: The public interface that exposes the `nixos/lib/testing` entrypoint.
+ - `flake.nix`: Exposes the `lib.nixos`, including the public test interface.
+
+Beyond the test driver itself, its integration into NixOS and Nixpkgs is important.
+
+ - `pkgs/top-level/all-packages.nix`: Defines the `nixosTests` attribute, used
+   by the package `tests` attributes and OfBorg.
+ - `nixos/release.nix`: Defines the `tests` attribute built by Hydra, independently, but analogous to `nixosTests`
+ - `nixos/release-combined.nix`: Defines which tests are channel blockers.
+
+Finally, we have legacy entrypoints that users should move away from, but are cared for on a best effort basis.
+These include `pkgs.nixosTest`, `testing-python.nix` and `make-test-python.nix`.
+
+## Testing changes to the test framework {#sec-test-the-test-framework}
+
+We currently have limited unit tests for the framework itself. You may run these with `nix-build -A nixosTests.nixos-test-driver`.
+
+When making significant changes to the test framework, we run the tests on Hydra, to avoid disrupting the larger NixOS project.
+
+For this, we use the `python-test-refactoring` branch in the `NixOS/nixpkgs` repository, and its [corresponding Hydra jobset](https://hydra.nixos.org/jobset/nixos/python-test-refactoring).
+This branch is used as a pointer, and not as a feature branch.
+
+1. Rebase the PR onto a recent, good evaluation of `nixos-unstable`
+2. Create a baseline evaluation by force-pushing this revision of `nixos-unstable` to `python-test-refactoring`.
+3. Note the evaluation number (we'll call it `<previous>`)
+4. Push the PR to `python-test-refactoring` and evaluate the PR on Hydra
+5. Create a comparison URL by navigating to the latest build of the PR and adding to the URL `?compare=<previous>`. This is not necessary for the evaluation that comes right after the baseline.
+
+Review the removed tests and newly failed tests using the constructed URL; otherwise you will accidentally compare iterations of the PR instead of changes to the PR base.
+
+As we currently have some flaky tests, newly failing tests are expected, but should be reviewed to make sure that
+ - The number of failures did not increase significantly.
+ - All failures that do occur can reasonably be assumed to fail for a different reason than the changes.
diff --git a/nixpkgs/nixos/doc/manual/development/development.md b/nixpkgs/nixos/doc/manual/development/development.md
new file mode 100644
index 000000000000..76f405c3b29c
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/development/development.md
@@ -0,0 +1,15 @@
+# Development {#ch-development}
+
+This chapter describes how you can modify and extend NixOS.
+
+```{=include=} chapters
+sources.chapter.md
+writing-modules.chapter.md
+building-parts.chapter.md
+bootspec.chapter.md
+what-happens-during-a-system-switch.chapter.md
+writing-documentation.chapter.md
+nixos-tests.chapter.md
+developing-the-test-driver.chapter.md
+testing-installer.chapter.md
+```
diff --git a/nixpkgs/nixos/doc/manual/development/development.xml b/nixpkgs/nixos/doc/manual/development/development.xml
deleted file mode 100644
index 624ee3931659..000000000000
--- a/nixpkgs/nixos/doc/manual/development/development.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<part   xmlns="http://docbook.org/ns/docbook"
-        xmlns:xlink="http://www.w3.org/1999/xlink"
-        xmlns:xi="http://www.w3.org/2001/XInclude"
-        version="5.0"
-        xml:id="ch-development">
- <title>Development</title>
- <partintro xml:id="ch-development-intro">
-  <para>
-   This chapter describes how you can modify and extend NixOS.
-  </para>
- </partintro>
- <xi:include href="../from_md/development/sources.chapter.xml" />
- <xi:include href="../from_md/development/writing-modules.chapter.xml" />
- <xi:include href="../from_md/development/building-parts.chapter.xml" />
- <xi:include href="../from_md/development/what-happens-during-a-system-switch.chapter.xml" />
- <xi:include href="../from_md/development/writing-documentation.chapter.xml" />
- <xi:include href="../from_md/development/nixos-tests.chapter.xml" />
- <xi:include href="../from_md/development/testing-installer.chapter.xml" />
-</part>
diff --git a/nixpkgs/nixos/doc/manual/development/freeform-modules.section.md b/nixpkgs/nixos/doc/manual/development/freeform-modules.section.md
index 10e876b96d59..4f344dd80460 100644
--- a/nixpkgs/nixos/doc/manual/development/freeform-modules.section.md
+++ b/nixpkgs/nixos/doc/manual/development/freeform-modules.section.md
@@ -13,9 +13,8 @@ checking for entire option trees, it is only recommended for use in
 submodules.
 
 ::: {#ex-freeform-module .example}
-::: {.title}
-**Example: Freeform submodule**
-:::
+### Freeform submodule
+
 The following shows a submodule assigning a freeform type that allows
 arbitrary attributes with `str` values below `settings`, but also
 declares an option for the `settings.port` attribute to have it
diff --git a/nixpkgs/nixos/doc/manual/development/meta-attributes.section.md b/nixpkgs/nixos/doc/manual/development/meta-attributes.section.md
index 946c08efd0a3..33b41fe74d29 100644
--- a/nixpkgs/nixos/doc/manual/development/meta-attributes.section.md
+++ b/nixpkgs/nixos/doc/manual/development/meta-attributes.section.md
@@ -23,7 +23,7 @@ file.
 
   meta = {
     maintainers = with lib.maintainers; [ ericsagnes ];
-    doc = ./default.xml;
+    doc = ./default.md;
     buildDocsInSandbox = true;
   };
 }
@@ -31,7 +31,9 @@ file.
 
 -   `maintainers` contains a list of the module maintainers.
 
--   `doc` points to a valid DocBook file containing the module
+-   `doc` points to a valid [Nixpkgs-flavored CommonMark](
+      https://nixos.org/manual/nixpkgs/unstable/#sec-contributing-markup
+    ) file containing the module
     documentation. Its contents is automatically added to
     [](#ch-configuration). Changes to a module documentation have to
     be checked to not break building the NixOS manual:
diff --git a/nixpkgs/nixos/doc/manual/development/nixos-tests.chapter.md b/nixpkgs/nixos/doc/manual/development/nixos-tests.chapter.md
index 2a4fdddeaa66..ec0e4b9f076a 100644
--- a/nixpkgs/nixos/doc/manual/development/nixos-tests.chapter.md
+++ b/nixpkgs/nixos/doc/manual/development/nixos-tests.chapter.md
@@ -5,9 +5,9 @@ NixOS tests are kept in the directory `nixos/tests`, and are executed
 (using Nix) by a testing framework that automatically starts one or more
 virtual machines containing the NixOS system(s) required for the test.
 
-```{=docbook}
-<xi:include href="writing-nixos-tests.section.xml" />
-<xi:include href="running-nixos-tests.section.xml" />
-<xi:include href="running-nixos-tests-interactively.section.xml" />
-<xi:include href="linking-nixos-tests-to-packages.section.xml" />
+```{=include=} sections
+writing-nixos-tests.section.md
+running-nixos-tests.section.md
+running-nixos-tests-interactively.section.md
+linking-nixos-tests-to-packages.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/development/option-declarations.section.md b/nixpkgs/nixos/doc/manual/development/option-declarations.section.md
index 8bf73a66456b..3448b07722b8 100644
--- a/nixpkgs/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixpkgs/nixos/doc/manual/development/option-declarations.section.md
@@ -11,7 +11,7 @@ options = {
     type = type specification;
     default = default value;
     example = example value;
-    description = "Description for use in the NixOS manual.";
+    description = lib.mdDoc "Description for use in the NixOS manual.";
   };
 };
 ```
@@ -44,26 +44,24 @@ The function `mkOption` accepts the following arguments.
 :   A textual representation of the default value to be rendered verbatim in
     the manual. Useful if the default value is a complex expression or depends
     on other values or packages.
-    Use `lib.literalExpression` for a Nix expression, `lib.literalDocBook` for
-    a plain English description in DocBook format.
+    Use `lib.literalExpression` for a Nix expression, `lib.literalMD` for
+    a plain English description in [Nixpkgs-flavored Markdown](
+    https://nixos.org/nixpkgs/manual/#sec-contributing-markup) format.
 
 `example`
 
 :   An example value that will be shown in the NixOS manual.
-    You can use `lib.literalExpression` and `lib.literalDocBook` in the same way
+    You can use `lib.literalExpression` and `lib.literalMD` in the same way
     as in `defaultText`.
 
 `description`
 
-:   A textual description of the option, in DocBook format, that will be
+:   A textual description of the option, in [Nixpkgs-flavored Markdown](
+    https://nixos.org/nixpkgs/manual/#sec-contributing-markup) format, that will be
     included in the NixOS manual. During the migration process from DocBook
-    to CommonMark the description may also be written in CommonMark, but has
-    to be wrapped in `lib.mdDoc` to differentiate it from DocBook. See
-    the nixpkgs manual for [the list of CommonMark extensions](
-    https://nixos.org/nixpkgs/manual/#sec-contributing-markup)
-    supported by NixOS documentation.
-
-    New documentation should preferably be written as CommonMark.
+    it is necessary to mark descriptions written in CommonMark with `lib.mdDoc`.
+    The description may still be written in DocBook (without any marker), but this
+    is discouraged and will be deprecated in the future.
 
 ## Utility functions for common option patterns {#sec-option-declarations-util}
 
@@ -79,18 +77,20 @@ The option's description is "Whether to enable \<name\>.".
 For example:
 
 ::: {#ex-options-declarations-util-mkEnableOption-magic .example}
+### `mkEnableOption` usage
 ```nix
-lib.mkEnableOption "magic"
+lib.mkEnableOption (lib.mdDoc "magic")
 # is like
 lib.mkOption {
   type = lib.types.bool;
   default = false;
   example = true;
-  description = "Whether to enable magic.";
+  description = lib.mdDoc "Whether to enable magic.";
 }
 ```
+:::
 
-### `mkPackageOption` {#sec-option-declarations-util-mkPackageOption}
+### `mkPackageOption`, `mkPackageOptionMD` {#sec-option-declarations-util-mkPackageOption}
 
 Usage:
 
@@ -102,42 +102,77 @@ Creates an Option attribute set for an option that specifies the package a modul
 
 **Note**: You shouldn’t necessarily make package options for all of your modules. You can always overwrite a specific package throughout nixpkgs by using [nixpkgs overlays](https://nixos.org/manual/nixpkgs/stable/#chap-overlays).
 
-The default package is specified as a list of strings representing its attribute path in nixpkgs. Because of this, you need to pass nixpkgs itself as the first argument.
+The package is specified in the third argument under `default` as a list of strings
+representing its attribute path in nixpkgs (or another package set).
+Because of this, you need to pass nixpkgs itself (or a subset) as the first argument.
+
+The second argument may be either a string or a list of strings.
+It provides the display name of the package in the description of the generated option
+(using only the last element if the passed value is a list)
+and serves as the fallback value for the `default` argument.
+
+To include extra information in the description, pass `extraDescription` to
+append arbitrary text to the generated description.
+You can also pass an `example` value, either a literal string or an attribute path.
+
+The default argument can be omitted if the provided name is
+an attribute of pkgs (if name is a string) or a
+valid attribute path in pkgs (if name is a list).
 
-The second argument is the name of the option, used in the description "The \<name\> package to use.". You can also pass an example value, either a literal string or a package's attribute path.
+If you wish to explicitly provide no default, pass `null` as `default`.
 
-You can omit the default path if the name of the option is also attribute path in nixpkgs.
+During the transition to CommonMark documentation `mkPackageOption` creates an option with a DocBook description attribute, once the transition is completed it will create a CommonMark description instead. `mkPackageOptionMD` always creates an option with a CommonMark description attribute and will be removed some time after the transition is completed.
 
-::: {#ex-options-declarations-util-mkPackageOption .title}
+[]{#ex-options-declarations-util-mkPackageOption}
 Examples:
 
 ::: {#ex-options-declarations-util-mkPackageOption-hello .example}
+### Simple `mkPackageOption` usage
 ```nix
-lib.mkPackageOption pkgs "hello" { }
+lib.mkPackageOptionMD pkgs "hello" { }
 # is like
 lib.mkOption {
   type = lib.types.package;
   default = pkgs.hello;
   defaultText = lib.literalExpression "pkgs.hello";
-  description = "The hello package to use.";
+  description = lib.mdDoc "The hello package to use.";
 }
 ```
+:::
 
 ::: {#ex-options-declarations-util-mkPackageOption-ghc .example}
+### `mkPackageOption` with explicit default and example
 ```nix
-lib.mkPackageOption pkgs "GHC" {
+lib.mkPackageOptionMD pkgs "GHC" {
   default = [ "ghc" ];
-  example = "pkgs.haskell.packages.ghc924.ghc.withPackages (hkgs: [ hkgs.primes ])";
+  example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])";
 }
 # is like
 lib.mkOption {
   type = lib.types.package;
   default = pkgs.ghc;
   defaultText = lib.literalExpression "pkgs.ghc";
-  example = lib.literalExpression "pkgs.haskell.packages.ghc924.ghc.withPackages (hkgs: [ hkgs.primes ])";
-  description = "The GHC package to use.";
+  example = lib.literalExpression "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])";
+  description = lib.mdDoc "The GHC package to use.";
 }
 ```
+:::
+
+::: {#ex-options-declarations-util-mkPackageOption-extraDescription .example}
+### `mkPackageOption` with additional description text
+```nix
+mkPackageOption pkgs [ "python39Packages" "pytorch" ] {
+  extraDescription = "This is an example and doesn't actually do anything.";
+}
+# is like
+lib.mkOption {
+  type = lib.types.package;
+  default = pkgs.python39Packages.pytorch;
+  defaultText = lib.literalExpression "pkgs.python39Packages.pytorch";
+  description = "The pytorch package to use. This is an example and doesn't actually do anything.";
+}
+```
+:::
 
 ## Extensible Option Types {#sec-option-declarations-eot}
 
@@ -151,7 +186,7 @@ multiple modules, or as an alternative to related `enable` options.
 
 As an example, we will take the case of display managers. There is a
 central display manager module for generic display manager options and a
-module file per display manager backend (sddm, gdm \...).
+module file per display manager backend (sddm, gdm ...).
 
 There are two approaches we could take with this module structure:
 
@@ -186,9 +221,7 @@ changing the main service module file and the type system automatically
 enforces that there can only be a single display manager enabled.
 
 ::: {#ex-option-declaration-eot-service .example}
-::: {.title}
-**Example: Extensible type placeholder in the service module**
-:::
+### Extensible type placeholder in the service module
 ```nix
 services.xserver.displayManager.enable = mkOption {
   description = "Display manager to use";
@@ -198,9 +231,7 @@ services.xserver.displayManager.enable = mkOption {
 :::
 
 ::: {#ex-option-declaration-eot-backend-gdm .example}
-::: {.title}
-**Example: Extending `services.xserver.displayManager.enable` in the `gdm` module**
-:::
+### Extending `services.xserver.displayManager.enable` in the `gdm` module
 ```nix
 services.xserver.displayManager.enable = mkOption {
   type = with types; nullOr (enum [ "gdm" ]);
@@ -209,9 +240,7 @@ services.xserver.displayManager.enable = mkOption {
 :::
 
 ::: {#ex-option-declaration-eot-backend-sddm .example}
-::: {.title}
-**Example: Extending `services.xserver.displayManager.enable` in the `sddm` module**
-:::
+### Extending `services.xserver.displayManager.enable` in the `sddm` module
 ```nix
 services.xserver.displayManager.enable = mkOption {
   type = with types; nullOr (enum [ "sddm" ]);
diff --git a/nixpkgs/nixos/doc/manual/development/option-def.section.md b/nixpkgs/nixos/doc/manual/development/option-def.section.md
index 91b24cd4a3a1..6a3dc26b99be 100644
--- a/nixpkgs/nixos/doc/manual/development/option-def.section.md
+++ b/nixpkgs/nixos/doc/manual/development/option-def.section.md
@@ -12,7 +12,7 @@ config = {
 However, sometimes you need to wrap an option definition or set of
 option definitions in a *property* to achieve certain effects:
 
-## Delaying Conditionals {#sec-option-definitions-delaying-conditionals .unnumbered}
+## Delaying Conditionals {#sec-option-definitions-delaying-conditionals}
 
 If a set of option definitions is conditional on the value of another
 option, you may need to use `mkIf`. Consider, for instance:
@@ -56,22 +56,40 @@ config = {
 };
 ```
 
-## Setting Priorities {#sec-option-definitions-setting-priorities .unnumbered}
+## Setting Priorities {#sec-option-definitions-setting-priorities}
 
 A module can override the definitions of an option in other modules by
-setting a *priority*. All option definitions that do not have the lowest
+setting an *override priority*. All option definitions that do not have the lowest
 priority value are discarded. By default, option definitions have
-priority 1000. You can specify an explicit priority by using
-`mkOverride`, e.g.
+priority 100 and option defaults have priority 1500.
+You can specify an explicit priority by using `mkOverride`, e.g.
 
 ```nix
 services.openssh.enable = mkOverride 10 false;
 ```
 
 This definition causes all other definitions with priorities above 10 to
-be discarded. The function `mkForce` is equal to `mkOverride 50`.
+be discarded. The function `mkForce` is equal to `mkOverride 50`, and
+`mkDefault` is equal to `mkOverride 1000`.
 
-## Merging Configurations {#sec-option-definitions-merging .unnumbered}
+## Ordering Definitions {#sec-option-definitions-ordering}
+
+It is also possible to influence the order in which the definitions for an option are
+merged by setting an *order priority* with `mkOrder`. The default order priority is 1000.
+The functions `mkBefore` and `mkAfter` are equal to `mkOrder 500` and `mkOrder 1500`, respectively.
+As an example,
+
+```nix
+hardware.firmware = mkBefore [ myFirmware ];
+```
+
+This definition ensures that `myFirmware` comes before other unordered
+definitions in the final list value of `hardware.firmware`.
+
+Note that this is different from [override priorities](#sec-option-definitions-setting-priorities):
+setting an order does not affect whether the definition is included or not.
+
+## Merging Configurations {#sec-option-definitions-merging}
 
 In conjunction with `mkIf`, it is sometimes useful for a module to
 return multiple sets of option definitions, to be merged together as if
diff --git a/nixpkgs/nixos/doc/manual/development/option-types.section.md b/nixpkgs/nixos/doc/manual/development/option-types.section.md
index 9b35e6630144..9e156ebff9d3 100644
--- a/nixpkgs/nixos/doc/manual/development/option-types.section.md
+++ b/nixpkgs/nixos/doc/manual/development/option-types.section.md
@@ -4,7 +4,7 @@ Option types are a way to put constraints on the values a module option
 can take. Types are also responsible of how values are merged in case of
 multiple value definitions.
 
-## Basic Types {#sec-option-types-basic}
+## Basic types {#sec-option-types-basic}
 
 Basic types are the simplest available types in the module system. Basic
 types include multiple string types that mainly differ in how definition
@@ -25,15 +25,19 @@ merging is handled.
 :   A top-level store path. This can be an attribute set pointing
     to a store path, like a derivation or a flake input.
 
+`types.enum` *`l`*
+
+:   One element of the list *`l`*, e.g. `types.enum [ "left" "right" ]`.
+    Multiple definitions cannot be merged.
+
 `types.anything`
 
 :   A type that accepts any value and recursively merges attribute sets
     together. This type is recommended when the option type is unknown.
 
     ::: {#ex-types-anything .example}
-    ::: {.title}
-    **Example: `types.anything` Example**
-    :::
+    ### `types.anything`
+
     Two definitions of this type like
 
     ```nix
@@ -87,15 +91,19 @@ merging is handled.
 :   A free-form attribute set.
 
     ::: {.warning}
-    This type will be deprecated in the future because it doesn\'t
+    This type will be deprecated in the future because it doesn't
     recurse into attribute sets, silently drops earlier attribute
-    definitions, and doesn\'t discharge `lib.mkDefault`, `lib.mkIf`
+    definitions, and doesn't discharge `lib.mkDefault`, `lib.mkIf`
     and co. For allowing arbitrary attribute sets, prefer
-    `types.attrsOf types.anything` instead which doesn\'t have these
+    `types.attrsOf types.anything` instead which doesn't have these
     problems.
     :::
 
-Integer-related types:
+`types.pkgs`
+
+:   A type for the top level Nixpkgs package set.
+
+### Numeric types {#sec-option-types-numeric}
 
 `types.int`
 
@@ -118,6 +126,10 @@ Integer-related types:
     from 0 to 2^n−1 respectively (e.g. `0`
     to `255` for 8 bits).
 
+`types.ints.between` *`lowest highest`*
+
+:   An integer between *`lowest`* and *`highest`* (both inclusive).
+
 `types.ints.positive`
 
 :   A positive integer (that is > 0).
@@ -127,12 +139,44 @@ Integer-related types:
 :   A port number. This type is an alias to
     `types.ints.u16`.
 
-String-related types:
+`types.float`
+
+:   A floating point number.
+
+    ::: {.warning}
+    Converting a floating point number to a string with `toString` or `toJSON`
+    may result in [precision loss](https://github.com/NixOS/nix/issues/5733).
+    :::
+
+`types.number`
+
+:   Either a signed integer or a floating point number. No implicit conversion
+    is done between the two types, and multiple equal definitions will only be
+    merged if they have the same type.
+
+`types.numbers.between` *`lowest highest`*
+
+:   An integer or floating point number between *`lowest`* and *`highest`* (both inclusive).
+
+`types.numbers.nonnegative`
+
+:   A nonnegative integer or floating point number (that is >= 0).
+
+`types.numbers.positive`
+
+:   A positive integer or floating point number (that is > 0).
+
+### String types {#sec-option-types-string}
 
 `types.str`
 
 :   A string. Multiple definitions cannot be merged.
 
+`types.separatedString` *`sep`*
+
+:   A string. Multiple definitions are concatenated with *`sep`*, e.g.
+    `types.separatedString "|"`.
+
 `types.lines`
 
 :   A string. Multiple definitions are concatenated with a new line
@@ -144,7 +188,7 @@ String-related types:
 
 `types.envVar`
 
-:   A string. Multiple definitions are concatenated with a collon `":"`.
+:   A string. Multiple definitions are concatenated with a colon `":"`.
 
 `types.strMatching`
 
@@ -152,24 +196,9 @@ String-related types:
     definitions cannot be merged. The regular expression is processed
     using `builtins.match`.
 
-## Value Types {#sec-option-types-value}
+## Submodule types {#sec-option-types-submodule}
 
-Value types are types that take a value parameter.
-
-`types.enum` *`l`*
-
-:   One element of the list *`l`*, e.g. `types.enum [ "left" "right" ]`.
-    Multiple definitions cannot be merged.
-
-`types.separatedString` *`sep`*
-
-:   A string with a custom separator *`sep`*, e.g.
-    `types.separatedString "|"`.
-
-`types.ints.between` *`lowest highest`*
-
-:   An integer between *`lowest`* and *`highest`* (both inclusive). Useful
-    for creating types like `types.port`.
+Submodules are detailed in [Submodule](#section-option-types-submodule).
 
 `types.submodule` *`o`*
 
@@ -178,7 +207,6 @@ Value types are types that take a value parameter.
     value. Submodules are used in composed types to create modular
     options. This is equivalent to
     `types.submoduleWith { modules = toList o; shorthandOnlyDefinesConfig = true; }`.
-    Submodules are detailed in [Submodule](#section-option-types-submodule).
 
 `types.submoduleWith` { *`modules`*, *`specialArgs`* ? {}, *`shorthandOnlyDefinesConfig`* ? false }
 
@@ -197,7 +225,7 @@ Value types are types that take a value parameter.
     -   *`specialArgs`* An attribute set of extra arguments to be passed
         to the module functions. The option `_module.args` should be
         used instead for most arguments since it allows overriding.
-        *`specialArgs`* should only be used for arguments that can\'t go
+        *`specialArgs`* should only be used for arguments that can't go
         through the module fixed-point, because of infinite recursion or
         other problems. An example is overriding the `lib` argument,
         because `lib` itself is used to define `_module.args`, which
@@ -211,7 +239,7 @@ Value types are types that take a value parameter.
         In such a case it would allow the option to be set with
         `the-submodule.config = "value"` instead of requiring
         `the-submodule.config.config = "value"`. This is because
-        only when modules *don\'t* set the `config` or `options`
+        only when modules *don't* set the `config` or `options`
         keys, all keys are interpreted as option definitions in the
         `config` section. Enabling this option implicitly puts all
         attributes in the `config` section.
@@ -239,7 +267,7 @@ Value types are types that take a value parameter.
     more convenient and discoverable than expecting the module user to
     type-merge with the `attrsOf submodule` option.
 
-## Composed Types {#sec-option-types-composed}
+## Composed types {#sec-option-types-composed}
 
 Composed types are types that take a type as parameter. `listOf
    int` and `either int str` are examples of composed types.
@@ -299,7 +327,7 @@ Composed types are types that take a type as parameter. `listOf
 :   Type *`t1`* or type *`t2`*, e.g. `with types; either int str`.
     Multiple definitions cannot be merged.
 
-`types.oneOf` \[ *`t1 t2`* \... \]
+`types.oneOf` \[ *`t1 t2`* ... \]
 
 :   Type *`t1`* or type *`t2`* and so forth, e.g.
     `with types; oneOf [ int str bool ]`. Multiple definitions cannot be
@@ -320,7 +348,7 @@ that are handled like a separate module.
 It takes a parameter *`o`*, that should be a set, or a function returning
 a set with an `options` key defining the sub-options. Submodule option
 definitions are type-checked accordingly to the `options` declarations.
-Of course, you can nest submodule option definitons for even higher
+Of course, you can nest submodule option definitions for even higher
 modularity.
 
 The option set can be defined directly
@@ -332,9 +360,7 @@ you will still need to provide a default value (e.g. an empty attribute set)
 if you want to allow users to leave it undefined.
 
 ::: {#ex-submodule-direct .example}
-::: {.title}
-**Example: Directly defined submodule**
-:::
+### Directly defined submodule
 ```nix
 options.mod = mkOption {
   description = "submodule example";
@@ -353,9 +379,7 @@ options.mod = mkOption {
 :::
 
 ::: {#ex-submodule-reference .example}
-::: {.title}
-**Example: Submodule defined as a reference**
-:::
+### Submodule defined as a reference
 ```nix
 let
   modOptions = {
@@ -383,9 +407,7 @@ multiple definitions of the submodule option set
 ([Example: Definition of a list of submodules](#ex-submodule-listof-definition)).
 
 ::: {#ex-submodule-listof-declaration .example}
-::: {.title}
-**Example: Declaration of a list of submodules**
-:::
+### Declaration of a list of submodules
 ```nix
 options.mod = mkOption {
   description = "submodule example";
@@ -404,9 +426,7 @@ options.mod = mkOption {
 :::
 
 ::: {#ex-submodule-listof-definition .example}
-::: {.title}
-**Example: Definition of a list of submodules**
-:::
+### Definition of a list of submodules
 ```nix
 config.mod = [
   { foo = 1; bar = "one"; }
@@ -421,9 +441,7 @@ multiple named definitions of the submodule option set
 ([Example: Definition of attribute sets of submodules](#ex-submodule-attrsof-definition)).
 
 ::: {#ex-submodule-attrsof-declaration .example}
-::: {.title}
-**Example: Declaration of attribute sets of submodules**
-:::
+### Declaration of attribute sets of submodules
 ```nix
 options.mod = mkOption {
   description = "submodule example";
@@ -442,9 +460,7 @@ options.mod = mkOption {
 :::
 
 ::: {#ex-submodule-attrsof-definition .example}
-::: {.title}
-**Example: Definition of attribute sets of submodules**
-:::
+### Definition of attribute sets of submodules
 ```nix
 config.mod.one = { foo = 1; bar = "one"; };
 config.mod.two = { foo = 2; bar = "two"; };
@@ -464,9 +480,8 @@ Types are mainly characterized by their `check` and `merge` functions.
     ([Example: Overriding a type check](#ex-extending-type-check-2)).
 
     ::: {#ex-extending-type-check-1 .example}
-    ::: {.title}
-    **Example: Adding a type check**
-    :::
+    ### Adding a type check
+
     ```nix
     byte = mkOption {
       description = "An integer between 0 and 255.";
@@ -476,9 +491,8 @@ Types are mainly characterized by their `check` and `merge` functions.
     :::
 
     ::: {#ex-extending-type-check-2 .example}
-    ::: {.title}
-    **Example: Overriding a type check**
-    :::
+    ### Overriding a type check
+
     ```nix
     nixThings = mkOption {
       description = "words that start with 'nix'";
@@ -496,7 +510,7 @@ Types are mainly characterized by their `check` and `merge` functions.
     of strings, and `defs` the list of defined values as a list. It is
     possible to override a type merge function for custom needs.
 
-## Custom Types {#sec-option-types-custom}
+## Custom types {#sec-option-types-custom}
 
 Custom types can be created with the `mkOptionType` function. As type
 creation includes some more complex topics such as submodule handling,
diff --git a/nixpkgs/nixos/doc/manual/development/replace-modules.section.md b/nixpkgs/nixos/doc/manual/development/replace-modules.section.md
index 0700a82004c1..ac9f5adbaf98 100644
--- a/nixpkgs/nixos/doc/manual/development/replace-modules.section.md
+++ b/nixpkgs/nixos/doc/manual/development/replace-modules.section.md
@@ -2,19 +2,26 @@
 
 Modules that are imported can also be disabled. The option declarations,
 config implementation and the imports of a disabled module will be
-ignored, allowing another to take it\'s place. This can be used to
+ignored, allowing another to take its place. This can be used to
 import a set of modules from another channel while keeping the rest of
 the system on a stable release.
 
 `disabledModules` is a top level attribute like `imports`, `options` and
 `config`. It contains a list of modules that will be disabled. This can
-either be the full path to the module or a string with the filename
-relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos).
+either be:
+ - the full path to the module,
+ - or a string with the filename relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos),
+ - or an attribute set containing a specific `key` attribute.
+
+The latter allows some modules to be disabled, despite them being distributed
+via attributes instead of file paths. The `key` should be globally unique, so
+it is recommended to include a file path in it, or rely on a framework to do it
+for you.
 
 This example will replace the existing postgresql module with the
 version defined in the nixos-unstable channel while keeping the rest of
 the modules and packages from the original nixos channel. This only
-overrides the module definition, this won\'t use postgresql from
+overrides the module definition, this won't use postgresql from
 nixos-unstable unless explicitly configured to do so.
 
 ```nix
@@ -35,7 +42,7 @@ nixos-unstable unless explicitly configured to do so.
 
 This example shows how to define a custom module as a replacement for an
 existing module. Importing this module will disable the original module
-without having to know it\'s implementation details.
+without having to know its implementation details.
 
 ```nix
 { config, lib, pkgs, ... }:
diff --git a/nixpkgs/nixos/doc/manual/development/running-nixos-tests-interactively.section.md b/nixpkgs/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
index a1431859ff59..54002941d634 100644
--- a/nixpkgs/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
+++ b/nixpkgs/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
@@ -24,6 +24,41 @@ back into the test driver command line upon its completion. This allows
 you to inspect the state of the VMs after the test (e.g. to debug the
 test script).
 
+## Shell access in interactive mode {#sec-nixos-test-shell-access}
+
+The function `<yourmachine>.shell_interact()` grants access to a shell running
+inside a virtual machine. To use it, replace `<yourmachine>` with the name of a
+virtual machine defined in the test, for example: `machine.shell_interact()`.
+Keep in mind that this shell may not display everything correctly as it is
+running within an interactive Python REPL, and logging output from the virtual
+machine may overwrite input and output from the guest shell:
+
+```py
+>>> machine.shell_interact()
+machine: Terminal is ready (there is no initial prompt):
+$ hostname
+machine
+```
+
+As an alternative, you can proxy the guest shell to a local TCP server by first
+starting a TCP server in a terminal using the command:
+
+```ShellSession
+$ socat 'READLINE,PROMPT=$ ' tcp-listen:4444,reuseaddr`
+```
+
+In the terminal where the test driver is running, connect to this server by
+using:
+
+```py
+>>> machine.shell_interact("tcp:127.0.0.1:4444")
+```
+
+Once the connection is established, you can enter commands in the socat terminal
+where socat is running.
+
+## Reuse VM state {#sec-nixos-test-reuse-vm-state}
+
 You can re-use the VM states coming from a previous run by setting the
 `--keep-vm-state` flag.
 
@@ -33,3 +68,15 @@ $ ./result/bin/nixos-test-driver --keep-vm-state
 
 The machine state is stored in the `$TMPDIR/vm-state-machinename`
 directory.
+
+## Interactive-only test configuration {#sec-nixos-test-interactive-configuration}
+
+The `.driverInteractive` attribute combines the regular test configuration with
+definitions from the [`interactive` submodule](#test-opt-interactive). This gives you
+a more usable, graphical, but slightly different configuration.
+
+You can add your own interactive-only test configuration by adding extra
+configuration to the [`interactive` submodule](#test-opt-interactive).
+
+To interactively run only the regular configuration, build the `<test>.driver` attribute
+instead, and call it with the flag `result/bin/nixos-test-driver --interactive`.
diff --git a/nixpkgs/nixos/doc/manual/development/running-nixos-tests.section.md b/nixpkgs/nixos/doc/manual/development/running-nixos-tests.section.md
index 1bec023b613a..33076f5dc2a7 100644
--- a/nixpkgs/nixos/doc/manual/development/running-nixos-tests.section.md
+++ b/nixpkgs/nixos/doc/manual/development/running-nixos-tests.section.md
@@ -2,22 +2,11 @@
 
 You can run tests using `nix-build`. For example, to run the test
 [`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix),
-you just do:
+you do:
 
 ```ShellSession
-$ nix-build '<nixpkgs/nixos/tests/login.nix>'
-```
-
-or, if you don't want to rely on `NIX_PATH`:
-
-```ShellSession
-$ cd /my/nixpkgs/nixos/tests
-$ nix-build login.nix
-…
-running the VM test script
-machine: QEMU running (pid 8841)
-…
-6 out of 6 tests succeeded
+$ cd /my/git/clone/of/nixpkgs
+$ nix-build -A nixosTests.login
 ```
 
 After building/downloading all required dependencies, this will perform
diff --git a/nixpkgs/nixos/doc/manual/development/settings-options.section.md b/nixpkgs/nixos/doc/manual/development/settings-options.section.md
index d569e23adbdc..5060dd98f58f 100644
--- a/nixpkgs/nixos/doc/manual/development/settings-options.section.md
+++ b/nixpkgs/nixos/doc/manual/development/settings-options.section.md
@@ -9,10 +9,10 @@ can be declared. File formats can be separated into two categories:
     `{ foo = { bar = 10; }; }`. Other examples are INI, YAML and TOML.
     The following section explains the convention for these settings.
 
--   Non-nix-representable ones: These can\'t be trivially mapped to a
+-   Non-nix-representable ones: These can't be trivially mapped to a
     subset of Nix syntax. Most generic programming languages are in this
     group, e.g. bash, since the statement `if true; then echo hi; fi`
-    doesn\'t have a trivial representation in Nix.
+    doesn't have a trivial representation in Nix.
 
     Currently there are no fixed conventions for these, but it is common
     to have a `configFile` option for setting the configuration file
@@ -24,7 +24,7 @@ can be declared. File formats can be separated into two categories:
     an `extraConfig` option of type `lines` to allow arbitrary text
     after the autogenerated part of the file.
 
-## Nix-representable Formats (JSON, YAML, TOML, INI, \...) {#sec-settings-nix-representable}
+## Nix-representable Formats (JSON, YAML, TOML, INI, ...) {#sec-settings-nix-representable}
 
 By convention, formats like this are handled with a generic `settings`
 option, representing the full program configuration as a Nix value. The
@@ -119,9 +119,8 @@ have a predefined type and string generator already declared under
         default Elixir keyword list
 
 
-::: {#pkgs-formats-result}
+[]{#pkgs-formats-result}
 These functions all return an attribute set with these values:
-:::
 
 `type`
 
@@ -144,9 +143,8 @@ These functions all return an attribute set with these values:
     :::
 
 ::: {#ex-settings-nix-representable .example}
-::: {.title}
-**Example: Module with conventional `settings` option**
-:::
+### Module with conventional `settings` option
+
 The following shows a module for an example program that uses a JSON
 configuration file. It demonstrates how above values can be used, along
 with some other related best practices. See the comments for
@@ -220,9 +218,7 @@ the port, which will enforce it to be a valid integer and make it show
 up in the manual.
 
 ::: {#ex-settings-typed-attrs .example}
-::: {.title}
-**Example: Declaring a type-checked `settings` attribute**
-:::
+### Declaring a type-checked `settings` attribute
 ```nix
 settings = lib.mkOption {
   type = lib.types.submodule {
diff --git a/nixpkgs/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md b/nixpkgs/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
index aad82831a3c2..9cbec729803a 100644
--- a/nixpkgs/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
+++ b/nixpkgs/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
@@ -47,7 +47,7 @@ Most of these actions are either self-explaining but some of them have to do
 with our units or the activation script. For this reason, these topics are
 explained in the next sections.
 
-```{=docbook}
-<xi:include href="unit-handling.section.xml" />
-<xi:include href="activation-script.section.xml" />
+```{=include=} sections
+unit-handling.section.md
+activation-script.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/development/writing-documentation.chapter.md b/nixpkgs/nixos/doc/manual/development/writing-documentation.chapter.md
index 7c29f600d701..8d504dfb0b0a 100644
--- a/nixpkgs/nixos/doc/manual/development/writing-documentation.chapter.md
+++ b/nixpkgs/nixos/doc/manual/development/writing-documentation.chapter.md
@@ -19,7 +19,7 @@ $ nix-shell
 nix-shell$ make
 ```
 
-Once you are done making modifications to the manual, it\'s important to
+Once you are done making modifications to the manual, it's important to
 build it before committing. You can do that as follows:
 
 ```ShellSession
@@ -83,7 +83,7 @@ Keep the following guidelines in mind when you create and add a topic:
 
 ## Adding a Topic to the Book {#sec-writing-docs-adding-a-topic}
 
-Open the parent XML file and add an `xi:include` element to the list of
+Open the parent CommonMark file and add a line to the list of
 chapters with the file name of the topic that you created. If you
 created a `section`, you add the file to the `chapter` file. If you created
 a `chapter`, you add the file to the `part` file.
diff --git a/nixpkgs/nixos/doc/manual/development/writing-modules.chapter.md b/nixpkgs/nixos/doc/manual/development/writing-modules.chapter.md
index 0c41cbd3cb75..e07b899e6df7 100644
--- a/nixpkgs/nixos/doc/manual/development/writing-modules.chapter.md
+++ b/nixpkgs/nixos/doc/manual/development/writing-modules.chapter.md
@@ -37,9 +37,7 @@ options, but does not declare any. The structure of full NixOS modules
 is shown in [Example: Structure of NixOS Modules](#ex-module-syntax).
 
 ::: {#ex-module-syntax .example}
-::: {.title}
-**Example: Structure of NixOS Modules**
-:::
+### Structure of NixOS Modules
 ```nix
 { config, pkgs, ... }:
 
@@ -71,7 +69,7 @@ The meaning of each part is as follows.
 -   This `imports` list enumerates the paths to other NixOS modules that
     should be included in the evaluation of the system configuration. A
     default set of modules is defined in the file `modules/module-list.nix`.
-    These don\'t need to be added in the import list.
+    These don't need to be added in the import list.
 
 -   The attribute `options` is a nested set of *option declarations*
     (described below).
@@ -102,9 +100,7 @@ Exec directives](#exec-escaping-example) for an example. When using these
 functions system environment substitution should *not* be disabled explicitly.
 
 ::: {#locate-example .example}
-::: {.title}
-**Example: NixOS Module for the "locate" Service**
-:::
+### NixOS Module for the "locate" Service
 ```nix
 { config, lib, pkgs, ... }:
 
@@ -165,9 +161,7 @@ in {
 :::
 
 ::: {#exec-escaping-example .example}
-::: {.title}
-**Example: Escaping in Exec directives**
-:::
+### Escaping in Exec directives
 ```nix
 { config, lib, pkgs, utils, ... }:
 
@@ -195,14 +189,14 @@ in {
 ```
 :::
 
-```{=docbook}
-<xi:include href="option-declarations.section.xml" />
-<xi:include href="option-types.section.xml" />
-<xi:include href="option-def.section.xml" />
-<xi:include href="assertions.section.xml" />
-<xi:include href="meta-attributes.section.xml" />
-<xi:include href="importing-modules.section.xml" />
-<xi:include href="replace-modules.section.xml" />
-<xi:include href="freeform-modules.section.xml" />
-<xi:include href="settings-options.section.xml" />
+```{=include=} sections
+option-declarations.section.md
+option-types.section.md
+option-def.section.md
+assertions.section.md
+meta-attributes.section.md
+importing-modules.section.md
+replace-modules.section.md
+freeform-modules.section.md
+settings-options.section.md
 ```
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 6934bb0face7..486a4b64a262 100644
--- a/nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -1,9 +1,9 @@
 # Writing Tests {#sec-writing-nixos-tests}
 
-A NixOS test is a Nix expression that has the following structure:
+A NixOS test is a module that has the following structure:
 
 ```nix
-import ./make-test-python.nix {
+{
 
   # One or more machines:
   nodes =
@@ -21,10 +21,13 @@ import ./make-test-python.nix {
 }
 ```
 
-The attribute `testScript` is a bit of Python code that executes the
+We refer to the whole test above as a test module, whereas the values
+in [`nodes.<name>`](#test-opt-nodes) are NixOS modules themselves.
+
+The option [`testScript`](#test-opt-testScript) is a piece of Python code that executes the
 test (described below). During the test, it will start one or more
 virtual machines, the configuration of which is described by
-the attribute `nodes`.
+the option [`nodes`](#test-opt-nodes).
 
 An example of a single-node test is
 [`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix).
@@ -34,7 +37,54 @@ when switching between consoles, and so on. An interesting multi-node test is
 [`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix).
 It uses two client nodes to test correct locking across server crashes.
 
-There are a few special NixOS configuration options for test VMs:
+## Calling a test {#sec-calling-nixos-tests}
+
+Tests are invoked differently depending on whether the test is part of NixOS or lives in a different project.
+
+### Testing within NixOS {#sec-call-nixos-test-in-nixos}
+
+Tests that are part of NixOS are added to [`nixos/tests/all-tests.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/all-tests.nix).
+
+```nix
+  hostname = runTest ./hostname.nix;
+```
+
+Overrides can be added by defining an anonymous module in `all-tests.nix`.
+
+```nix
+  hostname = runTest {
+    imports = [ ./hostname.nix ];
+    defaults.networking.firewall.enable = false;
+  };
+```
+
+You can run a test with attribute name `hostname` in `nixos/tests/all-tests.nix` by invoking:
+
+```shell
+cd /my/git/clone/of/nixpkgs
+nix-build -A nixosTests.hostname
+```
+
+### Testing outside the NixOS project {#sec-call-nixos-test-outside-nixos}
+
+Outside the `nixpkgs` repository, you can instantiate the test by first importing the NixOS library,
+
+```nix
+let nixos-lib = import (nixpkgs + "/nixos/lib") { };
+in
+
+nixos-lib.runTest {
+  imports = [ ./test.nix ];
+  hostPkgs = pkgs;  # the Nixpkgs package set used outside the VMs
+  defaults.services.foo.package = mypkg;
+}
+```
+
+`runTest` returns a derivation that runs the test.
+
+## Configuring the nodes {#sec-nixos-test-nodes}
+
+There are a few special NixOS options for test VMs:
 
 `virtualisation.memorySize`
 
@@ -80,6 +130,11 @@ starting them in parallel:
 start_all()
 ```
 
+If the hostname of a node contains characters that can't be used in a
+Python variable name, those characters will be replaced with
+underscores in the variable name, so `nodes.machine-a` will be exposed
+to Python as `machine_a`.
+
 ## Machine objects {#ssec-machine-objects}
 
 The following methods are available on machine objects:
@@ -115,22 +170,22 @@ The following methods are available on machine objects:
 `get_screen_text_variants`
 
 :   Return a list of different interpretations of what is currently
-    visible on the machine\'s screen using optical character
+    visible on the machine's screen using optical character
     recognition. The number and order of the interpretations is not
     specified and is subject to change, but if no exception is raised at
     least one will be returned.
 
     ::: {.note}
-    This requires passing `enableOCR` to the test attribute set.
+    This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
     :::
 
 `get_screen_text`
 
 :   Return a textual representation of what is currently visible on the
-    machine\'s screen using optical character recognition.
+    machine's screen using optical character recognition.
 
     ::: {.note}
-    This requires passing `enableOCR` to the test attribute set.
+    This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
     :::
 
 `send_monitor_command`
@@ -223,12 +278,13 @@ The following methods are available on machine objects:
 
 `wait_for_open_port`
 
-:   Wait until a process is listening on the given TCP port (on
-    `localhost`, at least).
+:   Wait until a process is listening on the given TCP port and IP address
+    (default `localhost`).
 
 `wait_for_closed_port`
 
-:   Wait until nobody is listening on the given TCP port.
+:   Wait until nobody is listening on the given TCP port and IP address
+    (default `localhost`).
 
 `wait_for_x`
 
@@ -241,14 +297,14 @@ The following methods are available on machine objects:
     `get_screen_text` and `get_screen_text_variants`).
 
     ::: {.note}
-    This requires passing `enableOCR` to the test attribute set.
+    This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
     :::
 
 `wait_for_console_text`
 
 :   Wait until the supplied regular expressions match a line of the
     serial console output. This method is useful when OCR is not
-    possibile or accurate enough.
+    possible or accurate enough.
 
 `wait_for_window`
 
@@ -300,11 +356,11 @@ machine.wait_for_unit("xautolock.service", "x-session-user")
 This applies to `systemctl`, `get_unit_info`, `wait_for_unit`,
 `start_job` and `stop_job`.
 
-For faster dev cycles it\'s also possible to disable the code-linters
-(this shouldn\'t be commited though):
+For faster dev cycles it's also possible to disable the code-linters
+(this shouldn't be committed though):
 
 ```nix
-import ./make-test-python.nix {
+{
   skipLint = true;
   nodes.machine =
     { config, pkgs, ... }:
@@ -320,7 +376,7 @@ import ./make-test-python.nix {
 
 This will produce a Nix warning at evaluation time. To fully disable the
 linter, wrap the test script in comment directives to disable the Black
-linter directly (again, don\'t commit this within the Nixpkgs
+linter directly (again, don't commit this within the Nixpkgs
 repository):
 
 ```nix
@@ -336,7 +392,7 @@ Similarly, the type checking of test scripts can be disabled in the following
 way:
 
 ```nix
-import ./make-test-python.nix {
+{
   skipTypeCheck = true;
   nodes.machine =
     { config, pkgs, ... }:
@@ -366,8 +422,7 @@ with foo_running:
 
 `seconds_interval`
 
-:
-    specifies how often the condition should be polled:
+:   specifies how often the condition should be polled:
 
 ```py
 @polling_condition(seconds_interval=10)
@@ -377,8 +432,7 @@ def foo_running():
 
 `description`
 
-:
-    is used in the log when the condition is checked. If this is not provided, the description is pulled from the docstring of the function. These two are therefore equivalent:
+:   is used in the log when the condition is checked. If this is not provided, the description is pulled from the docstring of the function. These two are therefore equivalent:
 
 ```py
 @polling_condition
@@ -400,7 +454,6 @@ added using the parameter `extraPythonPackages`. For example, you could add
 `numpy` like this:
 
 ```nix
-import ./make-test-python.nix
 {
   extraPythonPackages = p: [ p.numpy ];
 
@@ -417,3 +470,13 @@ import ./make-test-python.nix
 ```
 
 In that case, `numpy` is chosen from the generic `python3Packages`.
+
+## Test Options Reference {#sec-test-options-reference}
+
+The following options can be used when writing tests.
+
+```{=include=} options
+id-prefix: test-opt-
+list-id: test-options-list
+source: @NIXOS_TEST_OPTIONS_JSON@
+```
diff --git a/nixpkgs/nixos/doc/manual/from_md/README.md b/nixpkgs/nixos/doc/manual/from_md/README.md
deleted file mode 100644
index cc6d08ca0a15..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-This directory is temporarily needed while we transition the manual to CommonMark. It stores the output of the ../md-to-db.sh script that converts CommonMark files back to DocBook.
-
-We are choosing to convert the Markdown to DocBook at authoring time instead of manual building time, because we do not want the pandoc toolchain to become part of the NixOS closure.
-
-Do not edit the DocBook files inside this directory or its subdirectories. Instead, edit the corresponding .md file in the normal manual directories, and run ../md-to-db.sh to update the file here.
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/boot-problems.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/boot-problems.section.xml
deleted file mode 100644
index 144661c86eba..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/boot-problems.section.xml
+++ /dev/null
@@ -1,144 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-boot-problems">
-  <title>Boot Problems</title>
-  <para>
-    If NixOS fails to boot, there are a number of kernel command line
-    parameters that may help you to identify or fix the issue. You can
-    add these parameters in the GRUB boot menu by pressing “e” to modify
-    the selected boot entry and editing the line starting with
-    <literal>linux</literal>. The following are some useful kernel
-    command line parameters that are recognised by the NixOS boot
-    scripts or by systemd:
-  </para>
-  <variablelist>
-    <varlistentry>
-      <term>
-        <literal>boot.shell_on_fail</literal>
-      </term>
-      <listitem>
-        <para>
-          Allows the user to start a root shell if something goes wrong
-          in stage 1 of the boot process (the initial ramdisk). This is
-          disabled by default because there is no authentication for the
-          root shell.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>boot.debug1</literal>
-      </term>
-      <listitem>
-        <para>
-          Start an interactive shell in stage 1 before anything useful
-          has been done. That is, no modules have been loaded and no
-          file systems have been mounted, except for
-          <literal>/proc</literal> and <literal>/sys</literal>.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>boot.debug1devices</literal>
-      </term>
-      <listitem>
-        <para>
-          Like <literal>boot.debug1</literal>, but runs stage1 until
-          kernel modules are loaded and device nodes are created. This
-          may help with e.g. making the keyboard work.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>boot.debug1mounts</literal>
-      </term>
-      <listitem>
-        <para>
-          Like <literal>boot.debug1</literal> or
-          <literal>boot.debug1devices</literal>, but runs stage1 until
-          all filesystems that are mounted during initrd are mounted
-          (see
-          <link linkend="opt-fileSystems._name_.neededForBoot">neededForBoot</link>).
-          As a motivating example, this could be useful if you’ve
-          forgotten to set
-          <link linkend="opt-fileSystems._name_.neededForBoot">neededForBoot</link>
-          on a file system.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>boot.trace</literal>
-      </term>
-      <listitem>
-        <para>
-          Print every shell command executed by the stage 1 and 2 boot
-          scripts.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>single</literal>
-      </term>
-      <listitem>
-        <para>
-          Boot into rescue mode (a.k.a. single user mode). This will
-          cause systemd to start nothing but the unit
-          <literal>rescue.target</literal>, which runs
-          <literal>sulogin</literal> to prompt for the root password and
-          start a root login shell. Exiting the shell causes the system
-          to continue with the normal boot process.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>systemd.log_level=debug</literal>
-        <literal>systemd.log_target=console</literal>
-      </term>
-      <listitem>
-        <para>
-          Make systemd very verbose and send log messages to the console
-          instead of the journal. For more parameters recognised by
-          systemd, see systemd(1).
-        </para>
-      </listitem>
-    </varlistentry>
-  </variablelist>
-  <para>
-    In addition, these arguments are recognised by the live image only:
-  </para>
-  <variablelist>
-    <varlistentry>
-      <term>
-        <literal>live.nixos.passwd=password</literal>
-      </term>
-      <listitem>
-        <para>
-          Set the password for the <literal>nixos</literal> live user.
-          This can be used for SSH access if there are issues using the
-          terminal.
-        </para>
-      </listitem>
-    </varlistentry>
-  </variablelist>
-  <para>
-    Notice that for <literal>boot.shell_on_fail</literal>,
-    <literal>boot.debug1</literal>,
-    <literal>boot.debug1devices</literal>, and
-    <literal>boot.debug1mounts</literal>, if you did
-    <emphasis role="strong">not</emphasis> select <quote>start the new
-    shell as pid 1</quote>, and you <literal>exit</literal> from the new
-    shell, boot will proceed normally from the point where it failed, as
-    if you’d chosen <quote>ignore the error and continue</quote>.
-  </para>
-  <para>
-    If no login prompts or X11 login screens appear (e.g. due to hanging
-    dependencies), you can press Alt+ArrowUp. If you’re lucky, this will
-    start rescue mode (described above). (Also note that since most
-    units have a 90-second timeout before systemd gives up on them, the
-    <literal>agetty</literal> login prompts should appear eventually
-    unless something is very wrong.)
-  </para>
-</section>
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
deleted file mode 100644
index 4243d2bf53f9..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-nix-gc">
-  <title>Cleaning the Nix Store</title>
-  <para>
-    Nix has a purely functional model, meaning that packages are never
-    upgraded in place. Instead new versions of packages end up in a
-    different location in the Nix store (<literal>/nix/store</literal>).
-    You should periodically run Nix’s <emphasis>garbage
-    collector</emphasis> to remove old, unreferenced packages. This is
-    easy:
-  </para>
-  <programlisting>
-$ nix-collect-garbage
-</programlisting>
-  <para>
-    Alternatively, you can use a systemd unit that does the same in the
-    background:
-  </para>
-  <programlisting>
-# systemctl start nix-gc.service
-</programlisting>
-  <para>
-    You can tell NixOS in <literal>configuration.nix</literal> to run
-    this unit automatically at certain points in time, for instance,
-    every night at 03:15:
-  </para>
-  <programlisting language="bash">
-nix.gc.automatic = true;
-nix.gc.dates = &quot;03:15&quot;;
-</programlisting>
-  <para>
-    The commands above do not remove garbage collector roots, such as
-    old system configurations. Thus they do not remove the ability to
-    roll back to previous configurations. The following command deletes
-    old roots, removing the ability to roll back to them:
-  </para>
-  <programlisting>
-$ nix-collect-garbage -d
-</programlisting>
-  <para>
-    You can also do this for specific profiles, e.g.
-  </para>
-  <programlisting>
-$ nix-env -p /nix/var/nix/profiles/per-user/eelco/profile --delete-generations old
-</programlisting>
-  <para>
-    Note that NixOS system configurations are stored in the profile
-    <literal>/nix/var/nix/profiles/system</literal>.
-  </para>
-  <para>
-    Another way to reclaim disk space (often as much as 40% of the size
-    of the Nix store) is to run Nix’s store optimiser, which seeks out
-    identical files in the store and replaces them with hard links to a
-    single copy.
-  </para>
-  <programlisting>
-$ nix-store --optimise
-</programlisting>
-  <para>
-    Since this command needs to read the entire Nix store, it can take
-    quite a while to finish.
-  </para>
-  <section xml:id="sect-nixos-gc-boot-entries">
-    <title>NixOS Boot Entries</title>
-    <para>
-      If your <literal>/boot</literal> partition runs out of space,
-      after clearing old profiles you must rebuild your system with
-      <literal>nixos-rebuild boot</literal> or
-      <literal>nixos-rebuild switch</literal> to update the
-      <literal>/boot</literal> partition and clear space.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/container-networking.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/container-networking.section.xml
deleted file mode 100644
index 788a2b7b0acb..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/container-networking.section.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-container-networking">
-  <title>Container Networking</title>
-  <para>
-    When you create a container using
-    <literal>nixos-container create</literal>, it gets it own private
-    IPv4 address in the range <literal>10.233.0.0/16</literal>. You can
-    get the container’s IPv4 address as follows:
-  </para>
-  <programlisting>
-# nixos-container show-ip foo
-10.233.4.2
-
-$ ping -c1 10.233.4.2
-64 bytes from 10.233.4.2: icmp_seq=1 ttl=64 time=0.106 ms
-</programlisting>
-  <para>
-    Networking is implemented using a pair of virtual Ethernet devices.
-    The network interface in the container is called
-    <literal>eth0</literal>, while the matching interface in the host is
-    called <literal>ve-container-name</literal> (e.g.,
-    <literal>ve-foo</literal>). The container has its own network
-    namespace and the <literal>CAP_NET_ADMIN</literal> capability, so it
-    can perform arbitrary network configuration such as setting up
-    firewall rules, without affecting or having access to the host’s
-    network.
-  </para>
-  <para>
-    By default, containers cannot talk to the outside network. If you
-    want that, you should set up Network Address Translation (NAT) rules
-    on the host to rewrite container traffic to use your external IP
-    address. This can be accomplished using the following configuration
-    on the host:
-  </para>
-  <programlisting language="bash">
-networking.nat.enable = true;
-networking.nat.internalInterfaces = [&quot;ve-+&quot;];
-networking.nat.externalInterface = &quot;eth0&quot;;
-</programlisting>
-  <para>
-    where <literal>eth0</literal> should be replaced with the desired
-    external interface. Note that <literal>ve-+</literal> is a wildcard
-    that matches all container interfaces.
-  </para>
-  <para>
-    If you are using Network Manager, you need to explicitly prevent it
-    from managing container interfaces:
-  </para>
-  <programlisting language="bash">
-networking.networkmanager.unmanaged = [ &quot;interface-name:ve-*&quot; ];
-</programlisting>
-  <para>
-    You may need to restart your system for the changes to take effect.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/containers.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/containers.chapter.xml
deleted file mode 100644
index afbd5b35aaa5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/containers.chapter.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-containers">
-  <title>Container Management</title>
-  <para>
-    NixOS allows you to easily run other NixOS instances as
-    <emphasis>containers</emphasis>. Containers are a light-weight
-    approach to virtualisation that runs software in the container at
-    the same speed as in the host system. NixOS containers share the Nix
-    store of the host, making container creation very efficient.
-  </para>
-  <warning>
-    <para>
-      Currently, NixOS containers are not perfectly isolated from the
-      host system. This means that a user with root access to the
-      container can do things that affect the host. So you should not
-      give container root access to untrusted users.
-    </para>
-  </warning>
-  <para>
-    NixOS containers can be created in two ways: imperatively, using the
-    command <literal>nixos-container</literal>, and declaratively, by
-    specifying them in your <literal>configuration.nix</literal>. The
-    declarative approach implies that containers get upgraded along with
-    your host system when you run <literal>nixos-rebuild</literal>,
-    which is often not what you want. By contrast, in the imperative
-    approach, containers are configured and updated independently from
-    the host system.
-  </para>
-  <xi:include href="imperative-containers.section.xml" />
-  <xi:include href="declarative-containers.section.xml" />
-  <xi:include href="container-networking.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/control-groups.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/control-groups.chapter.xml
deleted file mode 100644
index 8dab2c9d44b4..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/control-groups.chapter.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-cgroups">
-  <title>Control Groups</title>
-  <para>
-    To keep track of the processes in a running system, systemd uses
-    <emphasis>control groups</emphasis> (cgroups). A control group is a
-    set of processes used to allocate resources such as CPU, memory or
-    I/O bandwidth. There can be multiple control group hierarchies,
-    allowing each kind of resource to be managed independently.
-  </para>
-  <para>
-    The command <literal>systemd-cgls</literal> lists all control groups
-    in the <literal>systemd</literal> hierarchy, which is what systemd
-    uses to keep track of the processes belonging to each service or
-    user session:
-  </para>
-  <programlisting>
-$ systemd-cgls
-├─user
-│ └─eelco
-│   └─c1
-│     ├─ 2567 -:0
-│     ├─ 2682 kdeinit4: kdeinit4 Running...
-│     ├─ ...
-│     └─10851 sh -c less -R
-└─system
-  ├─httpd.service
-  │ ├─2444 httpd -f /nix/store/3pyacby5cpr55a03qwbnndizpciwq161-httpd.conf -DNO_DETACH
-  │ └─...
-  ├─dhcpcd.service
-  │ └─2376 dhcpcd --config /nix/store/f8dif8dsi2yaa70n03xir8r653776ka6-dhcpcd.conf
-  └─ ...
-</programlisting>
-  <para>
-    Similarly, <literal>systemd-cgls cpu</literal> shows the cgroups in
-    the CPU hierarchy, which allows per-cgroup CPU scheduling
-    priorities. By default, every systemd service gets its own CPU
-    cgroup, while all user sessions are in the top-level CPU cgroup.
-    This ensures, for instance, that a thousand run-away processes in
-    the <literal>httpd.service</literal> cgroup cannot starve the CPU
-    for one process in the <literal>postgresql.service</literal> cgroup.
-    (By contrast, it they were in the same cgroup, then the PostgreSQL
-    process would get 1/1001 of the cgroup’s CPU time.) You can limit a
-    service’s CPU share in <literal>configuration.nix</literal>:
-  </para>
-  <programlisting language="bash">
-systemd.services.httpd.serviceConfig.CPUShares = 512;
-</programlisting>
-  <para>
-    By default, every cgroup has 1024 CPU shares, so this will halve the
-    CPU allocation of the <literal>httpd.service</literal> cgroup.
-  </para>
-  <para>
-    There also is a <literal>memory</literal> hierarchy that controls
-    memory allocation limits; by default, all processes are in the
-    top-level cgroup, so any service or session can exhaust all
-    available memory. Per-cgroup memory limits can be specified in
-    <literal>configuration.nix</literal>; for instance, to limit
-    <literal>httpd.service</literal> to 512 MiB of RAM (excluding swap):
-  </para>
-  <programlisting language="bash">
-systemd.services.httpd.serviceConfig.MemoryLimit = &quot;512M&quot;;
-</programlisting>
-  <para>
-    The command <literal>systemd-cgtop</literal> shows a continuously
-    updated list of all cgroups with their CPU and memory usage.
-  </para>
-</chapter>
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
deleted file mode 100644
index b8179dca1f8b..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-declarative-containers">
-  <title>Declarative Container Specification</title>
-  <para>
-    You can also specify containers and their configuration in the
-    host’s <literal>configuration.nix</literal>. For example, the
-    following specifies that there shall be a container named
-    <literal>database</literal> running PostgreSQL:
-  </para>
-  <programlisting language="bash">
-containers.database =
-  { config =
-      { config, pkgs, ... }:
-      { services.postgresql.enable = true;
-      services.postgresql.package = pkgs.postgresql_10;
-      };
-  };
-</programlisting>
-  <para>
-    If you run <literal>nixos-rebuild switch</literal>, the container
-    will be built. If the container was already running, it will be
-    updated in place, without rebooting. The container can be configured
-    to start automatically by setting
-    <literal>containers.database.autoStart = true</literal> in its
-    configuration.
-  </para>
-  <para>
-    By default, declarative containers share the network namespace of
-    the host, meaning that they can listen on (privileged) ports.
-    However, they cannot change the network configuration. You can give
-    a container its own network as follows:
-  </para>
-  <programlisting language="bash">
-containers.database = {
-  privateNetwork = true;
-  hostAddress = &quot;192.168.100.10&quot;;
-  localAddress = &quot;192.168.100.11&quot;;
-};
-</programlisting>
-  <para>
-    This gives the container a private virtual Ethernet interface with
-    IP address <literal>192.168.100.11</literal>, which is hooked up to
-    a virtual Ethernet interface on the host with IP address
-    <literal>192.168.100.10</literal>. (See the next section for details
-    on container networking.)
-  </para>
-  <para>
-    To disable the container, just remove it from
-    <literal>configuration.nix</literal> and run
-    <literal>nixos-rebuild switch</literal>. Note that this will not
-    delete the root directory of the container in
-    <literal>/var/lib/nixos-containers</literal>. Containers can be
-    destroyed using the imperative method:
-    <literal>nixos-container destroy foo</literal>.
-  </para>
-  <para>
-    Declarative containers can be started and stopped using the
-    corresponding systemd service, e.g.
-    <literal>systemctl start container@database</literal>.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/imperative-containers.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/imperative-containers.section.xml
deleted file mode 100644
index 865fc4689398..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/imperative-containers.section.xml
+++ /dev/null
@@ -1,132 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-imperative-containers">
-  <title>Imperative Container Management</title>
-  <para>
-    We’ll cover imperative container management using
-    <literal>nixos-container</literal> first. Be aware that container
-    management is currently only possible as <literal>root</literal>.
-  </para>
-  <para>
-    You create a container with identifier <literal>foo</literal> as
-    follows:
-  </para>
-  <programlisting>
-# nixos-container create foo
-</programlisting>
-  <para>
-    This creates the container’s root directory in
-    <literal>/var/lib/nixos-containers/foo</literal> and a small
-    configuration file in
-    <literal>/etc/nixos-containers/foo.conf</literal>. It also builds
-    the container’s initial system configuration and stores it in
-    <literal>/nix/var/nix/profiles/per-container/foo/system</literal>.
-    You can modify the initial configuration of the container on the
-    command line. For instance, to create a container that has
-    <literal>sshd</literal> running, with the given public key for
-    <literal>root</literal>:
-  </para>
-  <programlisting>
-# nixos-container create foo --config '
-  services.openssh.enable = true;
-  users.users.root.openssh.authorizedKeys.keys = [&quot;ssh-dss AAAAB3N…&quot;];
-'
-</programlisting>
-  <para>
-    By default the next free address in the
-    <literal>10.233.0.0/16</literal> subnet will be chosen as container
-    IP. This behavior can be altered by setting
-    <literal>--host-address</literal> and
-    <literal>--local-address</literal>:
-  </para>
-  <programlisting>
-# nixos-container create test --config-file test-container.nix \
-    --local-address 10.235.1.2 --host-address 10.235.1.1
-</programlisting>
-  <para>
-    Creating a container does not start it. To start the container, run:
-  </para>
-  <programlisting>
-# nixos-container start foo
-</programlisting>
-  <para>
-    This command will return as soon as the container has booted and has
-    reached <literal>multi-user.target</literal>. On the host, the
-    container runs within a systemd unit called
-    <literal>container@container-name.service</literal>. Thus, if
-    something went wrong, you can get status info using
-    <literal>systemctl</literal>:
-  </para>
-  <programlisting>
-# systemctl status container@foo
-</programlisting>
-  <para>
-    If the container has started successfully, you can log in as root
-    using the <literal>root-login</literal> operation:
-  </para>
-  <programlisting>
-# nixos-container root-login foo
-[root@foo:~]#
-</programlisting>
-  <para>
-    Note that only root on the host can do this (since there is no
-    authentication). You can also get a regular login prompt using the
-    <literal>login</literal> operation, which is available to all users
-    on the host:
-  </para>
-  <programlisting>
-# nixos-container login foo
-foo login: alice
-Password: ***
-</programlisting>
-  <para>
-    With <literal>nixos-container run</literal>, you can execute
-    arbitrary commands in the container:
-  </para>
-  <programlisting>
-# nixos-container run foo -- uname -a
-Linux foo 3.4.82 #1-NixOS SMP Thu Mar 20 14:44:05 UTC 2014 x86_64 GNU/Linux
-</programlisting>
-  <para>
-    There are several ways to change the configuration of the container.
-    First, on the host, you can edit
-    <literal>/var/lib/container/name/etc/nixos/configuration.nix</literal>,
-    and run
-  </para>
-  <programlisting>
-# nixos-container update foo
-</programlisting>
-  <para>
-    This will build and activate the new configuration. You can also
-    specify a new configuration on the command line:
-  </para>
-  <programlisting>
-# nixos-container update foo --config '
-  services.httpd.enable = true;
-  services.httpd.adminAddr = &quot;foo@example.org&quot;;
-  networking.firewall.allowedTCPPorts = [ 80 ];
-'
-
-# curl http://$(nixos-container show-ip foo)/
-&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 3.2 Final//EN&quot;&gt;…
-</programlisting>
-  <para>
-    However, note that this will overwrite the container’s
-    <literal>/etc/nixos/configuration.nix</literal>.
-  </para>
-  <para>
-    Alternatively, you can change the configuration from within the
-    container itself by running <literal>nixos-rebuild switch</literal>
-    inside the container. Note that the container by default does not
-    have a copy of the NixOS channel, so you should run
-    <literal>nix-channel --update</literal> first.
-  </para>
-  <para>
-    Containers can be stopped and started using
-    <literal>nixos-container stop</literal> and
-    <literal>nixos-container start</literal>, respectively, or by using
-    <literal>systemctl</literal> on the container’s service unit. To
-    destroy a container, including its file system, do
-  </para>
-  <programlisting>
-# nixos-container destroy foo
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/logging.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/logging.chapter.xml
deleted file mode 100644
index 4da38c065a27..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/logging.chapter.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-logging">
-  <title>Logging</title>
-  <para>
-    System-wide logging is provided by systemd’s
-    <emphasis>journal</emphasis>, which subsumes traditional logging
-    daemons such as syslogd and klogd. Log entries are kept in binary
-    files in <literal>/var/log/journal/</literal>. The command
-    <literal>journalctl</literal> allows you to see the contents of the
-    journal. For example,
-  </para>
-  <programlisting>
-$ journalctl -b
-</programlisting>
-  <para>
-    shows all journal entries since the last reboot. (The output of
-    <literal>journalctl</literal> is piped into <literal>less</literal>
-    by default.) You can use various options and match operators to
-    restrict output to messages of interest. For instance, to get all
-    messages from PostgreSQL:
-  </para>
-  <programlisting>
-$ journalctl -u postgresql.service
--- Logs begin at Mon, 2013-01-07 13:28:01 CET, end at Tue, 2013-01-08 01:09:57 CET. --
-...
-Jan 07 15:44:14 hagbard postgres[2681]: [2-1] LOG:  database system is shut down
--- Reboot --
-Jan 07 15:45:10 hagbard postgres[2532]: [1-1] LOG:  database system was shut down at 2013-01-07 15:44:14 CET
-Jan 07 15:45:13 hagbard postgres[2500]: [1-1] LOG:  database system is ready to accept connections
-</programlisting>
-  <para>
-    Or to get all messages since the last reboot that have at least a
-    <quote>critical</quote> severity level:
-  </para>
-  <programlisting>
-$ journalctl -b -p crit
-Dec 17 21:08:06 mandark sudo[3673]: pam_unix(sudo:auth): auth could not identify password for [alice]
-Dec 29 01:30:22 mandark kernel[6131]: [1053513.909444] CPU6: Core temperature above threshold, cpu clock throttled (total events = 1)
-</programlisting>
-  <para>
-    The system journal is readable by root and by users in the
-    <literal>wheel</literal> and <literal>systemd-journal</literal>
-    groups. All users have a private journal that can be read using
-    <literal>journalctl</literal>.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/maintenance-mode.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/maintenance-mode.section.xml
deleted file mode 100644
index c86b1911c117..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/maintenance-mode.section.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-maintenance-mode">
-  <title>Maintenance Mode</title>
-  <para>
-    You can enter rescue mode by running:
-  </para>
-  <programlisting>
-# systemctl rescue
-</programlisting>
-  <para>
-    This will eventually give you a single-user root shell. Systemd will
-    stop (almost) all system services. To get out of maintenance mode,
-    just exit from the rescue shell.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/network-problems.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/network-problems.section.xml
deleted file mode 100644
index 4c0598ca94e8..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/network-problems.section.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-nix-network-issues">
-  <title>Network Problems</title>
-  <para>
-    Nix uses a so-called <emphasis>binary cache</emphasis> to optimise
-    building a package from source into downloading it as a pre-built
-    binary. That is, whenever a command like
-    <literal>nixos-rebuild</literal> needs a path in the Nix store, Nix
-    will try to download that path from the Internet rather than build
-    it from source. The default binary cache is
-    <literal>https://cache.nixos.org/</literal>. If this cache is
-    unreachable, Nix operations may take a long time due to HTTP
-    connection timeouts. You can disable the use of the binary cache by
-    adding <literal>--option use-binary-caches false</literal>, e.g.
-  </para>
-  <programlisting>
-# nixos-rebuild switch --option use-binary-caches false
-</programlisting>
-  <para>
-    If you have an alternative binary cache at your disposal, you can
-    use it instead:
-  </para>
-  <programlisting>
-# nixos-rebuild switch --option binary-caches http://my-cache.example.org/
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/rebooting.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/rebooting.chapter.xml
deleted file mode 100644
index 78ee75afb642..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/rebooting.chapter.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-rebooting">
-  <title>Rebooting and Shutting Down</title>
-  <para>
-    The system can be shut down (and automatically powered off) by
-    doing:
-  </para>
-  <programlisting>
-# shutdown
-</programlisting>
-  <para>
-    This is equivalent to running <literal>systemctl poweroff</literal>.
-  </para>
-  <para>
-    To reboot the system, run
-  </para>
-  <programlisting>
-# reboot
-</programlisting>
-  <para>
-    which is equivalent to <literal>systemctl reboot</literal>.
-    Alternatively, you can quickly reboot the system using
-    <literal>kexec</literal>, which bypasses the BIOS by directly
-    loading the new kernel into memory:
-  </para>
-  <programlisting>
-# systemctl kexec
-</programlisting>
-  <para>
-    The machine can be suspended to RAM (if supported) using
-    <literal>systemctl suspend</literal>, and suspended to disk using
-    <literal>systemctl hibernate</literal>.
-  </para>
-  <para>
-    These commands can be run by any user who is logged in locally, i.e.
-    on a virtual console or in X11; otherwise, the user is asked for
-    authentication.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/rollback.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/rollback.section.xml
deleted file mode 100644
index a8df053011c5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/rollback.section.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-rollback">
-  <title>Rolling Back Configuration Changes</title>
-  <para>
-    After running <literal>nixos-rebuild</literal> to switch to a new
-    configuration, you may find that the new configuration doesn’t work
-    very well. In that case, there are several ways to return to a
-    previous configuration.
-  </para>
-  <para>
-    First, the GRUB boot manager allows you to boot into any previous
-    configuration that hasn’t been garbage-collected. These
-    configurations can be found under the GRUB submenu <quote>NixOS -
-    All configurations</quote>. This is especially useful if the new
-    configuration fails to boot. After the system has booted, you can
-    make the selected configuration the default for subsequent boots:
-  </para>
-  <programlisting>
-# /run/current-system/bin/switch-to-configuration boot
-</programlisting>
-  <para>
-    Second, you can switch to the previous configuration in a running
-    system:
-  </para>
-  <programlisting>
-# nixos-rebuild switch --rollback
-</programlisting>
-  <para>
-    This is equivalent to running:
-  </para>
-  <programlisting>
-# /nix/var/nix/profiles/system-N-link/bin/switch-to-configuration switch
-</programlisting>
-  <para>
-    where <literal>N</literal> is the number of the NixOS system
-    configuration. To get a list of the available configurations, do:
-  </para>
-  <programlisting>
-$ ls -l /nix/var/nix/profiles/system-*-link
-...
-lrwxrwxrwx 1 root root 78 Aug 12 13:54 /nix/var/nix/profiles/system-268-link -&gt; /nix/store/202b...-nixos-13.07pre4932_5a676e4-4be1055
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/service-mgmt.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/service-mgmt.chapter.xml
deleted file mode 100644
index 8b01b8f896a4..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/service-mgmt.chapter.xml
+++ /dev/null
@@ -1,141 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-systemctl">
-  <title>Service Management</title>
-  <para>
-    In NixOS, all system services are started and monitored using the
-    systemd program. systemd is the <quote>init</quote> process of the
-    system (i.e. PID 1), the parent of all other processes. It manages a
-    set of so-called <quote>units</quote>, which can be things like
-    system services (programs), but also mount points, swap files,
-    devices, targets (groups of units) and more. Units can have complex
-    dependencies; for instance, one unit can require that another unit
-    must be successfully started before the first unit can be started.
-    When the system boots, it starts a unit named
-    <literal>default.target</literal>; the dependencies of this unit
-    cause all system services to be started, file systems to be mounted,
-    swap files to be activated, and so on.
-  </para>
-  <section xml:id="sect-nixos-systemd-general">
-    <title>Interacting with a running systemd</title>
-    <para>
-      The command <literal>systemctl</literal> is the main way to
-      interact with <literal>systemd</literal>. The following paragraphs
-      demonstrate ways to interact with any OS running systemd as init
-      system. NixOS is of no exception. The
-      <link linkend="sect-nixos-systemd-nixos">next section </link>
-      explains NixOS specific things worth knowing.
-    </para>
-    <para>
-      Without any arguments, <literal>systemctl</literal> the status of
-      active units:
-    </para>
-    <programlisting>
-$ systemctl
--.mount          loaded active mounted   /
-swapfile.swap    loaded active active    /swapfile
-sshd.service     loaded active running   SSH Daemon
-graphical.target loaded active active    Graphical Interface
-...
-</programlisting>
-    <para>
-      You can ask for detailed status information about a unit, for
-      instance, the PostgreSQL database service:
-    </para>
-    <programlisting>
-$ systemctl status postgresql.service
-postgresql.service - PostgreSQL Server
-          Loaded: loaded (/nix/store/pn3q73mvh75gsrl8w7fdlfk3fq5qm5mw-unit/postgresql.service)
-          Active: active (running) since Mon, 2013-01-07 15:55:57 CET; 9h ago
-        Main PID: 2390 (postgres)
-          CGroup: name=systemd:/system/postgresql.service
-                  ├─2390 postgres
-                  ├─2418 postgres: writer process
-                  ├─2419 postgres: wal writer process
-                  ├─2420 postgres: autovacuum launcher process
-                  ├─2421 postgres: stats collector process
-                  └─2498 postgres: zabbix zabbix [local] idle
-
-Jan 07 15:55:55 hagbard postgres[2394]: [1-1] LOG:  database system was shut down at 2013-01-07 15:55:05 CET
-Jan 07 15:55:57 hagbard postgres[2390]: [1-1] LOG:  database system is ready to accept connections
-Jan 07 15:55:57 hagbard postgres[2420]: [1-1] LOG:  autovacuum launcher started
-Jan 07 15:55:57 hagbard systemd[1]: Started PostgreSQL Server.
-</programlisting>
-    <para>
-      Note that this shows the status of the unit (active and running),
-      all the processes belonging to the service, as well as the most
-      recent log messages from the service.
-    </para>
-    <para>
-      Units can be stopped, started or restarted:
-    </para>
-    <programlisting>
-# systemctl stop postgresql.service
-# systemctl start postgresql.service
-# systemctl restart postgresql.service
-</programlisting>
-    <para>
-      These operations are synchronous: they wait until the service has
-      finished starting or stopping (or has failed). Starting a unit
-      will cause the dependencies of that unit to be started as well (if
-      necessary).
-    </para>
-  </section>
-  <section xml:id="sect-nixos-systemd-nixos">
-    <title>systemd in NixOS</title>
-    <para>
-      Packages in Nixpkgs sometimes provide systemd units with them,
-      usually in e.g <literal>#pkg-out#/lib/systemd/</literal>. Putting
-      such a package in <literal>environment.systemPackages</literal>
-      doesn't make the service available to users or the system.
-    </para>
-    <para>
-      In order to enable a systemd <emphasis>system</emphasis> service
-      with provided upstream package, use (e.g):
-    </para>
-    <programlisting language="bash">
-systemd.packages = [ pkgs.packagekit ];
-</programlisting>
-    <para>
-      Usually NixOS modules written by the community do the above, plus
-      take care of other details. If a module was written for a service
-      you are interested in, you'd probably need only to use
-      <literal>services.#name#.enable = true;</literal>. These services
-      are defined in Nixpkgs'
-      <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules">
-      <literal>nixos/modules/</literal> directory </link>. In case the
-      service is simple enough, the above method should work, and start
-      the service on boot.
-    </para>
-    <para>
-      <emphasis>User</emphasis> systemd services on the other hand,
-      should be treated differently. Given a package that has a systemd
-      unit file at <literal>#pkg-out#/lib/systemd/user/</literal>, using
-      <xref linkend="opt-systemd.packages" /> will make you able to
-      start the service via <literal>systemctl --user start</literal>,
-      but it won't start automatically on login. However, You can
-      imperatively enable it by adding the package's attribute to
-      <xref linkend="opt-systemd.packages" /> and then do this (e.g):
-    </para>
-    <programlisting>
-$ mkdir -p ~/.config/systemd/user/default.target.wants
-$ ln -s /run/current-system/sw/lib/systemd/user/syncthing.service ~/.config/systemd/user/default.target.wants/
-$ systemctl --user daemon-reload
-$ systemctl --user enable syncthing.service
-</programlisting>
-    <para>
-      If you are interested in a timer file, use
-      <literal>timers.target.wants</literal> instead of
-      <literal>default.target.wants</literal> in the 1st and 2nd
-      command.
-    </para>
-    <para>
-      Using <literal>systemctl --user enable syncthing.service</literal>
-      instead of the above, will work, but it'll use the absolute path
-      of <literal>syncthing.service</literal> for the symlink, and this
-      path is in <literal>/nix/store/.../lib/systemd/user/</literal>.
-      Hence <link linkend="sec-nix-gc">garbage collection</link> will
-      remove that file and you will wind up with a broken symlink in
-      your systemd configuration, which in turn will not make the
-      service / timer start on login.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/store-corruption.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/store-corruption.section.xml
deleted file mode 100644
index 9ed572d484dc..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/store-corruption.section.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-nix-store-corruption">
-  <title>Nix Store Corruption</title>
-  <para>
-    After a system crash, it’s possible for files in the Nix store to
-    become corrupted. (For instance, the Ext4 file system has the
-    tendency to replace un-synced files with zero bytes.) NixOS tries
-    hard to prevent this from happening: it performs a
-    <literal>sync</literal> before switching to a new configuration, and
-    Nix’s database is fully transactional. If corruption still occurs,
-    you may be able to fix it automatically.
-  </para>
-  <para>
-    If the corruption is in a path in the closure of the NixOS system
-    configuration, you can fix it by doing
-  </para>
-  <programlisting>
-# nixos-rebuild switch --repair
-</programlisting>
-  <para>
-    This will cause Nix to check every path in the closure, and if its
-    cryptographic hash differs from the hash recorded in Nix’s database,
-    the path is rebuilt or redownloaded.
-  </para>
-  <para>
-    You can also scan the entire Nix store for corrupt paths:
-  </para>
-  <programlisting>
-# nix-store --verify --check-contents --repair
-</programlisting>
-  <para>
-    Any corrupt paths will be redownloaded if they’re available in a
-    binary cache; otherwise, they cannot be repaired.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml
deleted file mode 100644
index 8bbb8a1fe729..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-troubleshooting">
-  <title>Troubleshooting</title>
-  <para>
-    This chapter describes solutions to common problems you might
-    encounter when you manage your NixOS system.
-  </para>
-  <xi:include href="boot-problems.section.xml" />
-  <xi:include href="maintenance-mode.section.xml" />
-  <xi:include href="rollback.section.xml" />
-  <xi:include href="store-corruption.section.xml" />
-  <xi:include href="network-problems.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/administration/user-sessions.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/user-sessions.chapter.xml
deleted file mode 100644
index e8c64f153fc5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/administration/user-sessions.chapter.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-user-sessions">
-  <title>User Sessions</title>
-  <para>
-    Systemd keeps track of all users who are logged into the system
-    (e.g. on a virtual console or remotely via SSH). The command
-    <literal>loginctl</literal> allows querying and manipulating user
-    sessions. For instance, to list all user sessions:
-  </para>
-  <programlisting>
-$ loginctl
-   SESSION        UID USER             SEAT
-        c1        500 eelco            seat0
-        c3          0 root             seat0
-        c4        500 alice
-</programlisting>
-  <para>
-    This shows that two users are logged in locally, while another is
-    logged in remotely. (<quote>Seats</quote> are essentially the
-    combinations of displays and input devices attached to the system;
-    usually, there is only one seat.) To get information about a
-    session:
-  </para>
-  <programlisting>
-$ loginctl session-status c3
-c3 - root (0)
-           Since: Tue, 2013-01-08 01:17:56 CET; 4min 42s ago
-          Leader: 2536 (login)
-            Seat: seat0; vc3
-             TTY: /dev/tty3
-         Service: login; type tty; class user
-           State: online
-          CGroup: name=systemd:/user/root/c3
-                  ├─ 2536 /nix/store/10mn4xip9n7y9bxqwnsx7xwx2v2g34xn-shadow-4.1.5.1/bin/login --
-                  ├─10339 -bash
-                  └─10355 w3m nixos.org
-</programlisting>
-  <para>
-    This shows that the user is logged in on virtual console 3. It also
-    lists the processes belonging to this session. Since systemd keeps
-    track of this, you can terminate a session in a way that ensures
-    that all the session’s processes are gone:
-  </para>
-  <programlisting>
-# loginctl terminate-session c3
-</programlisting>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/abstractions.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/abstractions.section.xml
deleted file mode 100644
index c71e23e34adf..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/abstractions.section.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-module-abstractions">
-  <title>Abstractions</title>
-  <para>
-    If you find yourself repeating yourself over and over, it’s time to
-    abstract. Take, for instance, this Apache HTTP Server configuration:
-  </para>
-  <programlisting language="bash">
-{
-  services.httpd.virtualHosts =
-    { &quot;blog.example.org&quot; = {
-        documentRoot = &quot;/webroot/blog.example.org&quot;;
-        adminAddr = &quot;alice@example.org&quot;;
-        forceSSL = true;
-        enableACME = true;
-        enablePHP = true;
-      };
-      &quot;wiki.example.org&quot; = {
-        documentRoot = &quot;/webroot/wiki.example.org&quot;;
-        adminAddr = &quot;alice@example.org&quot;;
-        forceSSL = true;
-        enableACME = true;
-        enablePHP = true;
-      };
-    };
-}
-</programlisting>
-  <para>
-    It defines two virtual hosts with nearly identical configuration;
-    the only difference is the document root directories. To prevent
-    this duplication, we can use a <literal>let</literal>:
-  </para>
-  <programlisting language="bash">
-let
-  commonConfig =
-    { adminAddr = &quot;alice@example.org&quot;;
-      forceSSL = true;
-      enableACME = true;
-    };
-in
-{
-  services.httpd.virtualHosts =
-    { &quot;blog.example.org&quot; = (commonConfig // { documentRoot = &quot;/webroot/blog.example.org&quot;; });
-      &quot;wiki.example.org&quot; = (commonConfig // { documentRoot = &quot;/webroot/wiki.example.com&quot;; });
-    };
-}
-</programlisting>
-  <para>
-    The <literal>let commonConfig = ...</literal> defines a variable
-    named <literal>commonConfig</literal>. The <literal>//</literal>
-    operator merges two attribute sets, so the configuration of the
-    second virtual host is the set <literal>commonConfig</literal>
-    extended with the document root option.
-  </para>
-  <para>
-    You can write a <literal>let</literal> wherever an expression is
-    allowed. Thus, you also could have written:
-  </para>
-  <programlisting language="bash">
-{
-  services.httpd.virtualHosts =
-    let commonConfig = ...; in
-    { &quot;blog.example.org&quot; = (commonConfig // { ... })
-      &quot;wiki.example.org&quot; = (commonConfig // { ... })
-    };
-}
-</programlisting>
-  <para>
-    but not <literal>{ let commonConfig = ...; in ...; }</literal> since
-    attributes (as opposed to attribute values) are not expressions.
-  </para>
-  <para>
-    <emphasis role="strong">Functions</emphasis> provide another method
-    of abstraction. For instance, suppose that we want to generate lots
-    of different virtual hosts, all with identical configuration except
-    for the document root. This can be done as follows:
-  </para>
-  <programlisting language="bash">
-{
-  services.httpd.virtualHosts =
-    let
-      makeVirtualHost = webroot:
-        { documentRoot = webroot;
-          adminAddr = &quot;alice@example.org&quot;;
-          forceSSL = true;
-          enableACME = true;
-        };
-    in
-      { &quot;example.org&quot; = (makeVirtualHost &quot;/webroot/example.org&quot;);
-        &quot;example.com&quot; = (makeVirtualHost &quot;/webroot/example.com&quot;);
-        &quot;example.gov&quot; = (makeVirtualHost &quot;/webroot/example.gov&quot;);
-        &quot;example.nl&quot; = (makeVirtualHost &quot;/webroot/example.nl&quot;);
-      };
-}
-</programlisting>
-  <para>
-    Here, <literal>makeVirtualHost</literal> is a function that takes a
-    single argument <literal>webroot</literal> and returns the
-    configuration for a virtual host. That function is then called for
-    several names to produce the list of virtual host configurations.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-network-config.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-network-config.section.xml
deleted file mode 100644
index 035ee3122e15..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-network-config.section.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="ad-hoc-network-config">
-  <title>Ad-Hoc Configuration</title>
-  <para>
-    You can use <xref linkend="opt-networking.localCommands" /> to
-    specify shell commands to be run at the end of
-    <literal>network-setup.service</literal>. This is useful for doing
-    network configuration not covered by the existing NixOS modules. For
-    instance, to statically configure an IPv6 address:
-  </para>
-  <programlisting language="bash">
-networking.localCommands =
-  ''
-    ip -6 addr add 2001:610:685:1::1/64 dev eth0
-  '';
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-packages.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-packages.section.xml
deleted file mode 100644
index c9a8d4f3f106..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/ad-hoc-packages.section.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-ad-hoc-packages">
-  <title>Ad-Hoc Package Management</title>
-  <para>
-    With the command <literal>nix-env</literal>, you can install and
-    uninstall packages from the command line. For instance, to install
-    Mozilla Thunderbird:
-  </para>
-  <programlisting>
-$ nix-env -iA nixos.thunderbird
-</programlisting>
-  <para>
-    If you invoke this as root, the package is installed in the Nix
-    profile <literal>/nix/var/nix/profiles/default</literal> and visible
-    to all users of the system; otherwise, the package ends up in
-    <literal>/nix/var/nix/profiles/per-user/username/profile</literal>
-    and is not visible to other users. The <literal>-A</literal> flag
-    specifies the package by its attribute name; without it, the package
-    is installed by matching against its package name (e.g.
-    <literal>thunderbird</literal>). The latter is slower because it
-    requires matching against all available Nix packages, and is
-    ambiguous if there are multiple matching packages.
-  </para>
-  <para>
-    Packages come from the NixOS channel. You typically upgrade a
-    package by updating to the latest version of the NixOS channel:
-  </para>
-  <programlisting>
-$ nix-channel --update nixos
-</programlisting>
-  <para>
-    and then running <literal>nix-env -i</literal> again. Other packages
-    in the profile are <emphasis>not</emphasis> affected; this is the
-    crucial difference with the declarative style of package management,
-    where running <literal>nixos-rebuild switch</literal> causes all
-    packages to be updated to their current versions in the NixOS
-    channel. You can however upgrade all packages for which there is a
-    newer version by doing:
-  </para>
-  <programlisting>
-$ nix-env -u '*'
-</programlisting>
-  <para>
-    A package can be uninstalled using the <literal>-e</literal> flag:
-  </para>
-  <programlisting>
-$ nix-env -e thunderbird
-</programlisting>
-  <para>
-    Finally, you can roll back an undesirable <literal>nix-env</literal>
-    action:
-  </para>
-  <programlisting>
-$ nix-env --rollback
-</programlisting>
-  <para>
-    <literal>nix-env</literal> has many more flags. For details, see the
-    nix-env(1) manpage or the Nix manual.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
deleted file mode 100644
index 07f541666cbe..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
+++ /dev/null
@@ -1,118 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-custom-packages">
-  <title>Adding Custom Packages</title>
-  <para>
-    It’s possible that a package you need is not available in NixOS. In
-    that case, you can do two things. Either you can package it with
-    Nix, or you can try to use prebuilt packages from upstream. Due to
-    the peculiarities of NixOS, it is important to note that building
-    software from source is often easier than using pre-built
-    executables.
-  </para>
-  <section xml:id="sec-custom-packages-nix">
-    <title>Building with Nix</title>
-    <para>
-      This can be done either in-tree or out-of-tree. For an in-tree
-      build, you can clone the Nixpkgs repository, add the package to
-      your clone, and (optionally) submit a patch or pull request to
-      have it accepted into the main Nixpkgs repository. This is
-      described in detail in the
-      <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs
-      manual</link>. In short, you clone Nixpkgs:
-    </para>
-    <programlisting>
-$ git clone https://github.com/NixOS/nixpkgs
-$ cd nixpkgs
-</programlisting>
-    <para>
-      Then you write and test the package as described in the Nixpkgs
-      manual. Finally, you add it to
-      <xref linkend="opt-environment.systemPackages" />, e.g.
-    </para>
-    <programlisting language="bash">
-environment.systemPackages = [ pkgs.my-package ];
-</programlisting>
-    <para>
-      and you run <literal>nixos-rebuild</literal>, specifying your own
-      Nixpkgs tree:
-    </para>
-    <programlisting>
-# nixos-rebuild switch -I nixpkgs=/path/to/my/nixpkgs
-</programlisting>
-    <para>
-      The second possibility is to add the package outside of the
-      Nixpkgs tree. For instance, here is how you specify a build of the
-      <link xlink:href="https://www.gnu.org/software/hello/">GNU
-      Hello</link> package directly in
-      <literal>configuration.nix</literal>:
-    </para>
-    <programlisting language="bash">
-environment.systemPackages =
-  let
-    my-hello = with pkgs; stdenv.mkDerivation rec {
-      name = &quot;hello-2.8&quot;;
-      src = fetchurl {
-        url = &quot;mirror://gnu/hello/${name}.tar.gz&quot;;
-        sha256 = &quot;0wqd8sjmxfskrflaxywc7gqw7sfawrfvdxd9skxawzfgyy0pzdz6&quot;;
-      };
-    };
-  in
-  [ my-hello ];
-</programlisting>
-    <para>
-      Of course, you can also move the definition of
-      <literal>my-hello</literal> into a separate Nix expression, e.g.
-    </para>
-    <programlisting language="bash">
-environment.systemPackages = [ (import ./my-hello.nix) ];
-</programlisting>
-    <para>
-      where <literal>my-hello.nix</literal> contains:
-    </para>
-    <programlisting language="bash">
-with import &lt;nixpkgs&gt; {}; # bring all of Nixpkgs into scope
-
-stdenv.mkDerivation rec {
-  name = &quot;hello-2.8&quot;;
-  src = fetchurl {
-    url = &quot;mirror://gnu/hello/${name}.tar.gz&quot;;
-    sha256 = &quot;0wqd8sjmxfskrflaxywc7gqw7sfawrfvdxd9skxawzfgyy0pzdz6&quot;;
-  };
-}
-</programlisting>
-    <para>
-      This allows testing the package easily:
-    </para>
-    <programlisting>
-$ nix-build my-hello.nix
-$ ./result/bin/hello
-Hello, world!
-</programlisting>
-  </section>
-  <section xml:id="sec-custom-packages-prebuilt">
-    <title>Using pre-built executables</title>
-    <para>
-      Most pre-built executables will not work on NixOS. There are two
-      notable exceptions: flatpaks and AppImages. For flatpaks see the
-      <link linkend="module-services-flatpak">dedicated section</link>.
-      AppImages will not run <quote>as-is</quote> on NixOS. First you
-      need to install <literal>appimage-run</literal>: add to
-      <literal>/etc/nixos/configuration.nix</literal>
-    </para>
-    <programlisting language="bash">
-environment.systemPackages = [ pkgs.appimage-run ];
-</programlisting>
-    <para>
-      Then instead of running the AppImage <quote>as-is</quote>, run
-      <literal>appimage-run foo.appimage</literal>.
-    </para>
-    <para>
-      To make other pre-built executables work on NixOS, you need to
-      package them with Nix and special helpers like
-      <literal>autoPatchelfHook</literal> or
-      <literal>buildFHSUserEnv</literal>. See the
-      <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs
-      manual</link> for details. This is complex and often doing a
-      source build is easier.
-    </para>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/config-file.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/config-file.section.xml
deleted file mode 100644
index 952c6e600302..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/config-file.section.xml
+++ /dev/null
@@ -1,231 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-configuration-file">
-  <title>NixOS Configuration File</title>
-  <para>
-    The NixOS configuration file generally looks like this:
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ option definitions
-}
-</programlisting>
-  <para>
-    The first line (<literal>{ config, pkgs, ... }:</literal>) denotes
-    that this is actually a function that takes at least the two
-    arguments <literal>config</literal> and <literal>pkgs</literal>.
-    (These are explained later, in chapter
-    <xref linkend="sec-writing-modules" />) The function returns a
-    <emphasis>set</emphasis> of option definitions
-    (<literal>{ ... }</literal>). These definitions have the form
-    <literal>name = value</literal>, where <literal>name</literal> is
-    the name of an option and <literal>value</literal> is its value. For
-    example,
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ services.httpd.enable = true;
-  services.httpd.adminAddr = &quot;alice@example.org&quot;;
-  services.httpd.virtualHosts.localhost.documentRoot = &quot;/webroot&quot;;
-}
-</programlisting>
-  <para>
-    defines a configuration with three option definitions that together
-    enable the Apache HTTP Server with <literal>/webroot</literal> as
-    the document root.
-  </para>
-  <para>
-    Sets can be nested, and in fact dots in option names are shorthand
-    for defining a set containing another set. For instance,
-    <xref linkend="opt-services.httpd.enable" /> defines a set named
-    <literal>services</literal> that contains a set named
-    <literal>httpd</literal>, which in turn contains an option
-    definition named <literal>enable</literal> with value
-    <literal>true</literal>. This means that the example above can also
-    be written as:
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ services = {
-    httpd = {
-      enable = true;
-      adminAddr = &quot;alice@example.org&quot;;
-      virtualHosts = {
-        localhost = {
-          documentRoot = &quot;/webroot&quot;;
-        };
-      };
-    };
-  };
-}
-</programlisting>
-  <para>
-    which may be more convenient if you have lots of option definitions
-    that share the same prefix (such as
-    <literal>services.httpd</literal>).
-  </para>
-  <para>
-    NixOS checks your option definitions for correctness. For instance,
-    if you try to define an option that doesn’t exist (that is, doesn’t
-    have a corresponding <emphasis>option declaration</emphasis>),
-    <literal>nixos-rebuild</literal> will give an error like:
-  </para>
-  <programlisting>
-The option `services.httpd.enable' defined in `/etc/nixos/configuration.nix' does not exist.
-</programlisting>
-  <para>
-    Likewise, values in option definitions must have a correct type. For
-    instance, <literal>services.httpd.enable</literal> must be a Boolean
-    (<literal>true</literal> or <literal>false</literal>). Trying to
-    give it a value of another type, such as a string, will cause an
-    error:
-  </para>
-  <programlisting>
-The option value `services.httpd.enable' in `/etc/nixos/configuration.nix' is not a boolean.
-</programlisting>
-  <para>
-    Options have various types of values. The most important are:
-  </para>
-  <variablelist>
-    <varlistentry>
-      <term>
-        Strings
-      </term>
-      <listitem>
-        <para>
-          Strings are enclosed in double quotes, e.g.
-        </para>
-        <programlisting language="bash">
-networking.hostName = &quot;dexter&quot;;
-</programlisting>
-        <para>
-          Special characters can be escaped by prefixing them with a
-          backslash (e.g. <literal>\&quot;</literal>).
-        </para>
-        <para>
-          Multi-line strings can be enclosed in <emphasis>double single
-          quotes</emphasis>, e.g.
-        </para>
-        <programlisting language="bash">
-networking.extraHosts =
-  ''
-    127.0.0.2 other-localhost
-    10.0.0.1 server
-  '';
-</programlisting>
-        <para>
-          The main difference is that it strips from each line a number
-          of spaces equal to the minimal indentation of the string as a
-          whole (disregarding the indentation of empty lines), and that
-          characters like <literal>&quot;</literal> and
-          <literal>\</literal> are not special (making it more
-          convenient for including things like shell code). See more
-          info about this in the Nix manual
-          <link xlink:href="https://nixos.org/nix/manual/#ssec-values">here</link>.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        Booleans
-      </term>
-      <listitem>
-        <para>
-          These can be <literal>true</literal> or
-          <literal>false</literal>, e.g.
-        </para>
-        <programlisting language="bash">
-networking.firewall.enable = true;
-networking.firewall.allowPing = false;
-</programlisting>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        Integers
-      </term>
-      <listitem>
-        <para>
-          For example,
-        </para>
-        <programlisting language="bash">
-boot.kernel.sysctl.&quot;net.ipv4.tcp_keepalive_time&quot; = 60;
-</programlisting>
-        <para>
-          (Note that here the attribute name
-          <literal>net.ipv4.tcp_keepalive_time</literal> is enclosed in
-          quotes to prevent it from being interpreted as a set named
-          <literal>net</literal> containing a set named
-          <literal>ipv4</literal>, and so on. This is because it’s not a
-          NixOS option but the literal name of a Linux kernel setting.)
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        Sets
-      </term>
-      <listitem>
-        <para>
-          Sets were introduced above. They are name/value pairs enclosed
-          in braces, as in the option definition
-        </para>
-        <programlisting language="bash">
-fileSystems.&quot;/boot&quot; =
-  { device = &quot;/dev/sda1&quot;;
-    fsType = &quot;ext4&quot;;
-    options = [ &quot;rw&quot; &quot;data=ordered&quot; &quot;relatime&quot; ];
-  };
-</programlisting>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        Lists
-      </term>
-      <listitem>
-        <para>
-          The important thing to note about lists is that list elements
-          are separated by whitespace, like this:
-        </para>
-        <programlisting language="bash">
-boot.kernelModules = [ &quot;fuse&quot; &quot;kvm-intel&quot; &quot;coretemp&quot; ];
-</programlisting>
-        <para>
-          List elements can be any other type, e.g. sets:
-        </para>
-        <programlisting language="bash">
-swapDevices = [ { device = &quot;/dev/disk/by-label/swap&quot;; } ];
-</programlisting>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        Packages
-      </term>
-      <listitem>
-        <para>
-          Usually, the packages you need are already part of the Nix
-          Packages collection, which is a set that can be accessed
-          through the function argument <literal>pkgs</literal>. Typical
-          uses:
-        </para>
-        <programlisting language="bash">
-environment.systemPackages =
-  [ pkgs.thunderbird
-    pkgs.emacs
-  ];
-
-services.postgresql.package = pkgs.postgresql_10;
-</programlisting>
-        <para>
-          The latter option definition changes the default PostgreSQL
-          package used by NixOS’s PostgreSQL service to 10.x. For more
-          information on packages, including how to add new ones, see
-          <xref linkend="sec-custom-packages" />.
-        </para>
-      </listitem>
-    </varlistentry>
-  </variablelist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
deleted file mode 100644
index 01446e53e38f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-configuration-syntax">
-  <title>Configuration Syntax</title>
-  <para>
-    The NixOS configuration file
-    <literal>/etc/nixos/configuration.nix</literal> is actually a
-    <emphasis>Nix expression</emphasis>, which is the Nix package
-    manager’s purely functional language for describing how to build
-    packages and configurations. This means you have all the expressive
-    power of that language at your disposal, including the ability to
-    abstract over common patterns, which is very useful when managing
-    complex systems. The syntax and semantics of the Nix language are
-    fully described in the
-    <link xlink:href="https://nixos.org/nix/manual/#chap-writing-nix-expressions">Nix
-    manual</link>, but here we give a short overview of the most
-    important constructs useful in NixOS configuration files.
-  </para>
-  <xi:include href="config-file.section.xml" />
-  <xi:include href="abstractions.section.xml" />
-  <xi:include href="modularity.section.xml" />
-  <xi:include href="summary.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/customizing-packages.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/customizing-packages.section.xml
deleted file mode 100644
index f78b5dc5460c..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/customizing-packages.section.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-customising-packages">
-  <title>Customising Packages</title>
-  <para>
-    Some packages in Nixpkgs have options to enable or disable optional
-    functionality or change other aspects of the package. For instance,
-    the Firefox wrapper package (which provides Firefox with a set of
-    plugins such as the Adobe Flash player) has an option to enable the
-    Google Talk plugin. It can be set in
-    <literal>configuration.nix</literal> as follows:
-    <literal>nixpkgs.config.firefox.enableGoogleTalkPlugin = true;</literal>
-  </para>
-  <warning>
-    <para>
-      Unfortunately, Nixpkgs currently lacks a way to query available
-      configuration options.
-    </para>
-  </warning>
-  <para>
-    Apart from high-level options, it’s possible to tweak a package in
-    almost arbitrary ways, such as changing or disabling dependencies of
-    a package. For instance, the Emacs package in Nixpkgs by default has
-    a dependency on GTK 2. If you want to build it against GTK 3, you
-    can specify that as follows:
-  </para>
-  <programlisting language="bash">
-environment.systemPackages = [ (pkgs.emacs.override { gtk = pkgs.gtk3; }) ];
-</programlisting>
-  <para>
-    The function <literal>override</literal> performs the call to the
-    Nix function that produces Emacs, with the original arguments
-    amended by the set of arguments specified by you. So here the
-    function argument <literal>gtk</literal> gets the value
-    <literal>pkgs.gtk3</literal>, causing Emacs to depend on GTK 3. (The
-    parentheses are necessary because in Nix, function application binds
-    more weakly than list construction, so without them,
-    <xref linkend="opt-environment.systemPackages" /> would be a list
-    with two elements.)
-  </para>
-  <para>
-    Even greater customisation is possible using the function
-    <literal>overrideAttrs</literal>. While the
-    <literal>override</literal> mechanism above overrides the arguments
-    of a package function, <literal>overrideAttrs</literal> allows
-    changing the <emphasis>attributes</emphasis> passed to
-    <literal>mkDerivation</literal>. This permits changing any aspect of
-    the package, such as the source code. For instance, if you want to
-    override the source code of Emacs, you can say:
-  </para>
-  <programlisting language="bash">
-environment.systemPackages = [
-  (pkgs.emacs.overrideAttrs (oldAttrs: {
-    name = &quot;emacs-25.0-pre&quot;;
-    src = /path/to/my/emacs/tree;
-  }))
-];
-</programlisting>
-  <para>
-    Here, <literal>overrideAttrs</literal> takes the Nix derivation
-    specified by <literal>pkgs.emacs</literal> and produces a new
-    derivation in which the original’s <literal>name</literal> and
-    <literal>src</literal> attribute have been replaced by the given
-    values by re-calling <literal>stdenv.mkDerivation</literal>. The
-    original attributes are accessible via the function argument, which
-    is conventionally named <literal>oldAttrs</literal>.
-  </para>
-  <para>
-    The overrides shown above are not global. They do not affect the
-    original package; other packages in Nixpkgs continue to depend on
-    the original rather than the customised package. This means that if
-    another package in your system depends on the original package, you
-    end up with two instances of the package. If you want to have
-    everything depend on your customised instance, you can apply a
-    <emphasis>global</emphasis> override as follows:
-  </para>
-  <programlisting language="bash">
-nixpkgs.config.packageOverrides = pkgs:
-  { emacs = pkgs.emacs.override { gtk = pkgs.gtk3; };
-  };
-</programlisting>
-  <para>
-    The effect of this definition is essentially equivalent to modifying
-    the <literal>emacs</literal> attribute in the Nixpkgs source tree.
-    Any package in Nixpkgs that depends on <literal>emacs</literal> will
-    be passed your customised instance. (However, the value
-    <literal>pkgs.emacs</literal> in
-    <literal>nixpkgs.config.packageOverrides</literal> refers to the
-    original rather than overridden instance, to prevent an infinite
-    recursion.)
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml
deleted file mode 100644
index da31f18d9233..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-declarative-package-mgmt">
-  <title>Declarative Package Management</title>
-  <para>
-    With declarative package management, you specify which packages you
-    want on your system by setting the option
-    <xref linkend="opt-environment.systemPackages" />. For instance,
-    adding the following line to <literal>configuration.nix</literal>
-    enables the Mozilla Thunderbird email application:
-  </para>
-  <programlisting language="bash">
-environment.systemPackages = [ pkgs.thunderbird ];
-</programlisting>
-  <para>
-    The effect of this specification is that the Thunderbird package
-    from Nixpkgs will be built or downloaded as part of the system when
-    you run <literal>nixos-rebuild switch</literal>.
-  </para>
-  <note>
-    <para>
-      Some packages require additional global configuration such as
-      D-Bus or systemd service registration so adding them to
-      <xref linkend="opt-environment.systemPackages" /> might not be
-      sufficient. You are advised to check the
-      <link linkend="ch-options">list of options</link> whether a NixOS
-      module for the package does not exist.
-    </para>
-  </note>
-  <para>
-    You can get a list of the available packages as follows:
-  </para>
-  <programlisting>
-$ nix-env -qaP '*' --description
-nixos.firefox   firefox-23.0   Mozilla Firefox - the browser, reloaded
-...
-</programlisting>
-  <para>
-    The first column in the output is the <emphasis>attribute
-    name</emphasis>, such as <literal>nixos.thunderbird</literal>.
-  </para>
-  <para>
-    Note: the <literal>nixos</literal> prefix tells us that we want to
-    get the package from the <literal>nixos</literal> channel and works
-    only in CLI tools. In declarative configuration use
-    <literal>pkgs</literal> prefix (variable).
-  </para>
-  <para>
-    To <quote>uninstall</quote> a package, simply remove it from
-    <xref linkend="opt-environment.systemPackages" /> and run
-    <literal>nixos-rebuild switch</literal>.
-  </para>
-  <xi:include href="customizing-packages.section.xml" />
-  <xi:include href="adding-custom-packages.section.xml" />
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml
deleted file mode 100644
index 71441d8b4a5b..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-file-systems">
-  <title>File Systems</title>
-  <para>
-    You can define file systems using the <literal>fileSystems</literal>
-    configuration option. For instance, the following definition causes
-    NixOS to mount the Ext4 file system on device
-    <literal>/dev/disk/by-label/data</literal> onto the mount point
-    <literal>/data</literal>:
-  </para>
-  <programlisting language="bash">
-fileSystems.&quot;/data&quot; =
-  { device = &quot;/dev/disk/by-label/data&quot;;
-    fsType = &quot;ext4&quot;;
-  };
-</programlisting>
-  <para>
-    This will create an entry in <literal>/etc/fstab</literal>, which
-    will generate a corresponding
-    <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.mount.html">systemd.mount</link>
-    unit via
-    <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html">systemd-fstab-generator</link>.
-    The filesystem will be mounted automatically unless
-    <literal>&quot;noauto&quot;</literal> is present in
-    <link linkend="opt-fileSystems._name_.options">options</link>.
-    <literal>&quot;noauto&quot;</literal> filesystems can be mounted
-    explicitly using <literal>systemctl</literal> e.g.
-    <literal>systemctl start data.mount</literal>. Mount points are
-    created automatically if they don’t already exist. For
-    <literal>device</literal>, it’s best to use the topology-independent
-    device aliases in <literal>/dev/disk/by-label</literal> and
-    <literal>/dev/disk/by-uuid</literal>, as these don’t change if the
-    topology changes (e.g. if a disk is moved to another IDE
-    controller).
-  </para>
-  <para>
-    You can usually omit the file system type
-    (<literal>fsType</literal>), since <literal>mount</literal> can
-    usually detect the type and load the necessary kernel module
-    automatically. However, if the file system is needed at early boot
-    (in the initial ramdisk) and is not <literal>ext2</literal>,
-    <literal>ext3</literal> or <literal>ext4</literal>, then it’s best
-    to specify <literal>fsType</literal> to ensure that the kernel
-    module is available.
-  </para>
-  <note>
-    <para>
-      System startup will fail if any of the filesystems fails to mount,
-      dropping you to the emergency shell. You can make a mount
-      asynchronous and non-critical by adding
-      <literal>options = [ &quot;nofail&quot; ];</literal>.
-    </para>
-  </note>
-  <xi:include href="luks-file-systems.section.xml" />
-  <xi:include href="sshfs-file-systems.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/firewall.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/firewall.section.xml
deleted file mode 100644
index 24c19bb1c66d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/firewall.section.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-firewall">
-  <title>Firewall</title>
-  <para>
-    NixOS has a simple stateful firewall that blocks incoming
-    connections and other unexpected packets. The firewall applies to
-    both IPv4 and IPv6 traffic. It is enabled by default. It can be
-    disabled as follows:
-  </para>
-  <programlisting language="bash">
-networking.firewall.enable = false;
-</programlisting>
-  <para>
-    If the firewall is enabled, you can open specific TCP ports to the
-    outside world:
-  </para>
-  <programlisting language="bash">
-networking.firewall.allowedTCPPorts = [ 80 443 ];
-</programlisting>
-  <para>
-    Note that TCP port 22 (ssh) is opened automatically if the SSH
-    daemon is enabled
-    (<literal>services.openssh.enable = true</literal>). UDP ports can
-    be opened through
-    <xref linkend="opt-networking.firewall.allowedUDPPorts" />.
-  </para>
-  <para>
-    To open ranges of TCP ports:
-  </para>
-  <programlisting language="bash">
-networking.firewall.allowedTCPPortRanges = [
-  { from = 4000; to = 4007; }
-  { from = 8000; to = 8010; }
-];
-</programlisting>
-  <para>
-    Similarly, UDP port ranges can be opened through
-    <xref linkend="opt-networking.firewall.allowedUDPPortRanges" />.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml
deleted file mode 100644
index cc559a1933d9..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml
+++ /dev/null
@@ -1,239 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-gpu-accel">
-  <title>GPU acceleration</title>
-  <para>
-    NixOS provides various APIs that benefit from GPU hardware
-    acceleration, such as VA-API and VDPAU for video playback; OpenGL
-    and Vulkan for 3D graphics; and OpenCL for general-purpose
-    computing. This chapter describes how to set up GPU hardware
-    acceleration (as far as this is not done automatically) and how to
-    verify that hardware acceleration is indeed used.
-  </para>
-  <para>
-    Most of the aforementioned APIs are agnostic with regards to which
-    display server is used. Consequently, these instructions should
-    apply both to the X Window System and Wayland compositors.
-  </para>
-  <section xml:id="sec-gpu-accel-opencl">
-    <title>OpenCL</title>
-    <para>
-      <link xlink:href="https://en.wikipedia.org/wiki/OpenCL">OpenCL</link>
-      is a general compute API. It is used by various applications such
-      as Blender and Darktable to accelerate certain operations.
-    </para>
-    <para>
-      OpenCL applications load drivers through the <emphasis>Installable
-      Client Driver</emphasis> (ICD) mechanism. In this mechanism, an
-      ICD file specifies the path to the OpenCL driver for a particular
-      GPU family. In NixOS, there are two ways to make ICD files visible
-      to the ICD loader. The first is through the
-      <literal>OCL_ICD_VENDORS</literal> environment variable. This
-      variable can contain a directory which is scanned by the ICL
-      loader for ICD files. For example:
-    </para>
-    <programlisting>
-$ export \
-  OCL_ICD_VENDORS=`nix-build '&lt;nixpkgs&gt;' --no-out-link -A rocm-opencl-icd`/etc/OpenCL/vendors/
-</programlisting>
-    <para>
-      The second mechanism is to add the OpenCL driver package to
-      <xref linkend="opt-hardware.opengl.extraPackages" />. This links
-      the ICD file under <literal>/run/opengl-driver</literal>, where it
-      will be visible to the ICD loader.
-    </para>
-    <para>
-      The proper installation of OpenCL drivers can be verified through
-      the <literal>clinfo</literal> command of the clinfo package. This
-      command will report the number of hardware devices that is found
-      and give detailed information for each device:
-    </para>
-    <programlisting>
-$ clinfo | head -n3
-Number of platforms  1
-Platform Name        AMD Accelerated Parallel Processing
-Platform Vendor      Advanced Micro Devices, Inc.
-</programlisting>
-    <section xml:id="sec-gpu-accel-opencl-amd">
-      <title>AMD</title>
-      <para>
-        Modern AMD
-        <link xlink:href="https://en.wikipedia.org/wiki/Graphics_Core_Next">Graphics
-        Core Next</link> (GCN) GPUs are supported through the
-        rocm-opencl-icd package. Adding this package to
-        <xref linkend="opt-hardware.opengl.extraPackages" /> enables
-        OpenCL support:
-      </para>
-      <programlisting language="bash">
-hardware.opengl.extraPackages = [
-  rocm-opencl-icd
-];
-</programlisting>
-    </section>
-    <section xml:id="sec-gpu-accel-opencl-intel">
-      <title>Intel</title>
-      <para>
-        <link xlink:href="https://en.wikipedia.org/wiki/List_of_Intel_graphics_processing_units#Gen8">Intel
-        Gen8 and later GPUs</link> are supported by the Intel NEO OpenCL
-        runtime that is provided by the intel-compute-runtime package.
-        For Gen7 GPUs, the deprecated Beignet runtime can be used, which
-        is provided by the beignet package. The proprietary Intel OpenCL
-        runtime, in the intel-ocl package, is an alternative for Gen7
-        GPUs.
-      </para>
-      <para>
-        The intel-compute-runtime, beignet, or intel-ocl package can be
-        added to <xref linkend="opt-hardware.opengl.extraPackages" /> to
-        enable OpenCL support. For example, for Gen8 and later GPUs, the
-        following configuration can be used:
-      </para>
-      <programlisting language="bash">
-hardware.opengl.extraPackages = [
-  intel-compute-runtime
-];
-</programlisting>
-    </section>
-  </section>
-  <section xml:id="sec-gpu-accel-vulkan">
-    <title>Vulkan</title>
-    <para>
-      <link xlink:href="https://en.wikipedia.org/wiki/Vulkan_(API)">Vulkan</link>
-      is a graphics and compute API for GPUs. It is used directly by
-      games or indirectly though compatibility layers like
-      <link xlink:href="https://github.com/doitsujin/dxvk/wiki">DXVK</link>.
-    </para>
-    <para>
-      By default, if <xref linkend="opt-hardware.opengl.driSupport" />
-      is enabled, mesa is installed and provides Vulkan for supported
-      hardware.
-    </para>
-    <para>
-      Similar to OpenCL, Vulkan drivers are loaded through the
-      <emphasis>Installable Client Driver</emphasis> (ICD) mechanism.
-      ICD files for Vulkan are JSON files that specify the path to the
-      driver library and the supported Vulkan version. All successfully
-      loaded drivers are exposed to the application as different GPUs.
-      In NixOS, there are two ways to make ICD files visible to Vulkan
-      applications: an environment variable and a module option.
-    </para>
-    <para>
-      The first option is through the
-      <literal>VK_ICD_FILENAMES</literal> environment variable. This
-      variable can contain multiple JSON files, separated by
-      <literal>:</literal>. For example:
-    </para>
-    <programlisting>
-$ export \
-  VK_ICD_FILENAMES=`nix-build '&lt;nixpkgs&gt;' --no-out-link -A amdvlk`/share/vulkan/icd.d/amd_icd64.json
-</programlisting>
-    <para>
-      The second mechanism is to add the Vulkan driver package to
-      <xref linkend="opt-hardware.opengl.extraPackages" />. This links
-      the ICD file under <literal>/run/opengl-driver</literal>, where it
-      will be visible to the ICD loader.
-    </para>
-    <para>
-      The proper installation of Vulkan drivers can be verified through
-      the <literal>vulkaninfo</literal> command of the vulkan-tools
-      package. This command will report the hardware devices and drivers
-      found, in this example output amdvlk and radv:
-    </para>
-    <programlisting>
-$ vulkaninfo | grep GPU
-                GPU id  : 0 (Unknown AMD GPU)
-                GPU id  : 1 (AMD RADV NAVI10 (LLVM 9.0.1))
-     ...
-GPU0:
-        deviceType     = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
-        deviceName     = Unknown AMD GPU
-GPU1:
-        deviceType     = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
-</programlisting>
-    <para>
-      A simple graphical application that uses Vulkan is
-      <literal>vkcube</literal> from the vulkan-tools package.
-    </para>
-    <section xml:id="sec-gpu-accel-vulkan-amd">
-      <title>AMD</title>
-      <para>
-        Modern AMD
-        <link xlink:href="https://en.wikipedia.org/wiki/Graphics_Core_Next">Graphics
-        Core Next</link> (GCN) GPUs are supported through either radv,
-        which is part of mesa, or the amdvlk package. Adding the amdvlk
-        package to <xref linkend="opt-hardware.opengl.extraPackages" />
-        makes amdvlk the default driver and hides radv and lavapipe from
-        the device list. A specific driver can be forced as follows:
-      </para>
-      <programlisting language="bash">
-hardware.opengl.extraPackages = [
-  pkgs.amdvlk
-];
-
-# To enable Vulkan support for 32-bit applications, also add:
-hardware.opengl.extraPackages32 = [
-  pkgs.driversi686Linux.amdvlk
-];
-
-# Force radv
-environment.variables.AMD_VULKAN_ICD = &quot;RADV&quot;;
-# Or
-environment.variables.VK_ICD_FILENAMES =
-  &quot;/run/opengl-driver/share/vulkan/icd.d/radeon_icd.x86_64.json&quot;;
-</programlisting>
-    </section>
-  </section>
-  <section xml:id="sec-gpu-accel-common-issues">
-    <title>Common issues</title>
-    <section xml:id="sec-gpu-accel-common-issues-permissions">
-      <title>User permissions</title>
-      <para>
-        Except where noted explicitly, it should not be necessary to
-        adjust user permissions to use these acceleration APIs. In the
-        default configuration, GPU devices have world-read/write
-        permissions (<literal>/dev/dri/renderD*</literal>) or are tagged
-        as <literal>uaccess</literal>
-        (<literal>/dev/dri/card*</literal>). The access control lists of
-        devices with the <literal>uaccess</literal> tag will be updated
-        automatically when a user logs in through
-        <literal>systemd-logind</literal>. For example, if the user
-        <emphasis>alice</emphasis> is logged in, the access control list
-        should look as follows:
-      </para>
-      <programlisting>
-$ getfacl /dev/dri/card0
-# file: dev/dri/card0
-# owner: root
-# group: video
-user::rw-
-user:alice:rw-
-group::rw-
-mask::rw-
-other::---
-</programlisting>
-      <para>
-        If you disabled (this functionality of)
-        <literal>systemd-logind</literal>, you may need to add the user
-        to the <literal>video</literal> group and log in again.
-      </para>
-    </section>
-    <section xml:id="sec-gpu-accel-common-issues-mixing-nixpkgs">
-      <title>Mixing different versions of nixpkgs</title>
-      <para>
-        The <emphasis>Installable Client Driver</emphasis> (ICD)
-        mechanism used by OpenCL and Vulkan loads runtimes into its
-        address space using <literal>dlopen</literal>. Mixing an ICD
-        loader mechanism and runtimes from different version of nixpkgs
-        may not work. For example, if the ICD loader uses an older
-        version of glibc than the runtime, the runtime may not be
-        loadable due to missing symbols. Unfortunately, the loader will
-        generally be quiet about such issues.
-      </para>
-      <para>
-        If you suspect that you are running into library version
-        mismatches between an ICL loader and a runtime, you could run an
-        application with the <literal>LD_DEBUG</literal> variable set to
-        get more diagnostic information. For example, OpenCL can be
-        tested with <literal>LD_DEBUG=files clinfo</literal>, which
-        should report missing symbols.
-      </para>
-    </section>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/ipv4-config.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/ipv4-config.section.xml
deleted file mode 100644
index 047ba2165f07..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/ipv4-config.section.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-ipv4">
-  <title>IPv4 Configuration</title>
-  <para>
-    By default, NixOS uses DHCP (specifically,
-    <literal>dhcpcd</literal>) to automatically configure network
-    interfaces. However, you can configure an interface manually as
-    follows:
-  </para>
-  <programlisting language="bash">
-networking.interfaces.eth0.ipv4.addresses = [ {
-  address = &quot;192.168.1.2&quot;;
-  prefixLength = 24;
-} ];
-</programlisting>
-  <para>
-    Typically you’ll also want to set a default gateway and set of name
-    servers:
-  </para>
-  <programlisting language="bash">
-networking.defaultGateway = &quot;192.168.1.1&quot;;
-networking.nameservers = [ &quot;8.8.8.8&quot; ];
-</programlisting>
-  <note>
-    <para>
-      Statically configured interfaces are set up by the systemd service
-      <literal>interface-name-cfg.service</literal>. The default gateway
-      and name server configuration is performed by
-      <literal>network-setup.service</literal>.
-    </para>
-  </note>
-  <para>
-    The host name is set using
-    <xref linkend="opt-networking.hostName" />:
-  </para>
-  <programlisting language="bash">
-networking.hostName = &quot;cartman&quot;;
-</programlisting>
-  <para>
-    The default host name is <literal>nixos</literal>. Set it to the
-    empty string (<literal>&quot;&quot;</literal>) to allow the DHCP
-    server to provide the host name.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/ipv6-config.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/ipv6-config.section.xml
deleted file mode 100644
index 137c3d772a86..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/ipv6-config.section.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-ipv6">
-  <title>IPv6 Configuration</title>
-  <para>
-    IPv6 is enabled by default. Stateless address autoconfiguration is
-    used to automatically assign IPv6 addresses to all interfaces, and
-    Privacy Extensions (RFC 4946) are enabled by default. You can adjust
-    the default for this by setting
-    <xref linkend="opt-networking.tempAddresses" />. This option may be
-    overridden on a per-interface basis by
-    <xref linkend="opt-networking.interfaces._name_.tempAddress" />. You
-    can disable IPv6 support globally by setting:
-  </para>
-  <programlisting language="bash">
-networking.enableIPv6 = false;
-</programlisting>
-  <para>
-    You can disable IPv6 on a single interface using a normal sysctl (in
-    this example, we use interface <literal>eth0</literal>):
-  </para>
-  <programlisting language="bash">
-boot.kernel.sysctl.&quot;net.ipv6.conf.eth0.disable_ipv6&quot; = true;
-</programlisting>
-  <para>
-    As with IPv4 networking interfaces are automatically configured via
-    DHCPv6. You can configure an interface manually:
-  </para>
-  <programlisting language="bash">
-networking.interfaces.eth0.ipv6.addresses = [ {
-  address = &quot;fe00:aa:bb:cc::2&quot;;
-  prefixLength = 64;
-} ];
-</programlisting>
-  <para>
-    For configuring a gateway, optionally with explicitly specified
-    interface:
-  </para>
-  <programlisting language="bash">
-networking.defaultGateway6 = {
-  address = &quot;fe00::1&quot;;
-  interface = &quot;enp0s3&quot;;
-};
-</programlisting>
-  <para>
-    See <xref linkend="sec-ipv4" /> for similar examples and additional
-    information.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml
deleted file mode 100644
index 83a50d7c49d1..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml
+++ /dev/null
@@ -1,126 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-kubernetes">
-  <title>Kubernetes</title>
-  <para>
-    The NixOS Kubernetes module is a collective term for a handful of
-    individual submodules implementing the Kubernetes cluster
-    components.
-  </para>
-  <para>
-    There are generally two ways of enabling Kubernetes on NixOS. One
-    way is to enable and configure cluster components appropriately by
-    hand:
-  </para>
-  <programlisting language="bash">
-services.kubernetes = {
-  apiserver.enable = true;
-  controllerManager.enable = true;
-  scheduler.enable = true;
-  addonManager.enable = true;
-  proxy.enable = true;
-  flannel.enable = true;
-};
-</programlisting>
-  <para>
-    Another way is to assign cluster roles (&quot;master&quot; and/or
-    &quot;node&quot;) to the host. This enables apiserver,
-    controllerManager, scheduler, addonManager, kube-proxy and etcd:
-  </para>
-  <programlisting language="bash">
-services.kubernetes.roles = [ &quot;master&quot; ];
-</programlisting>
-  <para>
-    While this will enable the kubelet and kube-proxy only:
-  </para>
-  <programlisting language="bash">
-services.kubernetes.roles = [ &quot;node&quot; ];
-</programlisting>
-  <para>
-    Assigning both the master and node roles is usable if you want a
-    single node Kubernetes cluster for dev or testing purposes:
-  </para>
-  <programlisting language="bash">
-services.kubernetes.roles = [ &quot;master&quot; &quot;node&quot; ];
-</programlisting>
-  <para>
-    Note: Assigning either role will also default both
-    <xref linkend="opt-services.kubernetes.flannel.enable" /> and
-    <xref linkend="opt-services.kubernetes.easyCerts" /> to true. This
-    sets up flannel as CNI and activates automatic PKI bootstrapping.
-  </para>
-  <para>
-    As of kubernetes 1.10.X it has been deprecated to open
-    non-tls-enabled ports on kubernetes components. Thus, from NixOS
-    19.03 all plain HTTP ports have been disabled by default. While
-    opening insecure ports is still possible, it is recommended not to
-    bind these to other interfaces than loopback. To re-enable the
-    insecure port on the apiserver, see options:
-    <xref linkend="opt-services.kubernetes.apiserver.insecurePort" />
-    and
-    <xref linkend="opt-services.kubernetes.apiserver.insecureBindAddress" />
-  </para>
-  <note>
-    <para>
-      As of NixOS 19.03, it is mandatory to configure:
-      <xref linkend="opt-services.kubernetes.masterAddress" />. The
-      masterAddress must be resolveable and routeable by all cluster
-      nodes. In single node clusters, this can be set to
-      <literal>localhost</literal>.
-    </para>
-  </note>
-  <para>
-    Role-based access control (RBAC) authorization mode is enabled by
-    default. This means that anonymous requests to the apiserver secure
-    port will expectedly cause a permission denied error. All cluster
-    components must therefore be configured with x509 certificates for
-    two-way tls communication. The x509 certificate subject section
-    determines the roles and permissions granted by the apiserver to
-    perform clusterwide or namespaced operations. See also:
-    <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/">
-    Using RBAC Authorization</link>.
-  </para>
-  <para>
-    The NixOS kubernetes module provides an option for automatic
-    certificate bootstrapping and configuration,
-    <xref linkend="opt-services.kubernetes.easyCerts" />. The PKI
-    bootstrapping process involves setting up a certificate authority
-    (CA) daemon (cfssl) on the kubernetes master node. cfssl generates a
-    CA-cert for the cluster, and uses the CA-cert for signing
-    subordinate certs issued to each of the cluster components.
-    Subsequently, the certmgr daemon monitors active certificates and
-    renews them when needed. For single node Kubernetes clusters,
-    setting <xref linkend="opt-services.kubernetes.easyCerts" /> = true
-    is sufficient and no further action is required. For joining extra
-    node machines to an existing cluster on the other hand, establishing
-    initial trust is mandatory.
-  </para>
-  <para>
-    To add new nodes to the cluster: On any (non-master) cluster node
-    where <xref linkend="opt-services.kubernetes.easyCerts" /> is
-    enabled, the helper script
-    <literal>nixos-kubernetes-node-join</literal> is available on PATH.
-    Given a token on stdin, it will copy the token to the kubernetes
-    secrets directory and restart the certmgr service. As requested
-    certificates are issued, the script will restart kubernetes cluster
-    components as needed for them to pick up new keypairs.
-  </para>
-  <note>
-    <para>
-      Multi-master (HA) clusters are not supported by the easyCerts
-      module.
-    </para>
-  </note>
-  <para>
-    In order to interact with an RBAC-enabled cluster as an
-    administrator, one needs to have cluster-admin privileges. By
-    default, when easyCerts is enabled, a cluster-admin kubeconfig file
-    is generated and linked into
-    <literal>/etc/kubernetes/cluster-admin.kubeconfig</literal> as
-    determined by
-    <xref linkend="opt-services.kubernetes.pki.etcClusterAdminKubeconfig" />.
-    <literal>export KUBECONFIG=/etc/kubernetes/cluster-admin.kubeconfig</literal>
-    will make kubectl use this kubeconfig to access and authenticate the
-    cluster. The cluster-admin kubeconfig references an auto-generated
-    keypair owned by root. Thus, only root on the kubernetes master may
-    obtain cluster-admin rights by means of this file.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
deleted file mode 100644
index a1d6815af29c..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
+++ /dev/null
@@ -1,157 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-kernel-config">
-  <title>Linux Kernel</title>
-  <para>
-    You can override the Linux kernel and associated packages using the
-    option <literal>boot.kernelPackages</literal>. For instance, this
-    selects the Linux 3.10 kernel:
-  </para>
-  <programlisting language="bash">
-boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_10;
-</programlisting>
-  <para>
-    Note that this not only replaces the kernel, but also packages that
-    are specific to the kernel version, such as the NVIDIA video
-    drivers. This ensures that driver packages are consistent with the
-    kernel.
-  </para>
-  <para>
-    While <literal>pkgs.linuxKernel.packages</literal> contains all
-    available kernel packages, you may want to use one of the
-    unversioned <literal>pkgs.linuxPackages_*</literal> aliases such as
-    <literal>pkgs.linuxPackages_latest</literal>, that are kept up to
-    date with new versions.
-  </para>
-  <para>
-    The default Linux kernel configuration should be fine for most
-    users. You can see the configuration of your current kernel with the
-    following command:
-  </para>
-  <programlisting>
-zcat /proc/config.gz
-</programlisting>
-  <para>
-    If you want to change the kernel configuration, you can use the
-    <literal>packageOverrides</literal> feature (see
-    <xref linkend="sec-customising-packages" />). For instance, to
-    enable support for the kernel debugger KGDB:
-  </para>
-  <programlisting language="bash">
-nixpkgs.config.packageOverrides = pkgs: pkgs.lib.recursiveUpdate pkgs {
-  linuxKernel.kernels.linux_5_10 = pkgs.linuxKernel.kernels.linux_5_10.override {
-    extraConfig = ''
-      KGDB y
-    '';
-  };
-};
-</programlisting>
-  <para>
-    <literal>extraConfig</literal> takes a list of Linux kernel
-    configuration options, one per line. The name of the option should
-    not include the prefix <literal>CONFIG_</literal>. The option value
-    is typically <literal>y</literal>, <literal>n</literal> or
-    <literal>m</literal> (to build something as a kernel module).
-  </para>
-  <para>
-    Kernel modules for hardware devices are generally loaded
-    automatically by <literal>udev</literal>. You can force a module to
-    be loaded via <xref linkend="opt-boot.kernelModules" />, e.g.
-  </para>
-  <programlisting language="bash">
-boot.kernelModules = [ &quot;fuse&quot; &quot;kvm-intel&quot; &quot;coretemp&quot; ];
-</programlisting>
-  <para>
-    If the module is required early during the boot (e.g. to mount the
-    root file system), you can use
-    <xref linkend="opt-boot.initrd.kernelModules" />:
-  </para>
-  <programlisting language="bash">
-boot.initrd.kernelModules = [ &quot;cifs&quot; ];
-</programlisting>
-  <para>
-    This causes the specified modules and their dependencies to be added
-    to the initial ramdisk.
-  </para>
-  <para>
-    Kernel runtime parameters can be set through
-    <xref linkend="opt-boot.kernel.sysctl" />, e.g.
-  </para>
-  <programlisting language="bash">
-boot.kernel.sysctl.&quot;net.ipv4.tcp_keepalive_time&quot; = 120;
-</programlisting>
-  <para>
-    sets the kernel’s TCP keepalive time to 120 seconds. To see the
-    available parameters, run <literal>sysctl -a</literal>.
-  </para>
-  <section xml:id="sec-linux-config-customizing">
-    <title>Customize your kernel</title>
-    <para>
-      The first step before compiling the kernel is to generate an
-      appropriate <literal>.config</literal> configuration. Either you
-      pass your own config via the <literal>configfile</literal> setting
-      of <literal>linuxKernel.manualConfig</literal>:
-    </para>
-    <programlisting language="bash">
-custom-kernel = let base_kernel = linuxKernel.kernels.linux_4_9;
-  in super.linuxKernel.manualConfig {
-    inherit (super) stdenv hostPlatform;
-    inherit (base_kernel) src;
-    version = &quot;${base_kernel.version}-custom&quot;;
-
-    configfile = /home/me/my_kernel_config;
-    allowImportFromDerivation = true;
-};
-</programlisting>
-    <para>
-      You can edit the config with this snippet (by default
-      <literal>make menuconfig</literal> won't work out of the box on
-      nixos):
-    </para>
-    <programlisting>
-nix-shell -E 'with import &lt;nixpkgs&gt; {}; kernelToOverride.overrideAttrs (o: {nativeBuildInputs=o.nativeBuildInputs ++ [ pkg-config ncurses ];})'
-</programlisting>
-    <para>
-      or you can let nixpkgs generate the configuration. Nixpkgs
-      generates it via answering the interactive kernel utility
-      <literal>make config</literal>. The answers depend on parameters
-      passed to
-      <literal>pkgs/os-specific/linux/kernel/generic.nix</literal>
-      (which you can influence by overriding
-      <literal>extraConfig, autoModules, modDirVersion, preferBuiltin, extraConfig</literal>).
-    </para>
-    <programlisting language="bash">
-mptcp93.override ({
-  name=&quot;mptcp-local&quot;;
-
-  ignoreConfigErrors = true;
-  autoModules = false;
-  kernelPreferBuiltin = true;
-
-  enableParallelBuilding = true;
-
-  extraConfig = ''
-    DEBUG_KERNEL y
-    FRAME_POINTER y
-    KGDB y
-    KGDB_SERIAL_CONSOLE y
-    DEBUG_INFO y
-  '';
-});
-</programlisting>
-  </section>
-  <section xml:id="sec-linux-config-developing-modules">
-    <title>Developing kernel modules</title>
-    <para>
-      When developing kernel modules it's often convenient to run
-      edit-compile-run loop as quickly as possible. See below snippet as
-      an example of developing <literal>mellanox</literal> drivers.
-    </para>
-    <programlisting>
-$ nix-build '&lt;nixpkgs&gt;' -A linuxPackages.kernel.dev
-$ nix-shell '&lt;nixpkgs&gt;' -A linuxPackages.kernel
-$ unpackPhase
-$ cd linux-*
-$ make -C $dev/lib/modules/*/build M=$(pwd)/drivers/net/ethernet/mellanox modules
-# insmod ./drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.ko
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/luks-file-systems.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/luks-file-systems.section.xml
deleted file mode 100644
index 42b766eba98b..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/luks-file-systems.section.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-luks-file-systems">
-  <title>LUKS-Encrypted File Systems</title>
-  <para>
-    NixOS supports file systems that are encrypted using
-    <emphasis>LUKS</emphasis> (Linux Unified Key Setup). For example,
-    here is how you create an encrypted Ext4 file system on the device
-    <literal>/dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d</literal>:
-  </para>
-  <programlisting>
-# cryptsetup luksFormat /dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d
-
-WARNING!
-========
-This will overwrite data on /dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d irrevocably.
-
-Are you sure? (Type uppercase yes): YES
-Enter LUKS passphrase: ***
-Verify passphrase: ***
-
-# cryptsetup luksOpen /dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d crypted
-Enter passphrase for /dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d: ***
-
-# mkfs.ext4 /dev/mapper/crypted
-</programlisting>
-  <para>
-    The LUKS volume should be automatically picked up by
-    <literal>nixos-generate-config</literal>, but you might want to
-    verify that your <literal>hardware-configuration.nix</literal> looks
-    correct. To manually ensure that the system is automatically mounted
-    at boot time as <literal>/</literal>, add the following to
-    <literal>configuration.nix</literal>:
-  </para>
-  <programlisting language="bash">
-boot.initrd.luks.devices.crypted.device = &quot;/dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d&quot;;
-fileSystems.&quot;/&quot;.device = &quot;/dev/mapper/crypted&quot;;
-</programlisting>
-  <para>
-    Should grub be used as bootloader, and <literal>/boot</literal> is
-    located on an encrypted partition, it is necessary to add the
-    following grub option:
-  </para>
-  <programlisting language="bash">
-boot.loader.grub.enableCryptodisk = true;
-</programlisting>
-  <section xml:id="sec-luks-file-systems-fido2">
-    <title>FIDO2</title>
-    <para>
-      NixOS also supports unlocking your LUKS-Encrypted file system
-      using a FIDO2 compatible token. In the following example, we will
-      create a new FIDO2 credential and add it as a new key to our
-      existing device <literal>/dev/sda2</literal>:
-    </para>
-    <programlisting>
-# export FIDO2_LABEL=&quot;/dev/sda2 @ $HOSTNAME&quot;
-# fido2luks credential &quot;$FIDO2_LABEL&quot;
-f1d00200108b9d6e849a8b388da457688e3dd653b4e53770012d8f28e5d3b269865038c346802f36f3da7278b13ad6a3bb6a1452e24ebeeaa24ba40eef559b1b287d2a2f80b7
-
-# fido2luks -i add-key /dev/sda2 f1d00200108b9d6e849a8b388da457688e3dd653b4e53770012d8f28e5d3b269865038c346802f36f3da7278b13ad6a3bb6a1452e24ebeeaa24ba40eef559b1b287d2a2f80b7
-Password:
-Password (again):
-Old password:
-Old password (again):
-Added to key to device /dev/sda2, slot: 2
-</programlisting>
-    <para>
-      To ensure that this file system is decrypted using the FIDO2
-      compatible key, add the following to
-      <literal>configuration.nix</literal>:
-    </para>
-    <programlisting language="bash">
-boot.initrd.luks.fido2Support = true;
-boot.initrd.luks.devices.&quot;/dev/sda2&quot;.fido2.credential = &quot;f1d00200108b9d6e849a8b388da457688e3dd653b4e53770012d8f28e5d3b269865038c346802f36f3da7278b13ad6a3bb6a1452e24ebeeaa24ba40eef559b1b287d2a2f80b7&quot;;
-</programlisting>
-    <para>
-      You can also use the FIDO2 passwordless setup, but for security
-      reasons, you might want to enable it only when your device is PIN
-      protected, such as
-      <link xlink:href="https://trezor.io/">Trezor</link>.
-    </para>
-    <programlisting language="bash">
-boot.initrd.luks.devices.&quot;/dev/sda2&quot;.fido2.passwordLess = true;
-</programlisting>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/modularity.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/modularity.section.xml
deleted file mode 100644
index a7688090fcc5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/modularity.section.xml
+++ /dev/null
@@ -1,152 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-modularity">
-  <title>Modularity</title>
-  <para>
-    The NixOS configuration mechanism is modular. If your
-    <literal>configuration.nix</literal> becomes too big, you can split
-    it into multiple files. Likewise, if you have multiple NixOS
-    configurations (e.g. for different computers) with some commonality,
-    you can move the common configuration into a shared file.
-  </para>
-  <para>
-    Modules have exactly the same syntax as
-    <literal>configuration.nix</literal>. In fact,
-    <literal>configuration.nix</literal> is itself a module. You can use
-    other modules by including them from
-    <literal>configuration.nix</literal>, e.g.:
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ imports = [ ./vpn.nix ./kde.nix ];
-  services.httpd.enable = true;
-  environment.systemPackages = [ pkgs.emacs ];
-  ...
-}
-</programlisting>
-  <para>
-    Here, we include two modules from the same directory,
-    <literal>vpn.nix</literal> and <literal>kde.nix</literal>. The
-    latter might look like this:
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ services.xserver.enable = true;
-  services.xserver.displayManager.sddm.enable = true;
-  services.xserver.desktopManager.plasma5.enable = true;
-  environment.systemPackages = [ pkgs.vim ];
-}
-</programlisting>
-  <para>
-    Note that both <literal>configuration.nix</literal> and
-    <literal>kde.nix</literal> define the option
-    <xref linkend="opt-environment.systemPackages" />. When multiple
-    modules define an option, NixOS will try to
-    <emphasis>merge</emphasis> the definitions. In the case of
-    <xref linkend="opt-environment.systemPackages" />, that’s easy: the
-    lists of packages can simply be concatenated. The value in
-    <literal>configuration.nix</literal> is merged last, so for
-    list-type options, it will appear at the end of the merged list. If
-    you want it to appear first, you can use
-    <literal>mkBefore</literal>:
-  </para>
-  <programlisting language="bash">
-boot.kernelModules = mkBefore [ &quot;kvm-intel&quot; ];
-</programlisting>
-  <para>
-    This causes the <literal>kvm-intel</literal> kernel module to be
-    loaded before any other kernel modules.
-  </para>
-  <para>
-    For other types of options, a merge may not be possible. For
-    instance, if two modules define
-    <xref linkend="opt-services.httpd.adminAddr" />,
-    <literal>nixos-rebuild</literal> will give an error:
-  </para>
-  <programlisting>
-The unique option `services.httpd.adminAddr' is defined multiple times, in `/etc/nixos/httpd.nix' and `/etc/nixos/configuration.nix'.
-</programlisting>
-  <para>
-    When that happens, it’s possible to force one definition take
-    precedence over the others:
-  </para>
-  <programlisting language="bash">
-services.httpd.adminAddr = pkgs.lib.mkForce &quot;bob@example.org&quot;;
-</programlisting>
-  <para>
-    When using multiple modules, you may need to access configuration
-    values defined in other modules. This is what the
-    <literal>config</literal> function argument is for: it contains the
-    complete, merged system configuration. That is,
-    <literal>config</literal> is the result of combining the
-    configurations returned by every module <footnote>
-      <para>
-        If you’re wondering how it’s possible that the (indirect)
-        <emphasis>result</emphasis> of a function is passed as an
-        <emphasis>input</emphasis> to that same function: that’s because
-        Nix is a <quote>lazy</quote> language — it only computes values
-        when they are needed. This works as long as no individual
-        configuration value depends on itself.
-      </para>
-    </footnote> . For example, here is a module that adds some packages
-    to <xref linkend="opt-environment.systemPackages" /> only if
-    <xref linkend="opt-services.xserver.enable" /> is set to
-    <literal>true</literal> somewhere else:
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ environment.systemPackages =
-    if config.services.xserver.enable then
-      [ pkgs.firefox
-        pkgs.thunderbird
-      ]
-    else
-      [ ];
-}
-</programlisting>
-  <para>
-    With multiple modules, it may not be obvious what the final value of
-    a configuration option is. The command
-    <literal>nixos-option</literal> allows you to find out:
-  </para>
-  <programlisting>
-$ nixos-option services.xserver.enable
-true
-
-$ nixos-option boot.kernelModules
-[ &quot;tun&quot; &quot;ipv6&quot; &quot;loop&quot; ... ]
-</programlisting>
-  <para>
-    Interactive exploration of the configuration is possible using
-    <literal>nix repl</literal>, a read-eval-print loop for Nix
-    expressions. A typical use:
-  </para>
-  <programlisting>
-$ nix repl '&lt;nixpkgs/nixos&gt;'
-
-nix-repl&gt; config.networking.hostName
-&quot;mandark&quot;
-
-nix-repl&gt; map (x: x.hostName) config.services.httpd.virtualHosts
-[ &quot;example.org&quot; &quot;example.gov&quot; ]
-</programlisting>
-  <para>
-    While abstracting your configuration, you may find it useful to
-    generate modules using code, instead of writing files. The example
-    below would have the same effect as importing a file which sets
-    those options.
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-let netConfig = hostName: {
-  networking.hostName = hostName;
-  networking.useDHCP = false;
-};
-
-in
-
-{ imports = [ (netConfig &quot;nixos.localdomain&quot;) ]; }
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/network-manager.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/network-manager.section.xml
deleted file mode 100644
index 8f0d6d680ae0..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/network-manager.section.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-networkmanager">
-  <title>NetworkManager</title>
-  <para>
-    To facilitate network configuration, some desktop environments use
-    NetworkManager. You can enable NetworkManager by setting:
-  </para>
-  <programlisting language="bash">
-networking.networkmanager.enable = true;
-</programlisting>
-  <para>
-    some desktop managers (e.g., GNOME) enable NetworkManager
-    automatically for you.
-  </para>
-  <para>
-    All users that should have permission to change network settings
-    must belong to the <literal>networkmanager</literal> group:
-  </para>
-  <programlisting language="bash">
-users.users.alice.extraGroups = [ &quot;networkmanager&quot; ];
-</programlisting>
-  <para>
-    NetworkManager is controlled using either <literal>nmcli</literal>
-    or <literal>nmtui</literal> (curses-based terminal user interface).
-    See their manual pages for details on their usage. Some desktop
-    environments (GNOME, KDE) have their own configuration tools for
-    NetworkManager. On XFCE, there is no configuration tool for
-    NetworkManager by default: by enabling
-    <xref linkend="opt-programs.nm-applet.enable" />, the graphical
-    applet will be installed and will launch automatically when the
-    graphical session is started.
-  </para>
-  <note>
-    <para>
-      <literal>networking.networkmanager</literal> and
-      <literal>networking.wireless</literal> (WPA Supplicant) can be
-      used together if desired. To do this you need to instruct
-      NetworkManager to ignore those interfaces like:
-    </para>
-    <programlisting language="bash">
-networking.networkmanager.unmanaged = [
-   &quot;*&quot; &quot;except:type:wwan&quot; &quot;except:type:gsm&quot;
-];
-</programlisting>
-    <para>
-      Refer to the option description for the exact syntax and
-      references to external documentation.
-    </para>
-  </note>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/networking.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/networking.chapter.xml
deleted file mode 100644
index 2ed86ea3b589..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/networking.chapter.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-networking">
-  <title>Networking</title>
-  <para>
-    This section describes how to configure networking components on
-    your NixOS machine.
-  </para>
-  <xi:include href="network-manager.section.xml" />
-  <xi:include href="ssh.section.xml" />
-  <xi:include href="ipv4-config.section.xml" />
-  <xi:include href="ipv6-config.section.xml" />
-  <xi:include href="firewall.section.xml" />
-  <xi:include href="wireless.section.xml" />
-  <xi:include href="ad-hoc-network-config.section.xml" />
-  <xi:include href="renaming-interfaces.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml
deleted file mode 100644
index d3727edbe08d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-package-management">
-  <title>Package Management</title>
-  <para>
-    This section describes how to add additional packages to your
-    system. NixOS has two distinct styles of package management:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <emphasis>Declarative</emphasis>, where you declare what
-        packages you want in your <literal>configuration.nix</literal>.
-        Every time you run <literal>nixos-rebuild</literal>, NixOS will
-        ensure that you get a consistent set of binaries corresponding
-        to your specification.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <emphasis>Ad hoc</emphasis>, where you install, upgrade and
-        uninstall packages via the <literal>nix-env</literal> command.
-        This style allows mixing packages from different Nixpkgs
-        versions. It’s the only choice for non-root users.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <xi:include href="declarative-packages.section.xml" />
-  <xi:include href="ad-hoc-packages.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles.chapter.xml
deleted file mode 100644
index 6f5fc130c6a0..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles.chapter.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-profiles">
-  <title>Profiles</title>
-  <para>
-    In some cases, it may be desirable to take advantage of
-    commonly-used, predefined configurations provided by nixpkgs, but
-    different from those that come as default. This is a role fulfilled
-    by NixOS's Profiles, which come as files living in
-    <literal>&lt;nixpkgs/nixos/modules/profiles&gt;</literal>. That is
-    to say, expected usage is to add them to the imports list of your
-    <literal>/etc/configuration.nix</literal> as such:
-  </para>
-  <programlisting language="bash">
-imports = [
-  &lt;nixpkgs/nixos/modules/profiles/profile-name.nix&gt;
-];
-</programlisting>
-  <para>
-    Even if some of these profiles seem only useful in the context of
-    install media, many are actually intended to be used in real
-    installs.
-  </para>
-  <para>
-    What follows is a brief explanation on the purpose and use-case for
-    each profile. Detailing each option configured by each one is out of
-    scope.
-  </para>
-  <xi:include href="profiles/all-hardware.section.xml" />
-  <xi:include href="profiles/base.section.xml" />
-  <xi:include href="profiles/clone-config.section.xml" />
-  <xi:include href="profiles/demo.section.xml" />
-  <xi:include href="profiles/docker-container.section.xml" />
-  <xi:include href="profiles/graphical.section.xml" />
-  <xi:include href="profiles/hardened.section.xml" />
-  <xi:include href="profiles/headless.section.xml" />
-  <xi:include href="profiles/installation-device.section.xml" />
-  <xi:include href="profiles/minimal.section.xml" />
-  <xi:include href="profiles/qemu-guest.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/all-hardware.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/all-hardware.section.xml
deleted file mode 100644
index 43ac5edea7f8..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/all-hardware.section.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-all-hardware">
-  <title>All Hardware</title>
-  <para>
-    Enables all hardware supported by NixOS: i.e., all firmware is
-    included, and all devices from which one may boot are enabled in the
-    initrd. Its primary use is in the NixOS installation CDs.
-  </para>
-  <para>
-    The enabled kernel modules include support for SATA and PATA, SCSI
-    (partially), USB, Firewire (untested), Virtio (QEMU, KVM, etc.),
-    VMware, and Hyper-V. Additionally,
-    <xref linkend="opt-hardware.enableAllFirmware" /> is enabled, and
-    the firmware for the ZyDAS ZD1211 chipset is specifically installed.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/base.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/base.section.xml
deleted file mode 100644
index 83d35bd28676..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/base.section.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-base">
-  <title>Base</title>
-  <para>
-    Defines the software packages included in the <quote>minimal</quote>
-    installation CD. It installs several utilities useful in a simple
-    recovery or install media, such as a text-mode web browser, and
-    tools for manipulating block devices, networking, hardware
-    diagnostics, and filesystems (with their respective kernel modules).
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/clone-config.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/clone-config.section.xml
deleted file mode 100644
index 9430b49ea33d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/clone-config.section.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-clone-config">
-  <title>Clone Config</title>
-  <para>
-    This profile is used in installer images. It provides an editable
-    configuration.nix that imports all the modules that were also used
-    when creating the image in the first place. As a result it allows
-    users to edit and rebuild the live-system.
-  </para>
-  <para>
-    On images where the installation media also becomes an installation
-    target, copying over <literal>configuration.nix</literal> should be
-    disabled by setting <literal>installer.cloneConfig</literal> to
-    <literal>false</literal>. For example, this is done in
-    <literal>sd-image-aarch64-installer.nix</literal>.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/demo.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/demo.section.xml
deleted file mode 100644
index 09c2680a1067..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/demo.section.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-demo">
-  <title>Demo</title>
-  <para>
-    This profile just enables a <literal>demo</literal> user, with
-    password <literal>demo</literal>, uid <literal>1000</literal>,
-    <literal>wheel</literal> group and
-    <link linkend="opt-services.xserver.displayManager.autoLogin">autologin
-    in the SDDM display manager</link>.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/docker-container.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/docker-container.section.xml
deleted file mode 100644
index 97c2a92dcab5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/docker-container.section.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-docker-container">
-  <title>Docker Container</title>
-  <para>
-    This is the profile from which the Docker images are generated. It
-    prepares a working system by importing the
-    <link linkend="sec-profile-minimal">Minimal</link> and
-    <link linkend="sec-profile-clone-config">Clone Config</link>
-    profiles, and setting appropriate configuration options that are
-    useful inside a container context, like
-    <xref linkend="opt-boot.isContainer" />.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/graphical.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/graphical.section.xml
deleted file mode 100644
index 1b109519d436..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/graphical.section.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-graphical">
-  <title>Graphical</title>
-  <para>
-    Defines a NixOS configuration with the Plasma 5 desktop. It’s used
-    by the graphical installation CD.
-  </para>
-  <para>
-    It sets <xref linkend="opt-services.xserver.enable" />,
-    <xref linkend="opt-services.xserver.displayManager.sddm.enable" />,
-    <xref linkend="opt-services.xserver.desktopManager.plasma5.enable" />,
-    and <xref linkend="opt-services.xserver.libinput.enable" /> to true.
-    It also includes glxinfo and firefox in the system packages list.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml
deleted file mode 100644
index 44c11786d940..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-hardened">
-  <title>Hardened</title>
-  <para>
-    A profile with most (vanilla) hardening options enabled by default,
-    potentially at the cost of stability, features and performance.
-  </para>
-  <para>
-    This includes a hardened kernel, and limiting the system information
-    available to processes through the <literal>/sys</literal> and
-    <literal>/proc</literal> filesystems. It also disables the User
-    Namespaces feature of the kernel, which stops Nix from being able to
-    build anything (this particular setting can be overriden via
-    <xref linkend="opt-security.allowUserNamespaces" />). See the
-    <link xlink:href="https://github.com/nixos/nixpkgs/tree/master/nixos/modules/profiles/hardened.nix">profile
-    source</link> for further detail on which settings are altered.
-  </para>
-  <warning>
-    <para>
-      This profile enables options that are known to affect system
-      stability. If you experience any stability issues when using the
-      profile, try disabling it. If you report an issue and use this
-      profile, always mention that you do.
-    </para>
-  </warning>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/headless.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/headless.section.xml
deleted file mode 100644
index 0910b9ffaad2..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/headless.section.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-headless">
-  <title>Headless</title>
-  <para>
-    Common configuration for headless machines (e.g., Amazon EC2
-    instances).
-  </para>
-  <para>
-    Disables <link linkend="opt-sound.enable">sound</link>,
-    <link linkend="opt-boot.vesa">vesa</link>, serial consoles,
-    <link linkend="opt-systemd.enableEmergencyMode">emergency
-    mode</link>, <link linkend="opt-boot.loader.grub.splashImage">grub
-    splash images</link> and configures the kernel to reboot
-    automatically on panic.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/installation-device.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/installation-device.section.xml
deleted file mode 100644
index 837e69df06e1..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/installation-device.section.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-installation-device">
-  <title>Installation Device</title>
-  <para>
-    Provides a basic configuration for installation devices like CDs.
-    This enables redistributable firmware, includes the
-    <link linkend="sec-profile-clone-config">Clone Config profile</link>
-    and a copy of the Nixpkgs channel, so
-    <literal>nixos-install</literal> works out of the box.
-  </para>
-  <para>
-    Documentation for
-    <link linkend="opt-documentation.enable">Nixpkgs</link> and
-    <link linkend="opt-documentation.nixos.enable">NixOS</link> are
-    forcefully enabled (to override the
-    <link linkend="sec-profile-minimal">Minimal profile</link>
-    preference); the NixOS manual is shown automatically on TTY 8,
-    udisks is disabled. Autologin is enabled as <literal>nixos</literal>
-    user, while passwordless login as both <literal>root</literal> and
-    <literal>nixos</literal> is possible. Passwordless
-    <literal>sudo</literal> is enabled too.
-    <link linkend="opt-networking.wireless.enable">wpa_supplicant</link>
-    is enabled, but configured to not autostart.
-  </para>
-  <para>
-    It is explained how to login, start the ssh server, and if
-    available, how to start the display manager.
-  </para>
-  <para>
-    Several settings are tweaked so that the installer has a better
-    chance of succeeding under low-memory environments.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/minimal.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/minimal.section.xml
deleted file mode 100644
index a3fe30357dff..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/minimal.section.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-minimal">
-  <title>Minimal</title>
-  <para>
-    This profile defines a small NixOS configuration. It does not
-    contain any graphical stuff. It’s a very short file that enables
-    <link linkend="opt-environment.noXlibs">noXlibs</link>, sets
-    <xref linkend="opt-i18n.supportedLocales" /> to only support the
-    user-selected locale,
-    <link linkend="opt-documentation.enable">disables packages’
-    documentation</link>, and <link linkend="opt-sound.enable">disables
-    sound</link>.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/qemu-guest.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/qemu-guest.section.xml
deleted file mode 100644
index f33464f9db4d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/profiles/qemu-guest.section.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-profile-qemu-guest">
-  <title>QEMU Guest</title>
-  <para>
-    This profile contains common configuration for virtual machines
-    running under QEMU (using virtio).
-  </para>
-  <para>
-    It makes virtio modules available on the initrd and sets the system
-    time from the hardware clock to work around a bug in qemu-kvm.
-  </para>
-</section>
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
deleted file mode 100644
index 88c9e624c82f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-rename-ifs">
-  <title>Renaming network interfaces</title>
-  <para>
-    NixOS uses the udev
-    <link xlink:href="https://systemd.io/PREDICTABLE_INTERFACE_NAMES/">predictable
-    naming scheme</link> to assign names to network interfaces. This
-    means that by default cards are not given the traditional names like
-    <literal>eth0</literal> or <literal>eth1</literal>, whose order can
-    change unpredictably across reboots. Instead, relying on physical
-    locations and firmware information, the scheme produces names like
-    <literal>ens1</literal>, <literal>enp2s0</literal>, etc.
-  </para>
-  <para>
-    These names are predictable but less memorable and not necessarily
-    stable: for example installing new hardware or changing firmware
-    settings can result in a
-    <link xlink:href="https://github.com/systemd/systemd/issues/3715#issue-165347602">name
-    change</link>. If this is undesirable, for example if you have a
-    single ethernet card, you can revert to the traditional scheme by
-    setting
-    <xref linkend="opt-networking.usePredictableInterfaceNames" /> to
-    <literal>false</literal>.
-  </para>
-  <section xml:id="sec-custom-ifnames">
-    <title>Assigning custom names</title>
-    <para>
-      In case there are multiple interfaces of the same type, it’s
-      better to assign custom names based on the device hardware
-      address. For example, we assign the name <literal>wan</literal> to
-      the interface with MAC address
-      <literal>52:54:00:12:01:01</literal> using a netword link unit:
-    </para>
-    <programlisting language="bash">
-systemd.network.links.&quot;10-wan&quot; = {
-  matchConfig.PermanentMACAddress = &quot;52:54:00:12:01:01&quot;;
-  linkConfig.Name = &quot;wan&quot;;
-};
-</programlisting>
-    <para>
-      Note that links are directly read by udev, <emphasis>not
-      networkd</emphasis>, and will work even if networkd is disabled.
-    </para>
-    <para>
-      Alternatively, we can use a plain old udev rule:
-    </para>
-    <programlisting language="bash">
-services.udev.initrdRules = ''
-  SUBSYSTEM==&quot;net&quot;, ACTION==&quot;add&quot;, DRIVERS==&quot;?*&quot;, \
-  ATTR{address}==&quot;52:54:00:12:01:01&quot;, KERNEL==&quot;eth*&quot;, NAME=&quot;wan&quot;
-'';
-</programlisting>
-    <warning>
-      <para>
-        The rule must be installed in the initrd using
-        <literal>services.udev.initrdRules</literal>, not the usual
-        <literal>services.udev.extraRules</literal> option. This is to
-        avoid race conditions with other programs controlling the
-        interface.
-      </para>
-    </warning>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/ssh.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/ssh.section.xml
deleted file mode 100644
index 037418d8ea4d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/ssh.section.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-ssh">
-  <title>Secure Shell Access</title>
-  <para>
-    Secure shell (SSH) access to your machine can be enabled by setting:
-  </para>
-  <programlisting language="bash">
-services.openssh.enable = true;
-</programlisting>
-  <para>
-    By default, root logins using a password are disallowed. They can be
-    disabled entirely by setting
-    <xref linkend="opt-services.openssh.permitRootLogin" /> to
-    <literal>&quot;no&quot;</literal>.
-  </para>
-  <para>
-    You can declaratively specify authorised RSA/DSA public keys for a
-    user as follows:
-  </para>
-  <programlisting language="bash">
-users.users.alice.openssh.authorizedKeys.keys =
-  [ &quot;ssh-dss AAAAB3NzaC1kc3MAAACBAPIkGWVEt4...&quot; ];
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/sshfs-file-systems.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/sshfs-file-systems.section.xml
deleted file mode 100644
index 5d74712f35dc..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/sshfs-file-systems.section.xml
+++ /dev/null
@@ -1,139 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-sshfs-file-systems">
-  <title>SSHFS File Systems</title>
-  <para>
-    <link xlink:href="https://github.com/libfuse/sshfs">SSHFS</link> is
-    a
-    <link xlink:href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</link>
-    filesystem that allows easy access to directories on a remote
-    machine using the SSH File Transfer Protocol (SFTP). It means that
-    if you have SSH access to a machine, no additional setup is needed
-    to mount a directory.
-  </para>
-  <section xml:id="sec-sshfs-interactive">
-    <title>Interactive mounting</title>
-    <para>
-      In NixOS, SSHFS is packaged as <package>sshfs</package>. Once
-      installed, mounting a directory interactively is simple as
-      running:
-    </para>
-    <programlisting>
-$ sshfs my-user@example.com:/my-dir /mnt/my-dir
-</programlisting>
-    <para>
-      Like any other FUSE file system, the directory is unmounted using:
-    </para>
-    <programlisting>
-$ fusermount -u /mnt/my-dir
-</programlisting>
-  </section>
-  <section xml:id="sec-sshfs-non-interactive">
-    <title>Non-interactive mounting</title>
-    <para>
-      Mounting non-interactively requires some precautions because
-      <literal>sshfs</literal> will run at boot and under a different
-      user (root). For obvious reason, you can’t input a password, so
-      public key authentication using an unencrypted key is needed. To
-      create a new key without a passphrase you can do:
-    </para>
-    <programlisting>
-$ ssh-keygen -t ed25519 -P '' -f example-key
-Generating public/private ed25519 key pair.
-Your identification has been saved in test-key
-Your public key has been saved in test-key.pub
-The key fingerprint is:
-SHA256:yjxl3UbTn31fLWeyLYTAKYJPRmzknjQZoyG8gSNEoIE my-user@workstation
-</programlisting>
-    <para>
-      To keep the key safe, change the ownership to
-      <literal>root:root</literal> and make sure the permissions are
-      <literal>600</literal>: OpenSSH normally refuses to use the key if
-      it’s not well-protected.
-    </para>
-    <para>
-      The file system can be configured in NixOS via the usual
-      <link linkend="opt-fileSystems">fileSystems</link> option. Here’s
-      a typical setup:
-    </para>
-    <programlisting language="bash">
-{
-  system.fsPackages = [ pkgs.sshfs ];
-
-  fileSystems.&quot;/mnt/my-dir&quot; = {
-    device = &quot;my-user@example.com:/my-dir/&quot;;
-    fsType = &quot;sshfs&quot;;
-    options =
-      [ # Filesystem options
-        &quot;allow_other&quot;          # for non-root access
-        &quot;_netdev&quot;              # this is a network fs
-        &quot;x-systemd.automount&quot;  # mount on demand
-
-        # SSH options
-        &quot;reconnect&quot;              # handle connection drops
-        &quot;ServerAliveInterval=15&quot; # keep connections alive
-        &quot;IdentityFile=/var/secrets/example-key&quot;
-      ];
-  };
-}
-</programlisting>
-    <para>
-      More options from <literal>ssh_config(5)</literal> can be given as
-      well, for example you can change the default SSH port or specify a
-      jump proxy:
-    </para>
-    <programlisting language="bash">
-{
-  options =
-    [ &quot;ProxyJump=bastion@example.com&quot;
-      &quot;Port=22&quot;
-    ];
-}
-</programlisting>
-    <para>
-      It’s also possible to change the <literal>ssh</literal> command
-      used by SSHFS to connect to the server. For example:
-    </para>
-    <programlisting language="bash">
-{
-  options =
-    [ (builtins.replaceStrings [&quot; &quot;] [&quot;\\040&quot;]
-        &quot;ssh_command=${pkgs.openssh}/bin/ssh -v -L 8080:localhost:80&quot;)
-    ];
-
-}
-</programlisting>
-    <note>
-      <para>
-        The escaping of spaces is needed because every option is written
-        to the <literal>/etc/fstab</literal> file, which is a
-        space-separated table.
-      </para>
-    </note>
-    <section xml:id="sec-sshfs-troubleshooting">
-      <title>Troubleshooting</title>
-      <para>
-        If you’re having a hard time figuring out why mounting is
-        failing, you can add the option
-        <literal>&quot;debug&quot;</literal>. This enables a verbose log
-        in SSHFS that you can access via:
-      </para>
-      <programlisting>
-$ journalctl -u $(systemd-escape -p /mnt/my-dir/).mount
-Jun 22 11:41:18 workstation mount[87790]: SSHFS version 3.7.1
-Jun 22 11:41:18 workstation mount[87793]: executing &lt;ssh&gt; &lt;-x&gt; &lt;-a&gt; &lt;-oClearAllForwardings=yes&gt; &lt;-oServerAliveInterval=15&gt; &lt;-oIdentityFile=/var/secrets/wrong-key&gt; &lt;-2&gt; &lt;my-user@example.com&gt; &lt;-s&gt; &lt;sftp&gt;
-Jun 22 11:41:19 workstation mount[87793]: my-user@example.com: Permission denied (publickey).
-Jun 22 11:41:19 workstation mount[87790]: read: Connection reset by peer
-Jun 22 11:41:19 workstation systemd[1]: mnt-my\x2ddir.mount: Mount process exited, code=exited, status=1/FAILURE
-Jun 22 11:41:19 workstation systemd[1]: mnt-my\x2ddir.mount: Failed with result 'exit-code'.
-Jun 22 11:41:19 workstation systemd[1]: Failed to mount /mnt/my-dir.
-Jun 22 11:41:19 workstation systemd[1]: mnt-my\x2ddir.mount: Consumed 54ms CPU time, received 2.3K IP traffic, sent 2.7K IP traffic.
-</programlisting>
-      <note>
-        <para>
-          If the mount point contains special characters it needs to be
-          escaped using <literal>systemd-escape</literal>. This is due
-          to the way systemd converts paths into unit names.
-        </para>
-      </note>
-    </section>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/subversion.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/subversion.chapter.xml
deleted file mode 100644
index 794c2c34e399..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/subversion.chapter.xml
+++ /dev/null
@@ -1,121 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-subversion">
-  <title>Subversion</title>
-  <para>
-    <link xlink:href="https://subversion.apache.org/">Subversion</link>
-    is a centralized version-control system. It can use a
-    <link xlink:href="http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.choosing">variety
-    of protocols</link> for communication between client and server.
-  </para>
-  <section xml:id="module-services-subversion-apache-httpd">
-    <title>Subversion inside Apache HTTP</title>
-    <para>
-      This section focuses on configuring a web-based server on top of
-      the Apache HTTP server, which uses
-      <link xlink:href="http://www.webdav.org/">WebDAV</link>/<link xlink:href="http://www.webdav.org/deltav/WWW10/deltav-intro.htm">DeltaV</link>
-      for communication.
-    </para>
-    <para>
-      For more information on the general setup, please refer to the
-      <link xlink:href="http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.httpd">the
-      appropriate section of the Subversion book</link>.
-    </para>
-    <para>
-      To configure, include in
-      <literal>/etc/nixos/configuration.nix</literal> code to activate
-      Apache HTTP, setting
-      <xref linkend="opt-services.httpd.adminAddr" /> appropriately:
-    </para>
-    <programlisting language="bash">
-services.httpd.enable = true;
-services.httpd.adminAddr = ...;
-networking.firewall.allowedTCPPorts = [ 80 443 ];
-</programlisting>
-    <para>
-      For a simple Subversion server with basic authentication,
-      configure the Subversion module for Apache as follows, setting
-      <literal>hostName</literal> and <literal>documentRoot</literal>
-      appropriately, and <literal>SVNParentPath</literal> to the parent
-      directory of the repositories,
-      <literal>AuthzSVNAccessFile</literal> to the location of the
-      <literal>.authz</literal> file describing access permission, and
-      <literal>AuthUserFile</literal> to the password file.
-    </para>
-    <programlisting language="bash">
-services.httpd.extraModules = [
-    # note that order is *super* important here
-    { name = &quot;dav_svn&quot;; path = &quot;${pkgs.apacheHttpdPackages.subversion}/modules/mod_dav_svn.so&quot;; }
-    { name = &quot;authz_svn&quot;; path = &quot;${pkgs.apacheHttpdPackages.subversion}/modules/mod_authz_svn.so&quot;; }
-  ];
-  services.httpd.virtualHosts = {
-    &quot;svn&quot; = {
-       hostName = HOSTNAME;
-       documentRoot = DOCUMENTROOT;
-       locations.&quot;/svn&quot;.extraConfig = ''
-           DAV svn
-           SVNParentPath REPO_PARENT
-           AuthzSVNAccessFile ACCESS_FILE
-           AuthName &quot;SVN Repositories&quot;
-           AuthType Basic
-           AuthUserFile PASSWORD_FILE
-           Require valid-user
-      '';
-    }
-</programlisting>
-    <para>
-      The key <literal>&quot;svn&quot;</literal> is just a symbolic name
-      identifying the virtual host. The
-      <literal>&quot;/svn&quot;</literal> in
-      <literal>locations.&quot;/svn&quot;.extraConfig</literal> is the
-      path underneath which the repositories will be served.
-    </para>
-    <para>
-      <link xlink:href="https://wiki.archlinux.org/index.php/Subversion">This
-      page</link> explains how to set up the Subversion configuration
-      itself. This boils down to the following:
-    </para>
-    <para>
-      Underneath <literal>REPO_PARENT</literal> repositories can be set
-      up as follows:
-    </para>
-    <programlisting>
-$ svn create REPO_NAME
-</programlisting>
-    <para>
-      Repository files need to be accessible by
-      <literal>wwwrun</literal>:
-    </para>
-    <programlisting>
-$ chown -R wwwrun:wwwrun REPO_PARENT
-</programlisting>
-    <para>
-      The password file <literal>PASSWORD_FILE</literal> can be created
-      as follows:
-    </para>
-    <programlisting>
-$ htpasswd -cs PASSWORD_FILE USER_NAME
-</programlisting>
-    <para>
-      Additional users can be set up similarly, omitting the
-      <literal>c</literal> flag:
-    </para>
-    <programlisting>
-$ htpasswd -s PASSWORD_FILE USER_NAME
-</programlisting>
-    <para>
-      The file describing access permissions
-      <literal>ACCESS_FILE</literal> will look something like the
-      following:
-    </para>
-    <programlisting language="bash">
-[/]
-* = r
-
-[REPO_NAME:/]
-USER_NAME = rw
-</programlisting>
-    <para>
-      The Subversion repositories will be accessible as
-      <literal>http://HOSTNAME/svn/REPO_NAME</literal>.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/summary.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/summary.section.xml
deleted file mode 100644
index 96a178c4930e..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/summary.section.xml
+++ /dev/null
@@ -1,332 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-nix-syntax-summary">
-  <title>Syntax Summary</title>
-  <para>
-    Below is a summary of the most important syntactic constructs in the
-    Nix expression language. It’s not complete. In particular, there are
-    many other built-in functions. See the
-    <link xlink:href="https://nixos.org/nix/manual/#chap-writing-nix-expressions">Nix
-    manual</link> for the rest.
-  </para>
-  <informaltable>
-    <tgroup cols="2">
-      <colspec align="left" />
-      <colspec align="left" />
-      <thead>
-        <row>
-          <entry>
-            Example
-          </entry>
-          <entry>
-            Description
-          </entry>
-        </row>
-      </thead>
-      <tbody>
-        <row>
-          <entry>
-            <emphasis>Basic values</emphasis>
-          </entry>
-          <entry>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>&quot;Hello world&quot;</literal>
-          </entry>
-          <entry>
-            A string
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>&quot;${pkgs.bash}/bin/sh&quot;</literal>
-          </entry>
-          <entry>
-            A string containing an expression (expands to
-            <literal>&quot;/nix/store/hash-bash-version/bin/sh&quot;</literal>)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>true</literal>, <literal>false</literal>
-          </entry>
-          <entry>
-            Booleans
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>123</literal>
-          </entry>
-          <entry>
-            An integer
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>./foo.png</literal>
-          </entry>
-          <entry>
-            A path (relative to the containing Nix expression)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <emphasis>Compound values</emphasis>
-          </entry>
-          <entry>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x = 1; y = 2; }</literal>
-          </entry>
-          <entry>
-            A set with attributes named <literal>x</literal> and
-            <literal>y</literal>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ foo.bar = 1; }</literal>
-          </entry>
-          <entry>
-            A nested set, equivalent to
-            <literal>{ foo = { bar = 1; }; }</literal>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>rec { x = &quot;foo&quot;; y = x + &quot;bar&quot;; }</literal>
-          </entry>
-          <entry>
-            A recursive set, equivalent to
-            <literal>{ x = &quot;foo&quot;; y = &quot;foobar&quot;; }</literal>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>[ &quot;foo&quot; &quot;bar&quot; ]</literal>
-          </entry>
-          <entry>
-            A list with two elements
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <emphasis>Operators</emphasis>
-          </entry>
-          <entry>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>&quot;foo&quot; + &quot;bar&quot;</literal>
-          </entry>
-          <entry>
-            String concatenation
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>1 + 2</literal>
-          </entry>
-          <entry>
-            Integer addition
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>&quot;foo&quot; == &quot;f&quot; + &quot;oo&quot;</literal>
-          </entry>
-          <entry>
-            Equality test (evaluates to <literal>true</literal>)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>&quot;foo&quot; != &quot;bar&quot;</literal>
-          </entry>
-          <entry>
-            Inequality test (evaluates to <literal>true</literal>)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>!true</literal>
-          </entry>
-          <entry>
-            Boolean negation
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x = 1; y = 2; }.x</literal>
-          </entry>
-          <entry>
-            Attribute selection (evaluates to <literal>1</literal>)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x = 1; y = 2; }.z or 3</literal>
-          </entry>
-          <entry>
-            Attribute selection with default (evaluates to
-            <literal>3</literal>)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x = 1; y = 2; } // { z = 3; }</literal>
-          </entry>
-          <entry>
-            Merge two sets (attributes in the right-hand set taking
-            precedence)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <emphasis>Control structures</emphasis>
-          </entry>
-          <entry>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>if 1 + 1 == 2 then &quot;yes!&quot; else &quot;no!&quot;</literal>
-          </entry>
-          <entry>
-            Conditional expression
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>assert 1 + 1 == 2; &quot;yes!&quot;</literal>
-          </entry>
-          <entry>
-            Assertion check (evaluates to
-            <literal>&quot;yes!&quot;</literal>). See
-            <xref linkend="sec-assertions" /> for using assertions in
-            modules
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>let x = &quot;foo&quot;; y = &quot;bar&quot;; in x + y</literal>
-          </entry>
-          <entry>
-            Variable definition
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>with pkgs.lib; head [ 1 2 3 ]</literal>
-          </entry>
-          <entry>
-            Add all attributes from the given set to the scope
-            (evaluates to <literal>1</literal>)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <emphasis>Functions (lambdas)</emphasis>
-          </entry>
-          <entry>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>x: x + 1</literal>
-          </entry>
-          <entry>
-            A function that expects an integer and returns it increased
-            by 1
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>(x: x + 1) 100</literal>
-          </entry>
-          <entry>
-            A function call (evaluates to 101)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>let inc = x: x + 1; in inc (inc (inc 100))</literal>
-          </entry>
-          <entry>
-            A function bound to a variable and subsequently called by
-            name (evaluates to 103)
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x, y }: x + y</literal>
-          </entry>
-          <entry>
-            A function that expects a set with required attributes
-            <literal>x</literal> and <literal>y</literal> and
-            concatenates them
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x, y ? &quot;bar&quot; }: x + y</literal>
-          </entry>
-          <entry>
-            A function that expects a set with required attribute
-            <literal>x</literal> and optional <literal>y</literal>,
-            using <literal>&quot;bar&quot;</literal> as default value
-            for <literal>y</literal>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x, y, ... }: x + y</literal>
-          </entry>
-          <entry>
-            A function that expects a set with required attributes
-            <literal>x</literal> and <literal>y</literal> and ignores
-            any other attributes
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>{ x, y } @ args: x + y</literal>
-          </entry>
-          <entry>
-            A function that expects a set with required attributes
-            <literal>x</literal> and <literal>y</literal>, and binds the
-            whole set to <literal>args</literal>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <emphasis>Built-in functions</emphasis>
-          </entry>
-          <entry>
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>import ./foo.nix</literal>
-          </entry>
-          <entry>
-            Load and return Nix expression in given file
-          </entry>
-        </row>
-        <row>
-          <entry>
-            <literal>map (x: x + x) [ 1 2 3 ]</literal>
-          </entry>
-          <entry>
-            Apply a function to every element of a list (evaluates to
-            <literal>[ 2 4 6 ]</literal>)
-          </entry>
-        </row>
-      </tbody>
-    </tgroup>
-  </informaltable>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml
deleted file mode 100644
index 06492d5c2512..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-user-management">
-  <title>User Management</title>
-  <para>
-    NixOS supports both declarative and imperative styles of user
-    management. In the declarative style, users are specified in
-    <literal>configuration.nix</literal>. For instance, the following
-    states that a user account named <literal>alice</literal> shall
-    exist:
-  </para>
-  <programlisting language="bash">
-users.users.alice = {
-  isNormalUser = true;
-  home = &quot;/home/alice&quot;;
-  description = &quot;Alice Foobar&quot;;
-  extraGroups = [ &quot;wheel&quot; &quot;networkmanager&quot; ];
-  openssh.authorizedKeys.keys = [ &quot;ssh-dss AAAAB3Nza... alice@foobar&quot; ];
-};
-</programlisting>
-  <para>
-    Note that <literal>alice</literal> is a member of the
-    <literal>wheel</literal> and <literal>networkmanager</literal>
-    groups, which allows her to use <literal>sudo</literal> to execute
-    commands as <literal>root</literal> and to configure the network,
-    respectively. Also note the SSH public key that allows remote logins
-    with the corresponding private key. Users created in this way do not
-    have a password by default, so they cannot log in via mechanisms
-    that require a password. However, you can use the
-    <literal>passwd</literal> program to set a password, which is
-    retained across invocations of <literal>nixos-rebuild</literal>.
-  </para>
-  <para>
-    If you set <xref linkend="opt-users.mutableUsers" /> to false, then
-    the contents of <literal>/etc/passwd</literal> and
-    <literal>/etc/group</literal> will be congruent to your NixOS
-    configuration. For instance, if you remove a user from
-    <xref linkend="opt-users.users" /> and run nixos-rebuild, the user
-    account will cease to exist. Also, imperative commands for managing
-    users and groups, such as useradd, are no longer available.
-    Passwords may still be assigned by setting the user's
-    <link linkend="opt-users.users._name_.hashedPassword">hashedPassword</link>
-    option. A hashed password can be generated using
-    <literal>mkpasswd -m sha-512</literal>.
-  </para>
-  <para>
-    A user ID (uid) is assigned automatically. You can also specify a
-    uid manually by adding
-  </para>
-  <programlisting language="bash">
-uid = 1000;
-</programlisting>
-  <para>
-    to the user specification.
-  </para>
-  <para>
-    Groups can be specified similarly. The following states that a group
-    named <literal>students</literal> shall exist:
-  </para>
-  <programlisting language="bash">
-users.groups.students.gid = 1000;
-</programlisting>
-  <para>
-    As with users, the group ID (gid) is optional and will be assigned
-    automatically if it’s missing.
-  </para>
-  <para>
-    In the imperative style, users and groups are managed by commands
-    such as <literal>useradd</literal>, <literal>groupmod</literal> and
-    so on. For instance, to create a user account named
-    <literal>alice</literal>:
-  </para>
-  <programlisting>
-# useradd -m alice
-</programlisting>
-  <para>
-    To make all nix tools available to this new user use `su - USER`
-    which opens a login shell (==shell that loads the profile) for given
-    user. This will create the ~/.nix-defexpr symlink. So run:
-  </para>
-  <programlisting>
-# su - alice -c &quot;true&quot;
-</programlisting>
-  <para>
-    The flag <literal>-m</literal> causes the creation of a home
-    directory for the new user, which is generally what you want. The
-    user does not have an initial password and therefore cannot log in.
-    A password can be set using the <literal>passwd</literal> utility:
-  </para>
-  <programlisting>
-# passwd alice
-Enter new UNIX password: ***
-Retype new UNIX password: ***
-</programlisting>
-  <para>
-    A user can be deleted using <literal>userdel</literal>:
-  </para>
-  <programlisting>
-# userdel -r alice
-</programlisting>
-  <para>
-    The flag <literal>-r</literal> deletes the user’s home directory.
-    Accounts can be modified using <literal>usermod</literal>. Unix
-    groups can be managed using <literal>groupadd</literal>,
-    <literal>groupmod</literal> and <literal>groupdel</literal>.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/wayland.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/wayland.chapter.xml
deleted file mode 100644
index 1e90d4f31177..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/wayland.chapter.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-wayland">
-  <title>Wayland</title>
-  <para>
-    While X11 (see <xref linkend="sec-x11" />) is still the primary
-    display technology on NixOS, Wayland support is steadily improving.
-    Where X11 separates the X Server and the window manager, on Wayland
-    those are combined: a Wayland Compositor is like an X11 window
-    manager, but also embeds the Wayland 'Server' functionality. This
-    means it is sufficient to install a Wayland Compositor such as sway
-    without separately enabling a Wayland server:
-  </para>
-  <programlisting language="bash">
-programs.sway.enable = true;
-</programlisting>
-  <para>
-    This installs the sway compositor along with some essential
-    utilities. Now you can start sway from the TTY console.
-  </para>
-  <para>
-    If you are using a wlroots-based compositor, like sway, and want to
-    be able to share your screen, you might want to activate this
-    option:
-  </para>
-  <programlisting language="bash">
-xdg.portal.wlr.enable = true;
-</programlisting>
-  <para>
-    and configure Pipewire using
-    <xref linkend="opt-services.pipewire.enable" /> and related options.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/wireless.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/wireless.section.xml
deleted file mode 100644
index 82bc20135157..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/wireless.section.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-wireless">
-  <title>Wireless Networks</title>
-  <para>
-    For a desktop installation using NetworkManager (e.g., GNOME), you
-    just have to make sure the user is in the
-    <literal>networkmanager</literal> group and you can skip the rest of
-    this section on wireless networks.
-  </para>
-  <para>
-    NixOS will start wpa_supplicant for you if you enable this setting:
-  </para>
-  <programlisting language="bash">
-networking.wireless.enable = true;
-</programlisting>
-  <para>
-    NixOS lets you specify networks for wpa_supplicant declaratively:
-  </para>
-  <programlisting language="bash">
-networking.wireless.networks = {
-  echelon = {                # SSID with no spaces or special characters
-    psk = &quot;abcdefgh&quot;;
-  };
-  &quot;echelon's AP&quot; = {         # SSID with spaces and/or special characters
-    psk = &quot;ijklmnop&quot;;
-  };
-  echelon = {                # Hidden SSID
-    hidden = true;
-    psk = &quot;qrstuvwx&quot;;
-  };
-  free.wifi = {};            # Public wireless network
-};
-</programlisting>
-  <para>
-    Be aware that keys will be written to the nix store in plaintext!
-    When no networks are set, it will default to using a configuration
-    file at <literal>/etc/wpa_supplicant.conf</literal>. You should edit
-    this file yourself to define wireless networks, WPA keys and so on
-    (see wpa_supplicant.conf(5)).
-  </para>
-  <para>
-    If you are using WPA2 you can generate pskRaw key using
-    <literal>wpa_passphrase</literal>:
-  </para>
-  <programlisting>
-$ wpa_passphrase ESSID PSK
-network={
-        ssid=&quot;echelon&quot;
-        #psk=&quot;abcdefgh&quot;
-        psk=dca6d6ed41f4ab5a984c9f55f6f66d4efdc720ebf66959810f4329bb391c5435
-}
-</programlisting>
-  <programlisting language="bash">
-networking.wireless.networks = {
-  echelon = {
-    pskRaw = &quot;dca6d6ed41f4ab5a984c9f55f6f66d4efdc720ebf66959810f4329bb391c5435&quot;;
-  };
-}
-</programlisting>
-  <para>
-    or you can use it to directly generate the
-    <literal>wpa_supplicant.conf</literal>:
-  </para>
-  <programlisting>
-# wpa_passphrase ESSID PSK &gt; /etc/wpa_supplicant.conf
-</programlisting>
-  <para>
-    After you have edited the <literal>wpa_supplicant.conf</literal>,
-    you need to restart the wpa_supplicant service.
-  </para>
-  <programlisting>
-# systemctl restart wpa_supplicant.service
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml
deleted file mode 100644
index 274d0d817bc1..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml
+++ /dev/null
@@ -1,381 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-x11">
-  <title>X Window System</title>
-  <para>
-    The X Window System (X11) provides the basis of NixOS’ graphical
-    user interface. It can be enabled as follows:
-  </para>
-  <programlisting language="bash">
-services.xserver.enable = true;
-</programlisting>
-  <para>
-    The X server will automatically detect and use the appropriate video
-    driver from a set of X.org drivers (such as <literal>vesa</literal>
-    and <literal>intel</literal>). You can also specify a driver
-    manually, e.g.
-  </para>
-  <programlisting language="bash">
-services.xserver.videoDrivers = [ &quot;r128&quot; ];
-</programlisting>
-  <para>
-    to enable X.org’s <literal>xf86-video-r128</literal> driver.
-  </para>
-  <para>
-    You also need to enable at least one desktop or window manager.
-    Otherwise, you can only log into a plain undecorated
-    <literal>xterm</literal> window. Thus you should pick one or more of
-    the following lines:
-  </para>
-  <programlisting language="bash">
-services.xserver.desktopManager.plasma5.enable = true;
-services.xserver.desktopManager.xfce.enable = true;
-services.xserver.desktopManager.gnome.enable = true;
-services.xserver.desktopManager.mate.enable = true;
-services.xserver.windowManager.xmonad.enable = true;
-services.xserver.windowManager.twm.enable = true;
-services.xserver.windowManager.icewm.enable = true;
-services.xserver.windowManager.i3.enable = true;
-services.xserver.windowManager.herbstluftwm.enable = true;
-</programlisting>
-  <para>
-    NixOS’s default <emphasis>display manager</emphasis> (the program
-    that provides a graphical login prompt and manages the X server) is
-    LightDM. You can select an alternative one by picking one of the
-    following lines:
-  </para>
-  <programlisting language="bash">
-services.xserver.displayManager.sddm.enable = true;
-services.xserver.displayManager.gdm.enable = true;
-</programlisting>
-  <para>
-    You can set the keyboard layout (and optionally the layout variant):
-  </para>
-  <programlisting language="bash">
-services.xserver.layout = &quot;de&quot;;
-services.xserver.xkbVariant = &quot;neo&quot;;
-</programlisting>
-  <para>
-    The X server is started automatically at boot time. If you don’t
-    want this to happen, you can set:
-  </para>
-  <programlisting language="bash">
-services.xserver.autorun = false;
-</programlisting>
-  <para>
-    The X server can then be started manually:
-  </para>
-  <programlisting>
-# systemctl start display-manager.service
-</programlisting>
-  <para>
-    On 64-bit systems, if you want OpenGL for 32-bit programs such as in
-    Wine, you should also set the following:
-  </para>
-  <programlisting language="bash">
-hardware.opengl.driSupport32Bit = true;
-</programlisting>
-  <section xml:id="sec-x11-auto-login">
-    <title>Auto-login</title>
-    <para>
-      The x11 login screen can be skipped entirely, automatically
-      logging you into your window manager and desktop environment when
-      you boot your computer.
-    </para>
-    <para>
-      This is especially helpful if you have disk encryption enabled.
-      Since you already have to provide a password to decrypt your disk,
-      entering a second password to login can be redundant.
-    </para>
-    <para>
-      To enable auto-login, you need to define your default window
-      manager and desktop environment. If you wanted no desktop
-      environment and i3 as your your window manager, you'd define:
-    </para>
-    <programlisting language="bash">
-services.xserver.displayManager.defaultSession = &quot;none+i3&quot;;
-</programlisting>
-    <para>
-      Every display manager in NixOS supports auto-login, here is an
-      example using lightdm for a user <literal>alice</literal>:
-    </para>
-    <programlisting language="bash">
-services.xserver.displayManager.lightdm.enable = true;
-services.xserver.displayManager.autoLogin.enable = true;
-services.xserver.displayManager.autoLogin.user = &quot;alice&quot;;
-</programlisting>
-  </section>
-  <section xml:id="sec-x11--graphics-cards-intel">
-    <title>Intel Graphics drivers</title>
-    <para>
-      There are two choices for Intel Graphics drivers in X.org:
-      <literal>modesetting</literal> (included in the xorg-server
-      itself) and <literal>intel</literal> (provided by the package
-      xf86-video-intel).
-    </para>
-    <para>
-      The default and recommended is <literal>modesetting</literal>. It
-      is a generic driver which uses the kernel
-      <link xlink:href="https://en.wikipedia.org/wiki/Mode_setting">mode
-      setting</link> (KMS) mechanism. It supports Glamor (2D graphics
-      acceleration via OpenGL) and is actively maintained but may
-      perform worse in some cases (like in old chipsets).
-    </para>
-    <para>
-      The second driver, <literal>intel</literal>, is specific to Intel
-      GPUs, but not recommended by most distributions: it lacks several
-      modern features (for example, it doesn't support Glamor) and the
-      package hasn't been officially updated since 2015.
-    </para>
-    <para>
-      The results vary depending on the hardware, so you may have to try
-      both drivers. Use the option
-      <xref linkend="opt-services.xserver.videoDrivers" /> to set one.
-      The recommended configuration for modern systems is:
-    </para>
-    <programlisting language="bash">
-services.xserver.videoDrivers = [ &quot;modesetting&quot; ];
-services.xserver.useGlamor = true;
-</programlisting>
-    <para>
-      If you experience screen tearing no matter what, this
-      configuration was reported to resolve the issue:
-    </para>
-    <programlisting language="bash">
-services.xserver.videoDrivers = [ &quot;intel&quot; ];
-services.xserver.deviceSection = ''
-  Option &quot;DRI&quot; &quot;2&quot;
-  Option &quot;TearFree&quot; &quot;true&quot;
-'';
-</programlisting>
-    <para>
-      Note that this will likely downgrade the performance compared to
-      <literal>modesetting</literal> or <literal>intel</literal> with
-      DRI 3 (default).
-    </para>
-  </section>
-  <section xml:id="sec-x11-graphics-cards-nvidia">
-    <title>Proprietary NVIDIA drivers</title>
-    <para>
-      NVIDIA provides a proprietary driver for its graphics cards that
-      has better 3D performance than the X.org drivers. It is not
-      enabled by default because it’s not free software. You can enable
-      it as follows:
-    </para>
-    <programlisting language="bash">
-services.xserver.videoDrivers = [ &quot;nvidia&quot; ];
-</programlisting>
-    <para>
-      Or if you have an older card, you may have to use one of the
-      legacy drivers:
-    </para>
-    <programlisting language="bash">
-services.xserver.videoDrivers = [ &quot;nvidiaLegacy390&quot; ];
-services.xserver.videoDrivers = [ &quot;nvidiaLegacy340&quot; ];
-services.xserver.videoDrivers = [ &quot;nvidiaLegacy304&quot; ];
-</programlisting>
-    <para>
-      You may need to reboot after enabling this driver to prevent a
-      clash with other kernel modules.
-    </para>
-  </section>
-  <section xml:id="sec-x11--graphics-cards-amd">
-    <title>Proprietary AMD drivers</title>
-    <para>
-      AMD provides a proprietary driver for its graphics cards that is
-      not enabled by default because it’s not Free Software, is often
-      broken in nixpkgs and as of this writing doesn't offer more
-      features or performance. If you still want to use it anyway, you
-      need to explicitly set:
-    </para>
-    <programlisting language="bash">
-services.xserver.videoDrivers = [ &quot;amdgpu-pro&quot; ];
-</programlisting>
-    <para>
-      You will need to reboot after enabling this driver to prevent a
-      clash with other kernel modules.
-    </para>
-  </section>
-  <section xml:id="sec-x11-touchpads">
-    <title>Touchpads</title>
-    <para>
-      Support for Synaptics touchpads (found in many laptops such as the
-      Dell Latitude series) can be enabled as follows:
-    </para>
-    <programlisting language="bash">
-services.xserver.libinput.enable = true;
-</programlisting>
-    <para>
-      The driver has many options (see <xref linkend="ch-options" />).
-      For instance, the following disables tap-to-click behavior:
-    </para>
-    <programlisting language="bash">
-services.xserver.libinput.touchpad.tapping = false;
-</programlisting>
-    <para>
-      Note: the use of <literal>services.xserver.synaptics</literal> is
-      deprecated since NixOS 17.09.
-    </para>
-  </section>
-  <section xml:id="sec-x11-gtk-and-qt-themes">
-    <title>GTK/Qt themes</title>
-    <para>
-      GTK themes can be installed either to user profile or system-wide
-      (via <literal>environment.systemPackages</literal>). To make Qt 5
-      applications look similar to GTK ones, you can use the following
-      configuration:
-    </para>
-    <programlisting language="bash">
-qt5.enable = true;
-qt5.platformTheme = &quot;gtk2&quot;;
-qt5.style = &quot;gtk2&quot;;
-</programlisting>
-  </section>
-  <section xml:id="custom-xkb-layouts">
-    <title>Custom XKB layouts</title>
-    <para>
-      It is possible to install custom
-      <link xlink:href="https://en.wikipedia.org/wiki/X_keyboard_extension">
-      XKB </link> keyboard layouts using the option
-      <literal>services.xserver.extraLayouts</literal>.
-    </para>
-    <para>
-      As a first example, we are going to create a layout based on the
-      basic US layout, with an additional layer to type some greek
-      symbols by pressing the right-alt key.
-    </para>
-    <para>
-      Create a file called <literal>us-greek</literal> with the
-      following content (under a directory called
-      <literal>symbols</literal>; it's an XKB peculiarity that will help
-      with testing):
-    </para>
-    <programlisting language="bash">
-xkb_symbols &quot;us-greek&quot;
-{
-  include &quot;us(basic)&quot;            // includes the base US keys
-  include &quot;level3(ralt_switch)&quot;  // configures right alt as a third level switch
-
-  key &lt;LatA&gt; { [ a, A, Greek_alpha ] };
-  key &lt;LatB&gt; { [ b, B, Greek_beta  ] };
-  key &lt;LatG&gt; { [ g, G, Greek_gamma ] };
-  key &lt;LatD&gt; { [ d, D, Greek_delta ] };
-  key &lt;LatZ&gt; { [ z, Z, Greek_zeta  ] };
-};
-</programlisting>
-    <para>
-      A minimal layout specification must include the following:
-    </para>
-    <programlisting language="bash">
-services.xserver.extraLayouts.us-greek = {
-  description = &quot;US layout with alt-gr greek&quot;;
-  languages   = [ &quot;eng&quot; ];
-  symbolsFile = /yourpath/symbols/us-greek;
-};
-</programlisting>
-    <note>
-      <para>
-        The name (after <literal>extraLayouts.</literal>) should match
-        the one given to the <literal>xkb_symbols</literal> block.
-      </para>
-    </note>
-    <para>
-      Applying this customization requires rebuilding several packages,
-      and a broken XKB file can lead to the X session crashing at login.
-      Therefore, you're strongly advised to <emphasis role="strong">test
-      your layout before applying it</emphasis>:
-    </para>
-    <programlisting>
-$ nix-shell -p xorg.xkbcomp
-$ setxkbmap -I/yourpath us-greek -print | xkbcomp -I/yourpath - $DISPLAY
-</programlisting>
-    <para>
-      You can inspect the predefined XKB files for examples:
-    </para>
-    <programlisting>
-$ echo &quot;$(nix-build --no-out-link '&lt;nixpkgs&gt;' -A xorg.xkeyboardconfig)/etc/X11/xkb/&quot;
-</programlisting>
-    <para>
-      Once the configuration is applied, and you did a logout/login
-      cycle, the layout should be ready to use. You can try it by e.g.
-      running <literal>setxkbmap us-greek</literal> and then type
-      <literal>&lt;alt&gt;+a</literal> (it may not get applied in your
-      terminal straight away). To change the default, the usual
-      <literal>services.xserver.layout</literal> option can still be
-      used.
-    </para>
-    <para>
-      A layout can have several other components besides
-      <literal>xkb_symbols</literal>, for example we will define new
-      keycodes for some multimedia key and bind these to some symbol.
-    </para>
-    <para>
-      Use the <emphasis>xev</emphasis> utility from
-      <literal>pkgs.xorg.xev</literal> to find the codes of the keys of
-      interest, then create a <literal>media-key</literal> file to hold
-      the keycodes definitions
-    </para>
-    <programlisting language="bash">
-xkb_keycodes &quot;media&quot;
-{
- &lt;volUp&gt;   = 123;
- &lt;volDown&gt; = 456;
-}
-</programlisting>
-    <para>
-      Now use the newly define keycodes in <literal>media-sym</literal>:
-    </para>
-    <programlisting language="bash">
-xkb_symbols &quot;media&quot;
-{
- key.type = &quot;ONE_LEVEL&quot;;
- key &lt;volUp&gt;   { [ XF86AudioLowerVolume ] };
- key &lt;volDown&gt; { [ XF86AudioRaiseVolume ] };
-}
-</programlisting>
-    <para>
-      As before, to install the layout do
-    </para>
-    <programlisting language="bash">
-services.xserver.extraLayouts.media = {
-  description  = &quot;Multimedia keys remapping&quot;;
-  languages    = [ &quot;eng&quot; ];
-  symbolsFile  = /path/to/media-key;
-  keycodesFile = /path/to/media-sym;
-};
-</programlisting>
-    <note>
-      <para>
-        The function
-        <literal>pkgs.writeText &lt;filename&gt; &lt;content&gt;</literal>
-        can be useful if you prefer to keep the layout definitions
-        inside the NixOS configuration.
-      </para>
-    </note>
-    <para>
-      Unfortunately, the Xorg server does not (currently) support
-      setting a keymap directly but relies instead on XKB rules to
-      select the matching components (keycodes, types, ...) of a layout.
-      This means that components other than symbols won't be loaded by
-      default. As a workaround, you can set the keymap using
-      <literal>setxkbmap</literal> at the start of the session with:
-    </para>
-    <programlisting language="bash">
-services.xserver.displayManager.sessionCommands = &quot;setxkbmap -keycodes media&quot;;
-</programlisting>
-    <para>
-      If you are manually starting the X server, you should set the
-      argument <literal>-xkbdir /etc/X11/xkb</literal>, otherwise X
-      won't find your layout files. For example with
-      <literal>xinit</literal> run
-    </para>
-    <programlisting>
-$ xinit -- -xkbdir /etc/X11/xkb
-</programlisting>
-    <para>
-      To learn how to write layouts take a look at the XKB
-      <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts">documentation
-      </link>. More example layouts can also be found
-      <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples">here
-      </link>.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/xfce.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/xfce.chapter.xml
deleted file mode 100644
index 42e70d1d81d3..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/xfce.chapter.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-xfce">
-  <title>Xfce Desktop Environment</title>
-  <para>
-    To enable the Xfce Desktop Environment, set
-  </para>
-  <programlisting language="bash">
-services.xserver.desktopManager.xfce.enable = true;
-services.xserver.displayManager.defaultSession = &quot;xfce&quot;;
-</programlisting>
-  <para>
-    Optionally, <emphasis>picom</emphasis> can be enabled for nice
-    graphical effects, some example settings:
-  </para>
-  <programlisting language="bash">
-services.picom = {
-  enable = true;
-  fade = true;
-  inactiveOpacity = 0.9;
-  shadow = true;
-  fadeDelta = 4;
-};
-</programlisting>
-  <para>
-    Some Xfce programs are not installed automatically. To install them
-    manually (system wide), put them into your
-    <xref linkend="opt-environment.systemPackages" /> from
-    <literal>pkgs.xfce</literal>.
-  </para>
-  <section xml:id="sec-xfce-thunar-plugins">
-    <title>Thunar</title>
-    <para>
-      Thunar (the Xfce file manager) is automatically enabled when Xfce
-      is enabled. To enable Thunar without enabling Xfce, use the
-      configuration option <xref linkend="opt-programs.thunar.enable" />
-      instead of simply adding <literal>pkgs.xfce.thunar</literal> to
-      <xref linkend="opt-environment.systemPackages" />.
-    </para>
-    <para>
-      If you'd like to add extra plugins to Thunar, add them to
-      <xref linkend="opt-programs.thunar.plugins" />. You shouldn't just
-      add them to <xref linkend="opt-environment.systemPackages" />.
-    </para>
-  </section>
-  <section xml:id="sec-xfce-troubleshooting">
-    <title>Troubleshooting</title>
-    <para>
-      Even after enabling udisks2, volume management might not work.
-      Thunar and/or the desktop takes time to show up. Thunar will spit
-      out this kind of message on start (look at
-      <literal>journalctl --user -b</literal>).
-    </para>
-    <programlisting>
-Thunar:2410): GVFS-RemoteVolumeMonitor-WARNING **: remote volume monitor with dbus name org.gtk.Private.UDisks2VolumeMonitor is not supported
-</programlisting>
-    <para>
-      This is caused by some needed GNOME services not running. This is
-      all fixed by enabling &quot;Launch GNOME services on startup&quot;
-      in the Advanced tab of the Session and Startup settings panel.
-      Alternatively, you can run this command to do the same thing.
-    </para>
-    <programlisting>
-$ xfconf-query -c xfce4-session -p /compat/LaunchGNOME -s true
-</programlisting>
-    <para>
-      A log-out and re-log will be needed for this to take effect.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml
deleted file mode 100644
index a9b0c6a5eefa..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="chap-contributing">
-  <title>Contributing to this manual</title>
-  <para>
-    The DocBook and CommonMark sources of NixOS’ manual are in the
-    <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/doc/manual">nixos/doc/manual</link>
-    subdirectory of the
-    <link xlink:href="https://github.com/NixOS/nixpkgs">Nixpkgs</link>
-    repository.
-  </para>
-  <para>
-    You can quickly check your edits with the following:
-  </para>
-  <programlisting>
-$ cd /path/to/nixpkgs
-$ ./nixos/doc/manual/md-to-db.sh
-$ nix-build nixos/release.nix -A manual.x86_64-linux
-</programlisting>
-  <para>
-    If the build succeeds, the manual will be in
-    <literal>./result/share/doc/nixos/index.html</literal>.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/activation-script.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/activation-script.section.xml
deleted file mode 100644
index 981ebf37e60f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/activation-script.section.xml
+++ /dev/null
@@ -1,150 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-activation-script">
-  <title>Activation script</title>
-  <para>
-    The activation script is a bash script called to activate the new
-    configuration which resides in a NixOS system in
-    <literal>$out/activate</literal>. Since its contents depend on your
-    system configuration, the contents may differ. This chapter explains
-    how the script works in general and some common NixOS snippets.
-    Please be aware that the script is executed on every boot and system
-    switch, so tasks that can be performed in other places should be
-    performed there (for example letting a directory of a service be
-    created by systemd using mechanisms like
-    <literal>StateDirectory</literal>,
-    <literal>CacheDirectory</literal>, … or if that’s not possible using
-    <literal>preStart</literal> of the service).
-  </para>
-  <para>
-    Activation scripts are defined as snippets using
-    <xref linkend="opt-system.activationScripts" />. They can either be
-    a simple multiline string or an attribute set that can depend on
-    other snippets. The builder for the activation script will take
-    these dependencies into account and order the snippets accordingly.
-    As a simple example:
-  </para>
-  <programlisting language="bash">
-system.activationScripts.my-activation-script = {
-  deps = [ &quot;etc&quot; ];
-  # supportsDryActivation = true;
-  text = ''
-    echo &quot;Hallo i bims&quot;
-  '';
-};
-</programlisting>
-  <para>
-    This example creates an activation script snippet that is run after
-    the <literal>etc</literal> snippet. The special variable
-    <literal>supportsDryActivation</literal> can be set so the snippet
-    is also run when <literal>nixos-rebuild dry-activate</literal> is
-    run. To differentiate between real and dry activation, the
-    <literal>$NIXOS_ACTION</literal> environment variable can be read
-    which is set to <literal>dry-activate</literal> when a dry
-    activation is done.
-  </para>
-  <para>
-    An activation script can write to special files instructing
-    <literal>switch-to-configuration</literal> to restart/reload units.
-    The script will take these requests into account and will
-    incorperate the unit configuration as described above. This means
-    that the activation script will <quote>fake</quote> a modified unit
-    file and <literal>switch-to-configuration</literal> will act
-    accordingly. By doing so, configuration like
-    <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.restartIfChanged</link>
-    is respected. Since the activation script is run
-    <emphasis role="strong">after</emphasis> services are already
-    stopped,
-    <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.stopIfChanged</link>
-    cannot be taken into account anymore and the unit is always
-    restarted instead of being stopped and started afterwards.
-  </para>
-  <para>
-    The files that can be written to are
-    <literal>/run/nixos/activation-restart-list</literal> and
-    <literal>/run/nixos/activation-reload-list</literal> with their
-    respective counterparts for dry activation being
-    <literal>/run/nixos/dry-activation-restart-list</literal> and
-    <literal>/run/nixos/dry-activation-reload-list</literal>. Those
-    files can contain newline-separated lists of unit names where
-    duplicates are being ignored. These files are not create
-    automatically and activation scripts must take the possiblility into
-    account that they have to create them first.
-  </para>
-  <section xml:id="sec-activation-script-nixos-snippets">
-    <title>NixOS snippets</title>
-    <para>
-      There are some snippets NixOS enables by default because disabling
-      them would most likely break your system. This section lists a few
-      of them and what they do:
-    </para>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          <literal>binsh</literal> creates <literal>/bin/sh</literal>
-          which points to the runtime shell
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>etc</literal> sets up the contents of
-          <literal>/etc</literal>, this includes systemd units and
-          excludes <literal>/etc/passwd</literal>,
-          <literal>/etc/group</literal>, and
-          <literal>/etc/shadow</literal> (which are managed by the
-          <literal>users</literal> snippet)
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hostname</literal> sets the system’s hostname in the
-          kernel (not in <literal>/etc</literal>)
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>modprobe</literal> sets the path to the
-          <literal>modprobe</literal> binary for module auto-loading
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nix</literal> prepares the nix store and adds a
-          default initial channel
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>specialfs</literal> is responsible for mounting
-          filesystems like <literal>/proc</literal> and
-          <literal>sys</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>users</literal> creates and removes users and groups
-          by managing <literal>/etc/passwd</literal>,
-          <literal>/etc/group</literal> and
-          <literal>/etc/shadow</literal>. This also creates home
-          directories
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>usrbinenv</literal> creates
-          <literal>/usr/bin/env</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>var</literal> creates some directories in
-          <literal>/var</literal> that are not service-specific
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>wrappers</literal> creates setuid wrappers like
-          <literal>ping</literal> and <literal>sudo</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/assertions.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/assertions.section.xml
deleted file mode 100644
index 0844d484d60f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/assertions.section.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-assertions">
-  <title>Warnings and Assertions</title>
-  <para>
-    When configuration problems are detectable in a module, it is a good
-    idea to write an assertion or warning. Doing so provides clear
-    feedback to the user and prevents errors after the build.
-  </para>
-  <para>
-    Although Nix has the <literal>abort</literal> and
-    <literal>builtins.trace</literal>
-    <link xlink:href="https://nixos.org/nix/manual/#ssec-builtins">functions</link>
-    to perform such tasks, they are not ideally suited for NixOS
-    modules. Instead of these functions, you can declare your warnings
-    and assertions using the NixOS module system.
-  </para>
-  <section xml:id="sec-assertions-warnings">
-    <title>Warnings</title>
-    <para>
-      This is an example of using <literal>warnings</literal>.
-    </para>
-    <programlisting language="bash">
-{ config, lib, ... }:
-{
-  config = lib.mkIf config.services.foo.enable {
-    warnings =
-      if config.services.foo.bar
-      then [ ''You have enabled the bar feature of the foo service.
-               This is known to cause some specific problems in certain situations.
-               '' ]
-      else [];
-  }
-}
-</programlisting>
-  </section>
-  <section xml:id="sec-assertions-assetions">
-    <title>Assertions</title>
-    <para>
-      This example, extracted from the
-      <link xlink:href="https://github.com/NixOS/nixpkgs/blob/release-17.09/nixos/modules/services/logging/syslogd.nix"><literal>syslogd</literal>
-      module</link> shows how to use <literal>assertions</literal>.
-      Since there can only be one active syslog daemon at a time, an
-      assertion is useful to prevent such a broken system from being
-      built.
-    </para>
-    <programlisting language="bash">
-{ config, lib, ... }:
-{
-  config = lib.mkIf config.services.syslogd.enable {
-    assertions =
-      [ { assertion = !config.services.rsyslogd.enable;
-          message = &quot;rsyslogd conflicts with syslogd&quot;;
-        }
-      ];
-  }
-}
-</programlisting>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/building-parts.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/building-parts.chapter.xml
deleted file mode 100644
index 4df24cc95652..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/building-parts.chapter.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-building-parts">
-  <title>Building Specific Parts of NixOS</title>
-  <para>
-    With the command <literal>nix-build</literal>, you can build
-    specific parts of your NixOS configuration. This is done as follows:
-  </para>
-  <programlisting>
-$ cd /path/to/nixpkgs/nixos
-$ nix-build -A config.option
-</programlisting>
-  <para>
-    where <literal>option</literal> is a NixOS option with type
-    <quote>derivation</quote> (i.e. something that can be built).
-    Attributes of interest include:
-  </para>
-  <variablelist>
-    <varlistentry>
-      <term>
-        <literal>system.build.toplevel</literal>
-      </term>
-      <listitem>
-        <para>
-          The top-level option that builds the entire NixOS system.
-          Everything else in your configuration is indirectly pulled in
-          by this option. This is what <literal>nixos-rebuild</literal>
-          builds and what <literal>/run/current-system</literal> points
-          to afterwards.
-        </para>
-        <para>
-          A shortcut to build this is:
-        </para>
-        <programlisting>
-$ nix-build -A system
-</programlisting>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>system.build.manual.manualHTML</literal>
-      </term>
-      <listitem>
-        <para>
-          The NixOS manual.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>system.build.etc</literal>
-      </term>
-      <listitem>
-        <para>
-          A tree of symlinks that form the static parts of
-          <literal>/etc</literal>.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>system.build.initialRamdisk</literal> ,
-        <literal>system.build.kernel</literal>
-      </term>
-      <listitem>
-        <para>
-          The initial ramdisk and kernel of the system. This allows a
-          quick way to test whether the kernel and the initial ramdisk
-          boot correctly, by using QEMU’s <literal>-kernel</literal> and
-          <literal>-initrd</literal> options:
-        </para>
-        <programlisting>
-$ nix-build -A config.system.build.initialRamdisk -o initrd
-$ nix-build -A config.system.build.kernel -o kernel
-$ qemu-system-x86_64 -kernel ./kernel/bzImage -initrd ./initrd/initrd -hda /dev/null
-</programlisting>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>system.build.nixos-rebuild</literal> ,
-        <literal>system.build.nixos-install</literal> ,
-        <literal>system.build.nixos-generate-config</literal>
-      </term>
-      <listitem>
-        <para>
-          These build the corresponding NixOS commands.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>systemd.units.unit-name.unit</literal>
-      </term>
-      <listitem>
-        <para>
-          This builds the unit with the specified name. Note that since
-          unit names contain dots (e.g.
-          <literal>httpd.service</literal>), you need to put them
-          between quotes, like this:
-        </para>
-        <programlisting>
-$ nix-build -A 'config.systemd.units.&quot;httpd.service&quot;.unit'
-</programlisting>
-        <para>
-          You can also test individual units, without rebuilding the
-          whole system, by putting them in
-          <literal>/run/systemd/system</literal>:
-        </para>
-        <programlisting>
-$ cp $(nix-build -A 'config.systemd.units.&quot;httpd.service&quot;.unit')/httpd.service \
-    /run/systemd/system/tmp-httpd.service
-# systemctl daemon-reload
-# systemctl start tmp-httpd.service
-</programlisting>
-        <para>
-          Note that the unit must not have the same name as any unit in
-          <literal>/etc/systemd/system</literal> since those take
-          precedence over <literal>/run/systemd/system</literal>. That’s
-          why the unit is installed as
-          <literal>tmp-httpd.service</literal> here.
-        </para>
-      </listitem>
-    </varlistentry>
-  </variablelist>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/freeform-modules.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/freeform-modules.section.xml
deleted file mode 100644
index 86a9cf3140d8..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/freeform-modules.section.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-freeform-modules">
-  <title>Freeform modules</title>
-  <para>
-    Freeform modules allow you to define values for option paths that
-    have not been declared explicitly. This can be used to add
-    attribute-specific types to what would otherwise have to be
-    <literal>attrsOf</literal> options in order to accept all attribute
-    names.
-  </para>
-  <para>
-    This feature can be enabled by using the attribute
-    <literal>freeformType</literal> to define a freeform type. By doing
-    this, all assignments without an associated option will be merged
-    using the freeform type and combined into the resulting
-    <literal>config</literal> set. Since this feature nullifies name
-    checking for entire option trees, it is only recommended for use in
-    submodules.
-  </para>
-  <anchor xml:id="ex-freeform-module" />
-  <para>
-    <emphasis role="strong">Example: Freeform submodule</emphasis>
-  </para>
-  <para>
-    The following shows a submodule assigning a freeform type that
-    allows arbitrary attributes with <literal>str</literal> values below
-    <literal>settings</literal>, but also declares an option for the
-    <literal>settings.port</literal> attribute to have it type-checked
-    and assign a default value. See
-    <link linkend="ex-settings-typed-attrs">Example: Declaring a
-    type-checked <literal>settings</literal> attribute</link> for a more
-    complete example.
-  </para>
-  <programlisting language="bash">
-{ lib, config, ... }: {
-
-  options.settings = lib.mkOption {
-    type = lib.types.submodule {
-
-      freeformType = with lib.types; attrsOf str;
-
-      # We want this attribute to be checked for the correct type
-      options.port = lib.mkOption {
-        type = lib.types.port;
-        # Declaring the option also allows defining a default value
-        default = 8080;
-      };
-
-    };
-  };
-}
-</programlisting>
-  <para>
-    And the following shows what such a module then allows
-  </para>
-  <programlisting language="bash">
-{
-  # Not a declared option, but the freeform type allows this
-  settings.logLevel = &quot;debug&quot;;
-
-  # Not allowed because the the freeform type only allows strings
-  # settings.enable = true;
-
-  # Allowed because there is a port option declared
-  settings.port = 80;
-
-  # Not allowed because the port option doesn't allow strings
-  # settings.port = &quot;443&quot;;
-}
-</programlisting>
-  <note>
-    <para>
-      Freeform attributes cannot depend on other attributes of the same
-      set without infinite recursion:
-    </para>
-    <programlisting language="bash">
-{
-  # This throws infinite recursion encountered
-  settings.logLevel = lib.mkIf (config.settings.port == 80) &quot;debug&quot;;
-}
-</programlisting>
-    <para>
-      To prevent this, declare options for all attributes that need to
-      depend on others. For above example this means to declare
-      <literal>logLevel</literal> to be an option.
-    </para>
-  </note>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/importing-modules.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/importing-modules.section.xml
deleted file mode 100644
index cb04dde67c83..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/importing-modules.section.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-importing-modules">
-  <title>Importing Modules</title>
-  <para>
-    Sometimes NixOS modules need to be used in configuration but exist
-    outside of Nixpkgs. These modules can be imported:
-  </para>
-  <programlisting language="bash">
-{ config, lib, pkgs, ... }:
-
-{
-  imports =
-    [ # Use a locally-available module definition in
-      # ./example-module/default.nix
-        ./example-module
-    ];
-
-  services.exampleModule.enable = true;
-}
-</programlisting>
-  <para>
-    The environment variable <literal>NIXOS_EXTRA_MODULE_PATH</literal>
-    is an absolute path to a NixOS module that is included alongside the
-    Nixpkgs NixOS modules. Like any NixOS module, this module can import
-    additional modules:
-  </para>
-  <programlisting language="bash">
-# ./module-list/default.nix
-[
-  ./example-module1
-  ./example-module2
-]
-</programlisting>
-  <programlisting language="bash">
-# ./extra-module/default.nix
-{ imports = import ./module-list.nix; }
-</programlisting>
-  <programlisting language="bash">
-# NIXOS_EXTRA_MODULE_PATH=/absolute/path/to/extra-module
-{ config, lib, pkgs, ... }:
-
-{
-  # No `imports` needed
-
-  services.exampleModule1.enable = true;
-}
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/linking-nixos-tests-to-packages.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/linking-nixos-tests-to-packages.section.xml
deleted file mode 100644
index 666bbec6162b..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/linking-nixos-tests-to-packages.section.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-linking-nixos-tests-to-packages">
-  <title>Linking NixOS tests to packages</title>
-  <para>
-    You can link NixOS module tests to the packages that they exercised,
-    so that the tests can be run automatically during code review when
-    the package gets changed. This is
-    <link xlink:href="https://nixos.org/manual/nixpkgs/stable/#ssec-nixos-tests-linking">described
-    in the nixpkgs manual</link>.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/meta-attributes.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/meta-attributes.section.xml
deleted file mode 100644
index 1eb6e0f30368..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/meta-attributes.section.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-meta-attributes">
-  <title>Meta Attributes</title>
-  <para>
-    Like Nix packages, NixOS modules can declare meta-attributes to
-    provide extra information. Module meta attributes are defined in the
-    <literal>meta.nix</literal> special module.
-  </para>
-  <para>
-    <literal>meta</literal> is a top level attribute like
-    <literal>options</literal> and <literal>config</literal>. Available
-    meta-attributes are <literal>maintainers</literal>,
-    <literal>doc</literal>, and <literal>buildDocsInSandbox</literal>.
-  </para>
-  <para>
-    Each of the meta-attributes must be defined at most once per module
-    file.
-  </para>
-  <programlisting language="bash">
-{ config, lib, pkgs, ... }:
-{
-  options = {
-    ...
-  };
-
-  config = {
-    ...
-  };
-
-  meta = {
-    maintainers = with lib.maintainers; [ ericsagnes ];
-    doc = ./default.xml;
-    buildDocsInSandbox = true;
-  };
-}
-</programlisting>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <literal>maintainers</literal> contains a list of the module
-        maintainers.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>doc</literal> points to a valid DocBook file containing
-        the module documentation. Its contents is automatically added to
-        <xref linkend="ch-configuration" />. Changes to a module
-        documentation have to be checked to not break building the NixOS
-        manual:
-      </para>
-      <programlisting>
-$ nix-build nixos/release.nix -A manual.x86_64-linux
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>buildDocsInSandbox</literal> indicates whether the
-        option documentation for the module can be built in a derivation
-        sandbox. This option is currently only honored for modules
-        shipped by nixpkgs. User modules and modules taken from
-        <literal>NIXOS_EXTRA_MODULE_PATH</literal> are always built
-        outside of the sandbox, as has been the case in previous
-        releases.
-      </para>
-      <para>
-        Building NixOS option documentation in a sandbox allows caching
-        of the built documentation, which greatly decreases the amount
-        of time needed to evaluate a system configuration that has NixOS
-        documentation enabled. The sandbox also restricts which
-        attributes may be referenced by documentation attributes (such
-        as option descriptions) to the <literal>options</literal> and
-        <literal>lib</literal> module arguments and the
-        <literal>pkgs.formats</literal> attribute of the
-        <literal>pkgs</literal> argument, <literal>config</literal> and
-        the rest of <literal>pkgs</literal> are disallowed and will
-        cause doc build failures when used. This restriction is
-        necessary because we cannot reproduce the full nixpkgs
-        instantiation with configuration and overlays from a system
-        configuration inside the sandbox. The <literal>options</literal>
-        argument only includes options of modules that are also built
-        inside the sandbox, referencing an option of a module that isn’t
-        built in the sandbox is also forbidden.
-      </para>
-      <para>
-        The default is <literal>true</literal> and should usually not be
-        changed; set it to <literal>false</literal> only if the module
-        requires access to <literal>pkgs</literal> in its documentation
-        (e.g. because it loads information from a linked package to
-        build an option type) or if its documentation depends on other
-        modules that also aren’t sandboxed (e.g. by using types defined
-        in the other module).
-      </para>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml
deleted file mode 100644
index b9ff2269676c..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-nixos-tests">
-  <title>NixOS Tests</title>
-  <para>
-    When you add some feature to NixOS, you should write a test for it.
-    NixOS tests are kept in the directory
-    <literal>nixos/tests</literal>, and are executed (using Nix) by a
-    testing framework that automatically starts one or more virtual
-    machines containing the NixOS system(s) required for the test.
-  </para>
-  <xi:include href="writing-nixos-tests.section.xml" />
-  <xi:include href="running-nixos-tests.section.xml" />
-  <xi:include href="running-nixos-tests-interactively.section.xml" />
-  <xi:include href="linking-nixos-tests-to-packages.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/option-declarations.section.xml
deleted file mode 100644
index d7c7f7716bea..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/option-declarations.section.xml
+++ /dev/null
@@ -1,337 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-option-declarations">
-  <title>Option Declarations</title>
-  <para>
-    An option declaration specifies the name, type and description of a
-    NixOS configuration option. It is invalid to define an option that
-    hasn’t been declared in any module. An option declaration generally
-    looks like this:
-  </para>
-  <programlisting language="bash">
-options = {
-  name = mkOption {
-    type = type specification;
-    default = default value;
-    example = example value;
-    description = &quot;Description for use in the NixOS manual.&quot;;
-  };
-};
-</programlisting>
-  <para>
-    The attribute names within the <literal>name</literal> attribute
-    path must be camel cased in general but should, as an exception,
-    match the
-    <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-package-naming">
-    package attribute name</link> when referencing a Nixpkgs package.
-    For example, the option
-    <literal>services.nix-serve.bindAddress</literal> references the
-    <literal>nix-serve</literal> Nixpkgs package.
-  </para>
-  <para>
-    The function <literal>mkOption</literal> accepts the following
-    arguments.
-  </para>
-  <variablelist>
-    <varlistentry>
-      <term>
-        <literal>type</literal>
-      </term>
-      <listitem>
-        <para>
-          The type of the option (see
-          <xref linkend="sec-option-types" />). This argument is
-          mandatory for nixpkgs modules. Setting this is highly
-          recommended for the sake of documentation and type checking.
-          In case it is not set, a fallback type with unspecified
-          behavior is used.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>default</literal>
-      </term>
-      <listitem>
-        <para>
-          The default value used if no value is defined by any module. A
-          default is not required; but if a default is not given, then
-          users of the module will have to define the value of the
-          option, otherwise an error will be thrown.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>defaultText</literal>
-      </term>
-      <listitem>
-        <para>
-          A textual representation of the default value to be rendered
-          verbatim in the manual. Useful if the default value is a
-          complex expression or depends on other values or packages. Use
-          <literal>lib.literalExpression</literal> for a Nix expression,
-          <literal>lib.literalDocBook</literal> for a plain English
-          description in DocBook format.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>example</literal>
-      </term>
-      <listitem>
-        <para>
-          An example value that will be shown in the NixOS manual. You
-          can use <literal>lib.literalExpression</literal> and
-          <literal>lib.literalDocBook</literal> in the same way as in
-          <literal>defaultText</literal>.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>description</literal>
-      </term>
-      <listitem>
-        <para>
-          A textual description of the option, in DocBook format, that
-          will be included in the NixOS manual. During the migration
-          process from DocBook to CommonMark the description may also be
-          written in CommonMark, but has to be wrapped in
-          <literal>lib.mdDoc</literal> to differentiate it from DocBook.
-          See the nixpkgs manual for
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-contributing-markup">the
-          list of CommonMark extensions</link> supported by NixOS
-          documentation.
-        </para>
-        <para>
-          New documentation should preferably be written as CommonMark.
-        </para>
-      </listitem>
-    </varlistentry>
-  </variablelist>
-  <section xml:id="sec-option-declarations-util">
-    <title>Utility functions for common option patterns</title>
-    <section xml:id="sec-option-declarations-util-mkEnableOption">
-      <title><literal>mkEnableOption</literal></title>
-      <para>
-        Creates an Option attribute set for a boolean value option i.e
-        an option to be toggled on or off.
-      </para>
-      <para>
-        This function takes a single string argument, the name of the
-        thing to be toggled.
-      </para>
-      <para>
-        The option’s description is <quote>Whether to enable
-        &lt;name&gt;.</quote>.
-      </para>
-      <para>
-        For example:
-      </para>
-      <anchor xml:id="ex-options-declarations-util-mkEnableOption-magic" />
-      <programlisting language="bash">
-lib.mkEnableOption &quot;magic&quot;
-# is like
-lib.mkOption {
-  type = lib.types.bool;
-  default = false;
-  example = true;
-  description = &quot;Whether to enable magic.&quot;;
-}
-</programlisting>
-      <section xml:id="sec-option-declarations-util-mkPackageOption">
-        <title><literal>mkPackageOption</literal></title>
-        <para>
-          Usage:
-        </para>
-        <programlisting language="bash">
-mkPackageOption pkgs &quot;name&quot; { default = [ &quot;path&quot; &quot;in&quot; &quot;pkgs&quot; ]; example = &quot;literal example&quot;; }
-</programlisting>
-        <para>
-          Creates an Option attribute set for an option that specifies
-          the package a module should use for some purpose.
-        </para>
-        <para>
-          <emphasis role="strong">Note</emphasis>: You shouldn’t
-          necessarily make package options for all of your modules. You
-          can always overwrite a specific package throughout nixpkgs by
-          using
-          <link xlink:href="https://nixos.org/manual/nixpkgs/stable/#chap-overlays">nixpkgs
-          overlays</link>.
-        </para>
-        <para>
-          The default package is specified as a list of strings
-          representing its attribute path in nixpkgs. Because of this,
-          you need to pass nixpkgs itself as the first argument.
-        </para>
-        <para>
-          The second argument is the name of the option, used in the
-          description <quote>The &lt;name&gt; package to use.</quote>.
-          You can also pass an example value, either a literal string or
-          a package’s attribute path.
-        </para>
-        <para>
-          You can omit the default path if the name of the option is
-          also attribute path in nixpkgs.
-        </para>
-        <anchor xml:id="ex-options-declarations-util-mkPackageOption" />
-        <para>
-          Examples:
-        </para>
-        <anchor xml:id="ex-options-declarations-util-mkPackageOption-hello" />
-        <programlisting language="bash">
-lib.mkPackageOption pkgs &quot;hello&quot; { }
-# is like
-lib.mkOption {
-  type = lib.types.package;
-  default = pkgs.hello;
-  defaultText = lib.literalExpression &quot;pkgs.hello&quot;;
-  description = &quot;The hello package to use.&quot;;
-}
-</programlisting>
-        <anchor xml:id="ex-options-declarations-util-mkPackageOption-ghc" />
-        <programlisting language="bash">
-lib.mkPackageOption pkgs &quot;GHC&quot; {
-  default = [ &quot;ghc&quot; ];
-  example = &quot;pkgs.haskell.packages.ghc924.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
-}
-# is like
-lib.mkOption {
-  type = lib.types.package;
-  default = pkgs.ghc;
-  defaultText = lib.literalExpression &quot;pkgs.ghc&quot;;
-  example = lib.literalExpression &quot;pkgs.haskell.packages.ghc924.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
-  description = &quot;The GHC package to use.&quot;;
-}
-</programlisting>
-        <section xml:id="sec-option-declarations-eot">
-          <title>Extensible Option Types</title>
-          <para>
-            Extensible option types is a feature that allow to extend
-            certain types declaration through multiple module files.
-            This feature only work with a restricted set of types,
-            namely <literal>enum</literal> and
-            <literal>submodules</literal> and any composed forms of
-            them.
-          </para>
-          <para>
-            Extensible option types can be used for
-            <literal>enum</literal> options that affects multiple
-            modules, or as an alternative to related
-            <literal>enable</literal> options.
-          </para>
-          <para>
-            As an example, we will take the case of display managers.
-            There is a central display manager module for generic
-            display manager options and a module file per display
-            manager backend (sddm, gdm ...).
-          </para>
-          <para>
-            There are two approaches we could take with this module
-            structure:
-          </para>
-          <itemizedlist>
-            <listitem>
-              <para>
-                Configuring the display managers independently by adding
-                an enable option to every display manager module
-                backend. (NixOS)
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                Configuring the display managers in the central module
-                by adding an option to select which display manager
-                backend to use.
-              </para>
-            </listitem>
-          </itemizedlist>
-          <para>
-            Both approaches have problems.
-          </para>
-          <para>
-            Making backends independent can quickly become hard to
-            manage. For display managers, there can only be one enabled
-            at a time, but the type system cannot enforce this
-            restriction as there is no relation between each backend’s
-            <literal>enable</literal> option. As a result, this
-            restriction has to be done explicitly by adding assertions
-            in each display manager backend module.
-          </para>
-          <para>
-            On the other hand, managing the display manager backends in
-            the central module will require changing the central module
-            option every time a new backend is added or removed.
-          </para>
-          <para>
-            By using extensible option types, it is possible to create a
-            placeholder option in the central module
-            (<link linkend="ex-option-declaration-eot-service">Example:
-            Extensible type placeholder in the service module</link>),
-            and to extend it in each backend module
-            (<link linkend="ex-option-declaration-eot-backend-gdm">Example:
-            Extending
-            <literal>services.xserver.displayManager.enable</literal> in
-            the <literal>gdm</literal> module</link>,
-            <link linkend="ex-option-declaration-eot-backend-sddm">Example:
-            Extending
-            <literal>services.xserver.displayManager.enable</literal> in
-            the <literal>sddm</literal> module</link>).
-          </para>
-          <para>
-            As a result, <literal>displayManager.enable</literal> option
-            values can be added without changing the main service module
-            file and the type system automatically enforces that there
-            can only be a single display manager enabled.
-          </para>
-          <anchor xml:id="ex-option-declaration-eot-service" />
-          <para>
-            <emphasis role="strong">Example: Extensible type placeholder
-            in the service module</emphasis>
-          </para>
-          <programlisting language="bash">
-services.xserver.displayManager.enable = mkOption {
-  description = &quot;Display manager to use&quot;;
-  type = with types; nullOr (enum [ ]);
-};
-</programlisting>
-          <anchor xml:id="ex-option-declaration-eot-backend-gdm" />
-          <para>
-            <emphasis role="strong">Example: Extending
-            <literal>services.xserver.displayManager.enable</literal> in
-            the <literal>gdm</literal> module</emphasis>
-          </para>
-          <programlisting language="bash">
-services.xserver.displayManager.enable = mkOption {
-  type = with types; nullOr (enum [ &quot;gdm&quot; ]);
-};
-</programlisting>
-          <anchor xml:id="ex-option-declaration-eot-backend-sddm" />
-          <para>
-            <emphasis role="strong">Example: Extending
-            <literal>services.xserver.displayManager.enable</literal> in
-            the <literal>sddm</literal> module</emphasis>
-          </para>
-          <programlisting language="bash">
-services.xserver.displayManager.enable = mkOption {
-  type = with types; nullOr (enum [ &quot;sddm&quot; ]);
-};
-</programlisting>
-          <para>
-            The placeholder declaration is a standard
-            <literal>mkOption</literal> declaration, but it is important
-            that extensible option declarations only use the
-            <literal>type</literal> argument.
-          </para>
-          <para>
-            Extensible option types work with any of the composed
-            variants of <literal>enum</literal> such as
-            <literal>with types; nullOr (enum [ &quot;foo&quot; &quot;bar&quot; ])</literal>
-            or
-            <literal>with types; listOf (enum [ &quot;foo&quot; &quot;bar&quot; ])</literal>.
-          </para>
-        </section>
-      </section>
-    </section>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/option-def.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/option-def.section.xml
deleted file mode 100644
index 8c9ef181affd..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/option-def.section.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-option-definitions">
-  <title>Option Definitions</title>
-  <para>
-    Option definitions are generally straight-forward bindings of values
-    to option names, like
-  </para>
-  <programlisting language="bash">
-config = {
-  services.httpd.enable = true;
-};
-</programlisting>
-  <para>
-    However, sometimes you need to wrap an option definition or set of
-    option definitions in a <emphasis>property</emphasis> to achieve
-    certain effects:
-  </para>
-  <section xml:id="sec-option-definitions-delaying-conditionals">
-    <title>Delaying Conditionals</title>
-    <para>
-      If a set of option definitions is conditional on the value of
-      another option, you may need to use <literal>mkIf</literal>.
-      Consider, for instance:
-    </para>
-    <programlisting language="bash">
-config = if config.services.httpd.enable then {
-  environment.systemPackages = [ ... ];
-  ...
-} else {};
-</programlisting>
-    <para>
-      This definition will cause Nix to fail with an <quote>infinite
-      recursion</quote> error. Why? Because the value of
-      <literal>config.services.httpd.enable</literal> depends on the
-      value being constructed here. After all, you could also write the
-      clearly circular and contradictory:
-    </para>
-    <programlisting language="bash">
-config = if config.services.httpd.enable then {
-  services.httpd.enable = false;
-} else {
-  services.httpd.enable = true;
-};
-</programlisting>
-    <para>
-      The solution is to write:
-    </para>
-    <programlisting language="bash">
-config = mkIf config.services.httpd.enable {
-  environment.systemPackages = [ ... ];
-  ...
-};
-</programlisting>
-    <para>
-      The special function <literal>mkIf</literal> causes the evaluation
-      of the conditional to be <quote>pushed down</quote> into the
-      individual definitions, as if you had written:
-    </para>
-    <programlisting language="bash">
-config = {
-  environment.systemPackages = if config.services.httpd.enable then [ ... ] else [];
-  ...
-};
-</programlisting>
-  </section>
-  <section xml:id="sec-option-definitions-setting-priorities">
-    <title>Setting Priorities</title>
-    <para>
-      A module can override the definitions of an option in other
-      modules by setting a <emphasis>priority</emphasis>. All option
-      definitions that do not have the lowest priority value are
-      discarded. By default, option definitions have priority 1000. You
-      can specify an explicit priority by using
-      <literal>mkOverride</literal>, e.g.
-    </para>
-    <programlisting language="bash">
-services.openssh.enable = mkOverride 10 false;
-</programlisting>
-    <para>
-      This definition causes all other definitions with priorities above
-      10 to be discarded. The function <literal>mkForce</literal> is
-      equal to <literal>mkOverride 50</literal>.
-    </para>
-  </section>
-  <section xml:id="sec-option-definitions-merging">
-    <title>Merging Configurations</title>
-    <para>
-      In conjunction with <literal>mkIf</literal>, it is sometimes
-      useful for a module to return multiple sets of option definitions,
-      to be merged together as if they were declared in separate
-      modules. This can be done using <literal>mkMerge</literal>:
-    </para>
-    <programlisting language="bash">
-config = mkMerge
-  [ # Unconditional stuff.
-    { environment.systemPackages = [ ... ];
-    }
-    # Conditional stuff.
-    (mkIf config.services.bla.enable {
-      environment.systemPackages = [ ... ];
-    })
-  ];
-</programlisting>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/option-types.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/option-types.section.xml
deleted file mode 100644
index 929d5302ed41..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/option-types.section.xml
+++ /dev/null
@@ -1,1081 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-option-types">
-  <title>Options Types</title>
-  <para>
-    Option types are a way to put constraints on the values a module
-    option can take. Types are also responsible of how values are merged
-    in case of multiple value definitions.
-  </para>
-  <section xml:id="sec-option-types-basic">
-    <title>Basic Types</title>
-    <para>
-      Basic types are the simplest available types in the module system.
-      Basic types include multiple string types that mainly differ in
-      how definition merging is handled.
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>types.bool</literal>
-        </term>
-        <listitem>
-          <para>
-            A boolean, its values can be <literal>true</literal> or
-            <literal>false</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.path</literal>
-        </term>
-        <listitem>
-          <para>
-            A filesystem path is anything that starts with a slash when
-            coerced to a string. Even if derivations can be considered
-            as paths, the more specific <literal>types.package</literal>
-            should be preferred.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.package</literal>
-        </term>
-        <listitem>
-          <para>
-            A top-level store path. This can be an attribute set
-            pointing to a store path, like a derivation or a flake
-            input.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.anything</literal>
-        </term>
-        <listitem>
-          <para>
-            A type that accepts any value and recursively merges
-            attribute sets together. This type is recommended when the
-            option type is unknown.
-          </para>
-          <anchor xml:id="ex-types-anything" />
-          <para>
-            <emphasis role="strong">Example:
-            <literal>types.anything</literal> Example</emphasis>
-          </para>
-          <para>
-            Two definitions of this type like
-          </para>
-          <programlisting language="bash">
-{
-  str = lib.mkDefault &quot;foo&quot;;
-  pkg.hello = pkgs.hello;
-  fun.fun = x: x + 1;
-}
-</programlisting>
-          <programlisting language="bash">
-{
-  str = lib.mkIf true &quot;bar&quot;;
-  pkg.gcc = pkgs.gcc;
-  fun.fun = lib.mkForce (x: x + 2);
-}
-</programlisting>
-          <para>
-            will get merged to
-          </para>
-          <programlisting language="bash">
-{
-  str = &quot;bar&quot;;
-  pkg.gcc = pkgs.gcc;
-  pkg.hello = pkgs.hello;
-  fun.fun = x: x + 2;
-}
-</programlisting>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.raw</literal>
-        </term>
-        <listitem>
-          <para>
-            A type which doesn’t do any checking, merging or nested
-            evaluation. It accepts a single arbitrary value that is not
-            recursed into, making it useful for values coming from
-            outside the module system, such as package sets or arbitrary
-            data. Options of this type are still evaluated according to
-            priorities and conditionals, so <literal>mkForce</literal>,
-            <literal>mkIf</literal> and co. still work on the option
-            value itself, but not for any value nested within it. This
-            type should only be used when checking, merging and nested
-            evaluation are not desirable.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.optionType</literal>
-        </term>
-        <listitem>
-          <para>
-            The type of an option’s type. Its merging operation ensures
-            that nested options have the correct file location
-            annotated, and that if possible, multiple option definitions
-            are correctly merged together. The main use case is as the
-            type of the <literal>_module.freeformType</literal> option.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.attrs</literal>
-        </term>
-        <listitem>
-          <para>
-            A free-form attribute set.
-          </para>
-          <warning>
-            <para>
-              This type will be deprecated in the future because it
-              doesn't recurse into attribute sets, silently drops
-              earlier attribute definitions, and doesn't discharge
-              <literal>lib.mkDefault</literal>,
-              <literal>lib.mkIf</literal> and co. For allowing arbitrary
-              attribute sets, prefer
-              <literal>types.attrsOf types.anything</literal> instead
-              which doesn't have these problems.
-            </para>
-          </warning>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-    <para>
-      Integer-related types:
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>types.int</literal>
-        </term>
-        <listitem>
-          <para>
-            A signed integer.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.ints.{s8, s16, s32}</literal>
-        </term>
-        <listitem>
-          <para>
-            Signed integers with a fixed length (8, 16 or 32 bits). They
-            go from −2^n/2 to 2^n/2−1 respectively (e.g.
-            <literal>−128</literal> to <literal>127</literal> for 8
-            bits).
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.ints.unsigned</literal>
-        </term>
-        <listitem>
-          <para>
-            An unsigned integer (that is &gt;= 0).
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.ints.{u8, u16, u32}</literal>
-        </term>
-        <listitem>
-          <para>
-            Unsigned integers with a fixed length (8, 16 or 32 bits).
-            They go from 0 to 2^n−1 respectively (e.g.
-            <literal>0</literal> to <literal>255</literal> for 8 bits).
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.ints.positive</literal>
-        </term>
-        <listitem>
-          <para>
-            A positive integer (that is &gt; 0).
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.port</literal>
-        </term>
-        <listitem>
-          <para>
-            A port number. This type is an alias to
-            <literal>types.ints.u16</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-    <para>
-      String-related types:
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>types.str</literal>
-        </term>
-        <listitem>
-          <para>
-            A string. Multiple definitions cannot be merged.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.lines</literal>
-        </term>
-        <listitem>
-          <para>
-            A string. Multiple definitions are concatenated with a new
-            line <literal>&quot;\n&quot;</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.commas</literal>
-        </term>
-        <listitem>
-          <para>
-            A string. Multiple definitions are concatenated with a comma
-            <literal>&quot;,&quot;</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.envVar</literal>
-        </term>
-        <listitem>
-          <para>
-            A string. Multiple definitions are concatenated with a
-            collon <literal>&quot;:&quot;</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.strMatching</literal>
-        </term>
-        <listitem>
-          <para>
-            A string matching a specific regular expression. Multiple
-            definitions cannot be merged. The regular expression is
-            processed using <literal>builtins.match</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-  </section>
-  <section xml:id="sec-option-types-value">
-    <title>Value Types</title>
-    <para>
-      Value types are types that take a value parameter.
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>types.enum</literal>
-          <emphasis><literal>l</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            One element of the list
-            <emphasis><literal>l</literal></emphasis>, e.g.
-            <literal>types.enum [ &quot;left&quot; &quot;right&quot; ]</literal>.
-            Multiple definitions cannot be merged.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.separatedString</literal>
-          <emphasis><literal>sep</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            A string with a custom separator
-            <emphasis><literal>sep</literal></emphasis>, e.g.
-            <literal>types.separatedString &quot;|&quot;</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.ints.between</literal>
-          <emphasis><literal>lowest highest</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            An integer between
-            <emphasis><literal>lowest</literal></emphasis> and
-            <emphasis><literal>highest</literal></emphasis> (both
-            inclusive). Useful for creating types like
-            <literal>types.port</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.submodule</literal>
-          <emphasis><literal>o</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            A set of sub options
-            <emphasis><literal>o</literal></emphasis>.
-            <emphasis><literal>o</literal></emphasis> can be an
-            attribute set, a function returning an attribute set, or a
-            path to a file containing such a value. Submodules are used
-            in composed types to create modular options. This is
-            equivalent to
-            <literal>types.submoduleWith { modules = toList o; shorthandOnlyDefinesConfig = true; }</literal>.
-            Submodules are detailed in
-            <link linkend="section-option-types-submodule">Submodule</link>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.submoduleWith</literal> {
-          <emphasis><literal>modules</literal></emphasis>,
-          <emphasis><literal>specialArgs</literal></emphasis> ? {},
-          <emphasis><literal>shorthandOnlyDefinesConfig</literal></emphasis>
-          ? false }
-        </term>
-        <listitem>
-          <para>
-            Like <literal>types.submodule</literal>, but more flexible
-            and with better defaults. It has parameters
-          </para>
-          <itemizedlist>
-            <listitem>
-              <para>
-                <emphasis><literal>modules</literal></emphasis> A list
-                of modules to use by default for this submodule type.
-                This gets combined with all option definitions to build
-                the final list of modules that will be included.
-              </para>
-              <note>
-                <para>
-                  Only options defined with this argument are included
-                  in rendered documentation.
-                </para>
-              </note>
-            </listitem>
-            <listitem>
-              <para>
-                <emphasis><literal>specialArgs</literal></emphasis> An
-                attribute set of extra arguments to be passed to the
-                module functions. The option
-                <literal>_module.args</literal> should be used instead
-                for most arguments since it allows overriding.
-                <emphasis><literal>specialArgs</literal></emphasis>
-                should only be used for arguments that can't go through
-                the module fixed-point, because of infinite recursion or
-                other problems. An example is overriding the
-                <literal>lib</literal> argument, because
-                <literal>lib</literal> itself is used to define
-                <literal>_module.args</literal>, which makes using
-                <literal>_module.args</literal> to define it impossible.
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                <emphasis><literal>shorthandOnlyDefinesConfig</literal></emphasis>
-                Whether definitions of this type should default to the
-                <literal>config</literal> section of a module (see
-                <link linkend="ex-module-syntax">Example: Structure of
-                NixOS Modules</link>) if it is an attribute set.
-                Enabling this only has a benefit when the submodule
-                defines an option named <literal>config</literal> or
-                <literal>options</literal>. In such a case it would
-                allow the option to be set with
-                <literal>the-submodule.config = &quot;value&quot;</literal>
-                instead of requiring
-                <literal>the-submodule.config.config = &quot;value&quot;</literal>.
-                This is because only when modules
-                <emphasis>don't</emphasis> set the
-                <literal>config</literal> or <literal>options</literal>
-                keys, all keys are interpreted as option definitions in
-                the <literal>config</literal> section. Enabling this
-                option implicitly puts all attributes in the
-                <literal>config</literal> section.
-              </para>
-              <para>
-                With this option enabled, defining a
-                non-<literal>config</literal> section requires using a
-                function:
-                <literal>the-submodule = { ... }: { options = { ... }; }</literal>.
-              </para>
-            </listitem>
-          </itemizedlist>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.deferredModule</literal>
-        </term>
-        <listitem>
-          <para>
-            Whereas <literal>submodule</literal> represents an option
-            tree, <literal>deferredModule</literal> represents a module
-            value, such as a module file or a configuration.
-          </para>
-          <para>
-            It can be set multiple times.
-          </para>
-          <para>
-            Module authors can use its value in
-            <literal>imports</literal>, in
-            <literal>submoduleWith</literal><quote>s
-            <literal>modules</literal> or in
-            <literal>evalModules</literal></quote>
-            <literal>modules</literal> parameter, among other places.
-          </para>
-          <para>
-            Note that <literal>imports</literal> must be evaluated
-            before the module fixpoint. Because of this, deferred
-            modules can only be imported into <quote>other</quote>
-            fixpoints, such as submodules.
-          </para>
-          <para>
-            One use case for this type is the type of a
-            <quote>default</quote> module that allow the user to affect
-            all submodules in an <literal>attrsOf submodule</literal> at
-            once. This is more convenient and discoverable than
-            expecting the module user to type-merge with the
-            <literal>attrsOf submodule</literal> option.
-          </para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-  </section>
-  <section xml:id="sec-option-types-composed">
-    <title>Composed Types</title>
-    <para>
-      Composed types are types that take a type as parameter.
-      <literal>listOf int</literal> and
-      <literal>either int str</literal> are examples of composed types.
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>types.listOf</literal>
-          <emphasis><literal>t</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            A list of <emphasis><literal>t</literal></emphasis> type,
-            e.g. <literal>types.listOf int</literal>. Multiple
-            definitions are merged with list concatenation.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.attrsOf</literal>
-          <emphasis><literal>t</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            An attribute set of where all the values are of
-            <emphasis><literal>t</literal></emphasis> type. Multiple
-            definitions result in the joined attribute set.
-          </para>
-          <note>
-            <para>
-              This type is <emphasis>strict</emphasis> in its values,
-              which in turn means attributes cannot depend on other
-              attributes. See <literal> types.lazyAttrsOf</literal> for
-              a lazy version.
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.lazyAttrsOf</literal>
-          <emphasis><literal>t</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            An attribute set of where all the values are of
-            <emphasis><literal>t</literal></emphasis> type. Multiple
-            definitions result in the joined attribute set. This is the
-            lazy version of <literal>types.attrsOf </literal>, allowing
-            attributes to depend on each other.
-          </para>
-          <warning>
-            <para>
-              This version does not fully support conditional
-              definitions! With an option <literal>foo</literal> of this
-              type and a definition
-              <literal>foo.attr = lib.mkIf false 10</literal>,
-              evaluating <literal>foo ? attr</literal> will return
-              <literal>true</literal> even though it should be false.
-              Accessing the value will then throw an error. For types
-              <emphasis><literal>t</literal></emphasis> that have an
-              <literal>emptyValue</literal> defined, that value will be
-              returned instead of throwing an error. So if the type of
-              <literal>foo.attr</literal> was
-              <literal>lazyAttrsOf (nullOr int)</literal>,
-              <literal>null</literal> would be returned instead for the
-              same <literal>mkIf false</literal> definition.
-            </para>
-          </warning>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.nullOr</literal>
-          <emphasis><literal>t</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            <literal>null</literal> or type
-            <emphasis><literal>t</literal></emphasis>. Multiple
-            definitions are merged according to type
-            <emphasis><literal>t</literal></emphasis>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.uniq</literal>
-          <emphasis><literal>t</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            Ensures that type <emphasis><literal>t</literal></emphasis>
-            cannot be merged. It is used to ensure option definitions
-            are declared only once.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.unique</literal>
-          <literal>{ message = m }</literal>
-          <emphasis><literal>t</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            Ensures that type <emphasis><literal>t</literal></emphasis>
-            cannot be merged. Prints the message
-            <emphasis><literal>m</literal></emphasis>, after the line
-            <literal>The option &lt;option path&gt; is defined multiple times.</literal>
-            and before a list of definition locations.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.either</literal>
-          <emphasis><literal>t1 t2</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            Type <emphasis><literal>t1</literal></emphasis> or type
-            <emphasis><literal>t2</literal></emphasis>, e.g.
-            <literal>with types; either int str</literal>. Multiple
-            definitions cannot be merged.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.oneOf</literal> [
-          <emphasis><literal>t1 t2</literal></emphasis> ... ]
-        </term>
-        <listitem>
-          <para>
-            Type <emphasis><literal>t1</literal></emphasis> or type
-            <emphasis><literal>t2</literal></emphasis> and so forth,
-            e.g. <literal>with types; oneOf [ int str bool ]</literal>.
-            Multiple definitions cannot be merged.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>types.coercedTo</literal>
-          <emphasis><literal>from f to</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            Type <emphasis><literal>to</literal></emphasis> or type
-            <emphasis><literal>from</literal></emphasis> which will be
-            coerced to type <emphasis><literal>to</literal></emphasis>
-            using function <emphasis><literal>f</literal></emphasis>
-            which takes an argument of type
-            <emphasis><literal>from</literal></emphasis> and return a
-            value of type <emphasis><literal>to</literal></emphasis>.
-            Can be used to preserve backwards compatibility of an option
-            if its type was changed.
-          </para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-  </section>
-  <section xml:id="section-option-types-submodule">
-    <title>Submodule</title>
-    <para>
-      <literal>submodule</literal> is a very powerful type that defines
-      a set of sub-options that are handled like a separate module.
-    </para>
-    <para>
-      It takes a parameter <emphasis><literal>o</literal></emphasis>,
-      that should be a set, or a function returning a set with an
-      <literal>options</literal> key defining the sub-options. Submodule
-      option definitions are type-checked accordingly to the
-      <literal>options</literal> declarations. Of course, you can nest
-      submodule option definitons for even higher modularity.
-    </para>
-    <para>
-      The option set can be defined directly
-      (<link linkend="ex-submodule-direct">Example: Directly defined
-      submodule</link>) or as reference
-      (<link linkend="ex-submodule-reference">Example: Submodule defined
-      as a reference</link>).
-    </para>
-    <para>
-      Note that even if your submodule’s options all have a default
-      value, you will still need to provide a default value (e.g. an
-      empty attribute set) if you want to allow users to leave it
-      undefined.
-    </para>
-    <anchor xml:id="ex-submodule-direct" />
-    <para>
-      <emphasis role="strong">Example: Directly defined
-      submodule</emphasis>
-    </para>
-    <programlisting language="bash">
-options.mod = mkOption {
-  description = &quot;submodule example&quot;;
-  type = with types; submodule {
-    options = {
-      foo = mkOption {
-        type = int;
-      };
-      bar = mkOption {
-        type = str;
-      };
-    };
-  };
-};
-</programlisting>
-    <anchor xml:id="ex-submodule-reference" />
-    <para>
-      <emphasis role="strong">Example: Submodule defined as a
-      reference</emphasis>
-    </para>
-    <programlisting language="bash">
-let
-  modOptions = {
-    options = {
-      foo = mkOption {
-        type = int;
-      };
-      bar = mkOption {
-        type = int;
-      };
-    };
-  };
-in
-options.mod = mkOption {
-  description = &quot;submodule example&quot;;
-  type = with types; submodule modOptions;
-};
-</programlisting>
-    <para>
-      The <literal>submodule</literal> type is especially interesting
-      when used with composed types like <literal>attrsOf</literal> or
-      <literal>listOf</literal>. When composed with
-      <literal>listOf</literal>
-      (<link linkend="ex-submodule-listof-declaration">Example:
-      Declaration of a list of submodules</link>),
-      <literal>submodule</literal> allows multiple definitions of the
-      submodule option set
-      (<link linkend="ex-submodule-listof-definition">Example:
-      Definition of a list of submodules</link>).
-    </para>
-    <anchor xml:id="ex-submodule-listof-declaration" />
-    <para>
-      <emphasis role="strong">Example: Declaration of a list of
-      submodules</emphasis>
-    </para>
-    <programlisting language="bash">
-options.mod = mkOption {
-  description = &quot;submodule example&quot;;
-  type = with types; listOf (submodule {
-    options = {
-      foo = mkOption {
-        type = int;
-      };
-      bar = mkOption {
-        type = str;
-      };
-    };
-  });
-};
-</programlisting>
-    <anchor xml:id="ex-submodule-listof-definition" />
-    <para>
-      <emphasis role="strong">Example: Definition of a list of
-      submodules</emphasis>
-    </para>
-    <programlisting language="bash">
-config.mod = [
-  { foo = 1; bar = &quot;one&quot;; }
-  { foo = 2; bar = &quot;two&quot;; }
-];
-</programlisting>
-    <para>
-      When composed with <literal>attrsOf</literal>
-      (<link linkend="ex-submodule-attrsof-declaration">Example:
-      Declaration of attribute sets of submodules</link>),
-      <literal>submodule</literal> allows multiple named definitions of
-      the submodule option set
-      (<link linkend="ex-submodule-attrsof-definition">Example:
-      Definition of attribute sets of submodules</link>).
-    </para>
-    <anchor xml:id="ex-submodule-attrsof-declaration" />
-    <para>
-      <emphasis role="strong">Example: Declaration of attribute sets of
-      submodules</emphasis>
-    </para>
-    <programlisting language="bash">
-options.mod = mkOption {
-  description = &quot;submodule example&quot;;
-  type = with types; attrsOf (submodule {
-    options = {
-      foo = mkOption {
-        type = int;
-      };
-      bar = mkOption {
-        type = str;
-      };
-    };
-  });
-};
-</programlisting>
-    <anchor xml:id="ex-submodule-attrsof-definition" />
-    <para>
-      <emphasis role="strong">Example: Definition of attribute sets of
-      submodules</emphasis>
-    </para>
-    <programlisting language="bash">
-config.mod.one = { foo = 1; bar = &quot;one&quot;; };
-config.mod.two = { foo = 2; bar = &quot;two&quot;; };
-</programlisting>
-  </section>
-  <section xml:id="sec-option-types-extending">
-    <title>Extending types</title>
-    <para>
-      Types are mainly characterized by their <literal>check</literal>
-      and <literal>merge</literal> functions.
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>check</literal>
-        </term>
-        <listitem>
-          <para>
-            The function to type check the value. Takes a value as
-            parameter and return a boolean. It is possible to extend a
-            type check with the <literal>addCheck</literal> function
-            (<link linkend="ex-extending-type-check-1">Example: Adding a
-            type check</link>), or to fully override the check function
-            (<link linkend="ex-extending-type-check-2">Example:
-            Overriding a type check</link>).
-          </para>
-          <anchor xml:id="ex-extending-type-check-1" />
-          <para>
-            <emphasis role="strong">Example: Adding a type
-            check</emphasis>
-          </para>
-          <programlisting language="bash">
-byte = mkOption {
-  description = &quot;An integer between 0 and 255.&quot;;
-  type = types.addCheck types.int (x: x &gt;= 0 &amp;&amp; x &lt;= 255);
-};
-</programlisting>
-          <anchor xml:id="ex-extending-type-check-2" />
-          <para>
-            <emphasis role="strong">Example: Overriding a type
-            check</emphasis>
-          </para>
-          <programlisting language="bash">
-nixThings = mkOption {
-  description = &quot;words that start with 'nix'&quot;;
-  type = types.str // {
-    check = (x: lib.hasPrefix &quot;nix&quot; x)
-  };
-};
-</programlisting>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>merge</literal>
-        </term>
-        <listitem>
-          <para>
-            Function to merge the options values when multiple values
-            are set. The function takes two parameters,
-            <literal>loc</literal> the option path as a list of strings,
-            and <literal>defs</literal> the list of defined values as a
-            list. It is possible to override a type merge function for
-            custom needs.
-          </para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-  </section>
-  <section xml:id="sec-option-types-custom">
-    <title>Custom Types</title>
-    <para>
-      Custom types can be created with the
-      <literal>mkOptionType</literal> function. As type creation
-      includes some more complex topics such as submodule handling, it
-      is recommended to get familiar with <literal>types.nix</literal>
-      code before creating a new type.
-    </para>
-    <para>
-      The only required parameter is <literal>name</literal>.
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>name</literal>
-        </term>
-        <listitem>
-          <para>
-            A string representation of the type function name.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>definition</literal>
-        </term>
-        <listitem>
-          <para>
-            Description of the type used in documentation. Give
-            information of the type and any of its arguments.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>check</literal>
-        </term>
-        <listitem>
-          <para>
-            A function to type check the definition value. Takes the
-            definition value as a parameter and returns a boolean
-            indicating the type check result, <literal>true</literal>
-            for success and <literal>false</literal> for failure.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>merge</literal>
-        </term>
-        <listitem>
-          <para>
-            A function to merge multiple definitions values. Takes two
-            parameters:
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <emphasis><literal>loc</literal></emphasis>
-              </term>
-              <listitem>
-                <para>
-                  The option path as a list of strings, e.g.
-                  <literal>[&quot;boot&quot; &quot;loader &quot;grub&quot; &quot;enable&quot;]</literal>.
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <emphasis><literal>defs</literal></emphasis>
-              </term>
-              <listitem>
-                <para>
-                  The list of sets of defined <literal>value</literal>
-                  and <literal>file</literal> where the value was
-                  defined, e.g.
-                  <literal>[ { file = &quot;/foo.nix&quot;; value = 1; } { file = &quot;/bar.nix&quot;; value = 2 } ]</literal>.
-                  The <literal>merge</literal> function should return
-                  the merged value or throw an error in case the values
-                  are impossible or not meant to be merged.
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>getSubOptions</literal>
-        </term>
-        <listitem>
-          <para>
-            For composed types that can take a submodule as type
-            parameter, this function generate sub-options documentation.
-            It takes the current option prefix as a list and return the
-            set of sub-options. Usually defined in a recursive manner by
-            adding a term to the prefix, e.g.
-            <literal>prefix: elemType.getSubOptions (prefix ++ [&quot;prefix&quot;])</literal>
-            where
-            <emphasis><literal>&quot;prefix&quot;</literal></emphasis>
-            is the newly added prefix.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>getSubModules</literal>
-        </term>
-        <listitem>
-          <para>
-            For composed types that can take a submodule as type
-            parameter, this function should return the type parameters
-            submodules. If the type parameter is called
-            <literal>elemType</literal>, the function should just
-            recursively look into submodules by returning
-            <literal>elemType.getSubModules;</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>substSubModules</literal>
-        </term>
-        <listitem>
-          <para>
-            For composed types that can take a submodule as type
-            parameter, this function can be used to substitute the
-            parameter of a submodule type. It takes a module as
-            parameter and return the type with the submodule options
-            substituted. It is usually defined as a type function call
-            with a recursive call to <literal>substSubModules</literal>,
-            e.g for a type <literal>composedType</literal> that take an
-            <literal>elemtype</literal> type parameter, this function
-            should be defined as
-            <literal>m: composedType (elemType.substSubModules m)</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>typeMerge</literal>
-        </term>
-        <listitem>
-          <para>
-            A function to merge multiple type declarations. Takes the
-            type to merge <literal>functor</literal> as parameter. A
-            <literal>null</literal> return value means that type cannot
-            be merged.
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <emphasis><literal>f</literal></emphasis>
-              </term>
-              <listitem>
-                <para>
-                  The type to merge <literal>functor</literal>.
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-          <para>
-            Note: There is a generic <literal>defaultTypeMerge</literal>
-            that work with most of value and composed types.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>functor</literal>
-        </term>
-        <listitem>
-          <para>
-            An attribute set representing the type. It is used for type
-            operations and has the following keys:
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <literal>type</literal>
-              </term>
-              <listitem>
-                <para>
-                  The type function.
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>wrapped</literal>
-              </term>
-              <listitem>
-                <para>
-                  Holds the type parameter for composed types.
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>payload</literal>
-              </term>
-              <listitem>
-                <para>
-                  Holds the value parameter for value types. The types
-                  that have a <literal>payload</literal> are the
-                  <literal>enum</literal>,
-                  <literal>separatedString</literal> and
-                  <literal>submodule</literal> types.
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>binOp</literal>
-              </term>
-              <listitem>
-                <para>
-                  A binary operation that can merge the payloads of two
-                  same types. Defined as a function that take two
-                  payloads as parameters and return the payloads merged.
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/replace-modules.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/replace-modules.section.xml
deleted file mode 100644
index cf8a39ba844f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/replace-modules.section.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-replace-modules">
-  <title>Replace Modules</title>
-  <para>
-    Modules that are imported can also be disabled. The option
-    declarations, config implementation and the imports of a disabled
-    module will be ignored, allowing another to take it's place. This
-    can be used to import a set of modules from another channel while
-    keeping the rest of the system on a stable release.
-  </para>
-  <para>
-    <literal>disabledModules</literal> is a top level attribute like
-    <literal>imports</literal>, <literal>options</literal> and
-    <literal>config</literal>. It contains a list of modules that will
-    be disabled. This can either be the full path to the module or a
-    string with the filename relative to the modules path (eg.
-    &lt;nixpkgs/nixos/modules&gt; for nixos).
-  </para>
-  <para>
-    This example will replace the existing postgresql module with the
-    version defined in the nixos-unstable channel while keeping the rest
-    of the modules and packages from the original nixos channel. This
-    only overrides the module definition, this won't use postgresql from
-    nixos-unstable unless explicitly configured to do so.
-  </para>
-  <programlisting language="bash">
-{ config, lib, pkgs, ... }:
-
-{
-  disabledModules = [ &quot;services/databases/postgresql.nix&quot; ];
-
-  imports =
-    [ # Use postgresql service from nixos-unstable channel.
-      # sudo nix-channel --add https://nixos.org/channels/nixos-unstable nixos-unstable
-      &lt;nixos-unstable/nixos/modules/services/databases/postgresql.nix&gt;
-    ];
-
-  services.postgresql.enable = true;
-}
-</programlisting>
-  <para>
-    This example shows how to define a custom module as a replacement
-    for an existing module. Importing this module will disable the
-    original module without having to know it's implementation details.
-  </para>
-  <programlisting language="bash">
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.programs.man;
-in
-
-{
-  disabledModules = [ &quot;services/programs/man.nix&quot; ];
-
-  options = {
-    programs.man.enable = mkOption {
-      type = types.bool;
-      default = true;
-      description = &quot;Whether to enable manual pages.&quot;;
-    };
-  };
-
-  config = mkIf cfg.enabled {
-    warnings = [ &quot;disabled manpages for production deployments.&quot; ];
-  };
-}
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
deleted file mode 100644
index 0e47350a0d24..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-running-nixos-tests-interactively">
-  <title>Running Tests interactively</title>
-  <para>
-    The test itself can be run interactively. This is particularly
-    useful when developing or debugging a test:
-  </para>
-  <programlisting>
-$ nix-build . -A nixosTests.login.driverInteractive
-$ ./result/bin/nixos-test-driver
-[...]
-&gt;&gt;&gt;
-</programlisting>
-  <para>
-    You can then take any Python statement, e.g.
-  </para>
-  <programlisting language="python">
-&gt;&gt;&gt; start_all()
-&gt;&gt;&gt; test_script()
-&gt;&gt;&gt; machine.succeed(&quot;touch /tmp/foo&quot;)
-&gt;&gt;&gt; print(machine.succeed(&quot;pwd&quot;)) # Show stdout of command
-</programlisting>
-  <para>
-    The function <literal>test_script</literal> executes the entire test
-    script and drops you back into the test driver command line upon its
-    completion. This allows you to inspect the state of the VMs after
-    the test (e.g. to debug the test script).
-  </para>
-  <para>
-    You can re-use the VM states coming from a previous run by setting
-    the <literal>--keep-vm-state</literal> flag.
-  </para>
-  <programlisting>
-$ ./result/bin/nixos-test-driver --keep-vm-state
-</programlisting>
-  <para>
-    The machine state is stored in the
-    <literal>$TMPDIR/vm-state-machinename</literal> directory.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
deleted file mode 100644
index da2e5076c956..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-running-nixos-tests">
-  <title>Running Tests</title>
-  <para>
-    You can run tests using <literal>nix-build</literal>. For example,
-    to run the test
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix"><literal>login.nix</literal></link>,
-    you just do:
-  </para>
-  <programlisting>
-$ nix-build '&lt;nixpkgs/nixos/tests/login.nix&gt;'
-</programlisting>
-  <para>
-    or, if you don’t want to rely on <literal>NIX_PATH</literal>:
-  </para>
-  <programlisting>
-$ cd /my/nixpkgs/nixos/tests
-$ nix-build login.nix
-…
-running the VM test script
-machine: QEMU running (pid 8841)
-…
-6 out of 6 tests succeeded
-</programlisting>
-  <para>
-    After building/downloading all required dependencies, this will
-    perform a build that starts a QEMU/KVM virtual machine containing a
-    NixOS system. The virtual machine mounts the Nix store of the host;
-    this makes VM creation very fast, as no disk image needs to be
-    created. Afterwards, you can view a log of the test:
-  </para>
-  <programlisting>
-$ nix-store --read-log result
-</programlisting>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/settings-options.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/settings-options.section.xml
deleted file mode 100644
index d26dd96243db..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/settings-options.section.xml
+++ /dev/null
@@ -1,421 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-settings-options">
-  <title>Options for Program Settings</title>
-  <para>
-    Many programs have configuration files where program-specific
-    settings can be declared. File formats can be separated into two
-    categories:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Nix-representable ones: These can trivially be mapped to a
-        subset of Nix syntax. E.g. JSON is an example, since its values
-        like <literal>{&quot;foo&quot;:{&quot;bar&quot;:10}}</literal>
-        can be mapped directly to Nix:
-        <literal>{ foo = { bar = 10; }; }</literal>. Other examples are
-        INI, YAML and TOML. The following section explains the
-        convention for these settings.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Non-nix-representable ones: These can't be trivially mapped to a
-        subset of Nix syntax. Most generic programming languages are in
-        this group, e.g. bash, since the statement
-        <literal>if true; then echo hi; fi</literal> doesn't have a
-        trivial representation in Nix.
-      </para>
-      <para>
-        Currently there are no fixed conventions for these, but it is
-        common to have a <literal>configFile</literal> option for
-        setting the configuration file path directly. The default value
-        of <literal>configFile</literal> can be an auto-generated file,
-        with convenient options for controlling the contents. For
-        example an option of type <literal>attrsOf str</literal> can be
-        used for representing environment variables which generates a
-        section like <literal>export FOO=&quot;foo&quot;</literal>.
-        Often it can also be useful to also include an
-        <literal>extraConfig</literal> option of type
-        <literal>lines</literal> to allow arbitrary text after the
-        autogenerated part of the file.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <section xml:id="sec-settings-nix-representable">
-    <title>Nix-representable Formats (JSON, YAML, TOML, INI,
-    ...)</title>
-    <para>
-      By convention, formats like this are handled with a generic
-      <literal>settings</literal> option, representing the full program
-      configuration as a Nix value. The type of this option should
-      represent the format. The most common formats have a predefined
-      type and string generator already declared under
-      <literal>pkgs.formats</literal>:
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>pkgs.formats.javaProperties</literal> {
-          <emphasis><literal>comment</literal></emphasis> ?
-          <literal>&quot;Generated with Nix&quot;</literal> }
-        </term>
-        <listitem>
-          <para>
-            A function taking an attribute set with values
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <literal>comment</literal>
-              </term>
-              <listitem>
-                <para>
-                  A string to put at the start of the file in a comment.
-                  It can have multiple lines.
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-          <para>
-            It returns the <literal>type</literal>:
-            <literal>attrsOf str</literal> and a function
-            <literal>generate</literal> to build a Java
-            <literal>.properties</literal> file, taking care of the
-            correct escaping, etc.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>pkgs.formats.json</literal> { }
-        </term>
-        <listitem>
-          <para>
-            A function taking an empty attribute set (for future
-            extensibility) and returning a set with JSON-specific
-            attributes <literal>type</literal> and
-            <literal>generate</literal> as specified
-            <link linkend="pkgs-formats-result">below</link>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>pkgs.formats.yaml</literal> { }
-        </term>
-        <listitem>
-          <para>
-            A function taking an empty attribute set (for future
-            extensibility) and returning a set with YAML-specific
-            attributes <literal>type</literal> and
-            <literal>generate</literal> as specified
-            <link linkend="pkgs-formats-result">below</link>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>pkgs.formats.ini</literal> {
-          <emphasis><literal>listsAsDuplicateKeys</literal></emphasis> ?
-          false, <emphasis><literal>listToValue</literal></emphasis> ?
-          null, ... }
-        </term>
-        <listitem>
-          <para>
-            A function taking an attribute set with values
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <literal>listsAsDuplicateKeys</literal>
-              </term>
-              <listitem>
-                <para>
-                  A boolean for controlling whether list values can be
-                  used to represent duplicate INI keys
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>listToValue</literal>
-              </term>
-              <listitem>
-                <para>
-                  A function for turning a list of values into a single
-                  value.
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-          <para>
-            It returns a set with INI-specific attributes
-            <literal>type</literal> and <literal>generate</literal> as
-            specified <link linkend="pkgs-formats-result">below</link>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>pkgs.formats.toml</literal> { }
-        </term>
-        <listitem>
-          <para>
-            A function taking an empty attribute set (for future
-            extensibility) and returning a set with TOML-specific
-            attributes <literal>type</literal> and
-            <literal>generate</literal> as specified
-            <link linkend="pkgs-formats-result">below</link>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>pkgs.formats.elixirConf { elixir ? pkgs.elixir }</literal>
-        </term>
-        <listitem>
-          <para>
-            A function taking an attribute set with values
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <literal>elixir</literal>
-              </term>
-              <listitem>
-                <para>
-                  The Elixir package which will be used to format the
-                  generated output
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-          <para>
-            It returns a set with Elixir-Config-specific attributes
-            <literal>type</literal>, <literal>lib</literal>, and
-            <literal>generate</literal> as specified
-            <link linkend="pkgs-formats-result">below</link>.
-          </para>
-          <para>
-            The <literal>lib</literal> attribute contains functions to
-            be used in settings, for generating special Elixir values:
-          </para>
-          <variablelist>
-            <varlistentry>
-              <term>
-                <literal>mkRaw elixirCode</literal>
-              </term>
-              <listitem>
-                <para>
-                  Outputs the given string as raw Elixir code
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>mkGetEnv { envVariable, fallback ? null }</literal>
-              </term>
-              <listitem>
-                <para>
-                  Makes the configuration fetch an environment variable
-                  at runtime
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>mkAtom atom</literal>
-              </term>
-              <listitem>
-                <para>
-                  Outputs the given string as an Elixir atom, instead of
-                  the default Elixir binary string. Note: lowercase
-                  atoms still needs to be prefixed with
-                  <literal>:</literal>
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>mkTuple array</literal>
-              </term>
-              <listitem>
-                <para>
-                  Outputs the given array as an Elixir tuple, instead of
-                  the default Elixir list
-                </para>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>
-                <literal>mkMap attrset</literal>
-              </term>
-              <listitem>
-                <para>
-                  Outputs the given attribute set as an Elixir map,
-                  instead of the default Elixir keyword list
-                </para>
-              </listitem>
-            </varlistentry>
-          </variablelist>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-    <para xml:id="pkgs-formats-result">
-      These functions all return an attribute set with these values:
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>type</literal>
-        </term>
-        <listitem>
-          <para>
-            A module system type representing a value of the format
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>lib</literal>
-        </term>
-        <listitem>
-          <para>
-            Utility functions for convenience, or special interactions
-            with the format. This attribute is optional. It may contain
-            inside a <literal>types</literal> attribute containing types
-            specific to this format.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>generate</literal>
-          <emphasis><literal>filename jsonValue</literal></emphasis>
-        </term>
-        <listitem>
-          <para>
-            A function that can render a value of the format to a file.
-            Returns a file path.
-          </para>
-          <note>
-            <para>
-              This function puts the value contents in the Nix store. So
-              this should be avoided for secrets.
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-    <anchor xml:id="ex-settings-nix-representable" />
-    <para>
-      <emphasis role="strong">Example: Module with conventional
-      <literal>settings</literal> option</emphasis>
-    </para>
-    <para>
-      The following shows a module for an example program that uses a
-      JSON configuration file. It demonstrates how above values can be
-      used, along with some other related best practices. See the
-      comments for explanations.
-    </para>
-    <programlisting language="bash">
-{ options, config, lib, pkgs, ... }:
-let
-  cfg = config.services.foo;
-  # Define the settings format used for this program
-  settingsFormat = pkgs.formats.json {};
-in {
-
-  options.services.foo = {
-    enable = lib.mkEnableOption &quot;foo service&quot;;
-
-    settings = lib.mkOption {
-      # Setting this type allows for correct merging behavior
-      type = settingsFormat.type;
-      default = {};
-      description = ''
-        Configuration for foo, see
-        &lt;link xlink:href=&quot;https://example.com/docs/foo&quot;/&gt;
-        for supported settings.
-      '';
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    # We can assign some default settings here to make the service work by just
-    # enabling it. We use `mkDefault` for values that can be changed without
-    # problems
-    services.foo.settings = {
-      # Fails at runtime without any value set
-      log_level = lib.mkDefault &quot;WARN&quot;;
-
-      # We assume systemd's `StateDirectory` is used, so we require this value,
-      # therefore no mkDefault
-      data_path = &quot;/var/lib/foo&quot;;
-
-      # Since we use this to create a user we need to know the default value at
-      # eval time
-      user = lib.mkDefault &quot;foo&quot;;
-    };
-
-    environment.etc.&quot;foo.json&quot;.source =
-      # The formats generator function takes a filename and the Nix value
-      # representing the format value and produces a filepath with that value
-      # rendered in the format
-      settingsFormat.generate &quot;foo-config.json&quot; cfg.settings;
-
-    # We know that the `user` attribute exists because we set a default value
-    # for it above, allowing us to use it without worries here
-    users.users.${cfg.settings.user} = { isSystemUser = true; };
-
-    # ...
-  };
-}
-</programlisting>
-    <section xml:id="sec-settings-attrs-options">
-      <title>Option declarations for attributes</title>
-      <para>
-        Some <literal>settings</literal> attributes may deserve some
-        extra care. They may need a different type, default or merging
-        behavior, or they are essential options that should show their
-        documentation in the manual. This can be done using
-        <xref linkend="sec-freeform-modules" />.
-      </para>
-      <para>
-        We extend above example using freeform modules to declare an
-        option for the port, which will enforce it to be a valid integer
-        and make it show up in the manual.
-      </para>
-      <anchor xml:id="ex-settings-typed-attrs" />
-      <para>
-        <emphasis role="strong">Example: Declaring a type-checked
-        <literal>settings</literal> attribute</emphasis>
-      </para>
-      <programlisting language="bash">
-settings = lib.mkOption {
-  type = lib.types.submodule {
-
-    freeformType = settingsFormat.type;
-
-    # Declare an option for the port such that the type is checked and this option
-    # is shown in the manual.
-    options.port = lib.mkOption {
-      type = lib.types.port;
-      default = 8080;
-      description = ''
-        Which port this service should listen on.
-      '';
-    };
-
-  };
-  default = {};
-  description = ''
-    Configuration for Foo, see
-    &lt;link xlink:href=&quot;https://example.com/docs/foo&quot;/&gt;
-    for supported values.
-  '';
-};
-</programlisting>
-    </section>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/sources.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/sources.chapter.xml
deleted file mode 100644
index aac18c9d06c8..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/sources.chapter.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-getting-sources">
-  <title>Getting the Sources</title>
-  <para>
-    By default, NixOS’s <literal>nixos-rebuild</literal> command uses
-    the NixOS and Nixpkgs sources provided by the
-    <literal>nixos</literal> channel (kept in
-    <literal>/nix/var/nix/profiles/per-user/root/channels/nixos</literal>).
-    To modify NixOS, however, you should check out the latest sources
-    from Git. This is as follows:
-  </para>
-  <programlisting>
-$ git clone https://github.com/NixOS/nixpkgs
-$ cd nixpkgs
-$ git remote update origin
-</programlisting>
-  <para>
-    This will check out the latest Nixpkgs sources to
-    <literal>./nixpkgs</literal> the NixOS sources to
-    <literal>./nixpkgs/nixos</literal>. (The NixOS source tree lives in
-    a subdirectory of the Nixpkgs repository.) The
-    <literal>nixpkgs</literal> repository has branches that correspond
-    to each Nixpkgs/NixOS channel (see <xref linkend="sec-upgrading" />
-    for more information about channels). Thus, the Git branch
-    <literal>origin/nixos-17.03</literal> will contain the latest built
-    and tested version available in the <literal>nixos-17.03</literal>
-    channel.
-  </para>
-  <para>
-    It’s often inconvenient to develop directly on the master branch,
-    since if somebody has just committed (say) a change to GCC, then the
-    binary cache may not have caught up yet and you’ll have to rebuild
-    everything from source. So you may want to create a local branch
-    based on your current NixOS version:
-  </para>
-  <programlisting>
-$ nixos-version
-17.09pre104379.6e0b727 (Hummingbird)
-
-$ git checkout -b local 6e0b727
-</programlisting>
-  <para>
-    Or, to base your local branch on the latest version available in a
-    NixOS channel:
-  </para>
-  <programlisting>
-$ git remote update origin
-$ git checkout -b local origin/nixos-17.03
-</programlisting>
-  <para>
-    (Replace <literal>nixos-17.03</literal> with the name of the channel
-    you want to use.) You can use <literal>git merge</literal> or
-    <literal>git rebase</literal> to keep your local branch in sync with
-    the channel, e.g.
-  </para>
-  <programlisting>
-$ git remote update origin
-$ git merge origin/nixos-17.03
-</programlisting>
-  <para>
-    You can use <literal>git cherry-pick</literal> to copy commits from
-    your local branch to the upstream branch.
-  </para>
-  <para>
-    If you want to rebuild your system using your (modified) sources,
-    you need to tell <literal>nixos-rebuild</literal> about them using
-    the <literal>-I</literal> flag:
-  </para>
-  <programlisting>
-# nixos-rebuild switch -I nixpkgs=/my/sources/nixpkgs
-</programlisting>
-  <para>
-    If you want <literal>nix-env</literal> to use the expressions in
-    <literal>/my/sources</literal>, use
-    <literal>nix-env -f /my/sources/nixpkgs</literal>, or change the
-    default by adding a symlink in <literal>~/.nix-defexpr</literal>:
-  </para>
-  <programlisting>
-$ ln -s /my/sources/nixpkgs ~/.nix-defexpr/nixpkgs
-</programlisting>
-  <para>
-    You may want to delete the symlink
-    <literal>~/.nix-defexpr/channels_root</literal> to prevent root’s
-    NixOS channel from clashing with your own tree (this may break the
-    command-not-found utility though). If you want to go back to the
-    default state, you may just remove the
-    <literal>~/.nix-defexpr</literal> directory completely, log out and
-    log in again and it should have been recreated with a link to the
-    root channels.
-  </para>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/testing-installer.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/testing-installer.chapter.xml
deleted file mode 100644
index 286d49f3c291..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/testing-installer.chapter.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="ch-testing-installer">
-  <title>Testing the Installer</title>
-  <para>
-    Building, burning, and booting from an installation CD is rather
-    tedious, so here is a quick way to see if the installer works
-    properly:
-  </para>
-  <programlisting>
-# mount -t tmpfs none /mnt
-# nixos-generate-config --root /mnt
-$ nix-build '&lt;nixpkgs/nixos&gt;' -A config.system.build.nixos-install
-# ./result/bin/nixos-install
-</programlisting>
-  <para>
-    To start a login shell in the new NixOS installation in
-    <literal>/mnt</literal>:
-  </para>
-  <programlisting>
-$ nix-build '&lt;nixpkgs/nixos&gt;' -A config.system.build.nixos-enter
-# ./result/bin/nixos-enter
-</programlisting>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/unit-handling.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/unit-handling.section.xml
deleted file mode 100644
index 4c980e1213a8..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/unit-handling.section.xml
+++ /dev/null
@@ -1,131 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-unit-handling">
-  <title>Unit handling</title>
-  <para>
-    To figure out what units need to be
-    started/stopped/restarted/reloaded, the script first checks the
-    current state of the system, similar to what
-    <literal>systemctl list-units</literal> shows. For each of the
-    units, the script goes through the following checks:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Is the unit file still in the new system? If not,
-        <emphasis role="strong">stop</emphasis> the service unless it
-        sets <literal>X-StopOnRemoval</literal> in the
-        <literal>[Unit]</literal> section to <literal>false</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Is it a <literal>.target</literal> unit? If so,
-        <emphasis role="strong">start</emphasis> it unless it sets
-        <literal>RefuseManualStart</literal> in the
-        <literal>[Unit]</literal> section to <literal>true</literal> or
-        <literal>X-OnlyManualStart</literal> in the
-        <literal>[Unit]</literal> section to <literal>true</literal>.
-        Also <emphasis role="strong">stop</emphasis> the unit again
-        unless it sets <literal>X-StopOnReconfiguration</literal> to
-        <literal>false</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Are the contents of the unit files different? They are compared
-        by parsing them and comparing their contents. If they are
-        different but only <literal>X-Reload-Triggers</literal> in the
-        <literal>[Unit]</literal> section is changed,
-        <emphasis role="strong">reload</emphasis> the unit. The NixOS
-        module system allows setting these triggers with the option
-        <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.reloadTriggers</link>.
-        There are some additional keys in the <literal>[Unit]</literal>
-        section that are ignored as well. If the unit files differ in
-        any way, the following actions are performed:
-      </para>
-      <itemizedlist>
-        <listitem>
-          <para>
-            <literal>.path</literal> and <literal>.slice</literal> units
-            are ignored. There is no need to restart them since changes
-            in their values are applied by systemd when systemd is
-            reloaded.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            <literal>.mount</literal> units are
-            <emphasis role="strong">reload</emphasis>ed. These mostly
-            come from the <literal>/etc/fstab</literal> parser.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            <literal>.socket</literal> units are currently ignored. This
-            is to be fixed at a later point.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            The rest of the units (mostly <literal>.service</literal>
-            units) are then <emphasis role="strong">reload</emphasis>ed
-            if <literal>X-ReloadIfChanged</literal> in the
-            <literal>[Service]</literal> section is set to
-            <literal>true</literal> (exposed via
-            <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.reloadIfChanged</link>).
-            A little exception is done for units that were deactivated
-            in the meantime, for example because they require a unit
-            that got stopped before. These are
-            <emphasis role="strong">start</emphasis>ed instead of
-            reloaded.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            If the reload flag is not set, some more flags decide if the
-            unit is skipped. These flags are
-            <literal>X-RestartIfChanged</literal> in the
-            <literal>[Service]</literal> section (exposed via
-            <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.restartIfChanged</link>),
-            <literal>RefuseManualStop</literal> in the
-            <literal>[Unit]</literal> section, and
-            <literal>X-OnlyManualStart</literal> in the
-            <literal>[Unit]</literal> section.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            Further behavior depends on the unit having
-            <literal>X-StopIfChanged</literal> in the
-            <literal>[Service]</literal> section set to
-            <literal>true</literal> (exposed via
-            <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.stopIfChanged</link>).
-            This is set to <literal>true</literal> by default and must
-            be explicitly turned off if not wanted. If the flag is
-            enabled, the unit is
-            <emphasis role="strong">stop</emphasis>ped and then
-            <emphasis role="strong">start</emphasis>ed. If not, the unit
-            is <emphasis role="strong">restart</emphasis>ed. The goal of
-            the flag is to make sure that the new unit never runs in the
-            old environment which is still in place before the
-            activation script is run. This behavior is different when
-            the service is socket-activated, as outlined in the
-            following steps.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            The last thing that is taken into account is whether the
-            unit is a service and socket-activated. If
-            <literal>X-StopIfChanged</literal> is
-            <emphasis role="strong">not</emphasis> set, the service is
-            <emphasis role="strong">restart</emphasis>ed with the
-            others. If it is set, both the service and the socket are
-            <emphasis role="strong">stop</emphasis>ped and the socket is
-            <emphasis role="strong">start</emphasis>ed, leaving socket
-            activation to start the service when it’s needed.
-          </para>
-        </listitem>
-      </itemizedlist>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml
deleted file mode 100644
index 66ba792ddacb..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/what-happens-during-a-system-switch.chapter.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-switching-systems">
-  <title>What happens during a system switch?</title>
-  <para>
-    Running <literal>nixos-rebuild switch</literal> is one of the more
-    common tasks under NixOS. This chapter explains some of the
-    internals of this command to make it simpler for new module
-    developers to configure their units correctly and to make it easier
-    to understand what is happening and why for curious administrators.
-  </para>
-  <para>
-    <literal>nixos-rebuild</literal>, like many deployment solutions,
-    calls <literal>switch-to-configuration</literal> which resides in a
-    NixOS system at <literal>$out/bin/switch-to-configuration</literal>.
-    The script is called with the action that is to be performed like
-    <literal>switch</literal>, <literal>test</literal>,
-    <literal>boot</literal>. There is also the
-    <literal>dry-activate</literal> action which does not really perform
-    the actions but rather prints what it would do if you called it with
-    <literal>test</literal>. This feature can be used to check what
-    service states would be changed if the configuration was switched
-    to.
-  </para>
-  <para>
-    If the action is <literal>switch</literal> or
-    <literal>boot</literal>, the bootloader is updated first so the
-    configuration will be the next one to boot. Unless
-    <literal>NIXOS_NO_SYNC</literal> is set to <literal>1</literal>,
-    <literal>/nix/store</literal> is synced to disk.
-  </para>
-  <para>
-    If the action is <literal>switch</literal> or
-    <literal>test</literal>, the currently running system is inspected
-    and the actions to switch to the new system are calculated. This
-    process takes two data sources into account:
-    <literal>/etc/fstab</literal> and the current systemd status. Mounts
-    and swaps are read from <literal>/etc/fstab</literal> and the
-    corresponding actions are generated. If a new mount is added, for
-    example, the proper <literal>.mount</literal> unit is marked to be
-    started. The current systemd state is inspected, the difference
-    between the current system and the desired configuration is
-    calculated and actions are generated to get to this state. There are
-    a lot of nuances that can be controlled by the units which are
-    explained here.
-  </para>
-  <para>
-    After calculating what should be done, the actions are carried out.
-    The order of actions is always the same:
-  </para>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        Stop units (<literal>systemctl stop</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Run activation script (<literal>$out/activate</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        See if the activation script requested more units to restart
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Restart systemd if needed
-        (<literal>systemd daemon-reexec</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Forget about the failed state of units
-        (<literal>systemctl reset-failed</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Reload systemd (<literal>systemctl daemon-reload</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Reload systemd user instances
-        (<literal>systemctl --user daemon-reload</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Set up tmpfiles (<literal>systemd-tmpfiles --create</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Reload units (<literal>systemctl reload</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Restart units (<literal>systemctl restart</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Start units (<literal>systemctl start</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Inspect what changed during these actions and print units that
-        failed and that were newly started
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    Most of these actions are either self-explaining but some of them
-    have to do with our units or the activation script. For this reason,
-    these topics are explained in the next sections.
-  </para>
-  <xi:include href="unit-handling.section.xml" />
-  <xi:include href="activation-script.section.xml" />
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/writing-documentation.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/writing-documentation.chapter.xml
deleted file mode 100644
index 079c80060576..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/writing-documentation.chapter.xml
+++ /dev/null
@@ -1,144 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-writing-documentation">
-  <title>Writing NixOS Documentation</title>
-  <para>
-    As NixOS grows, so too does the need for a catalogue and explanation
-    of its extensive functionality. Collecting pertinent information
-    from disparate sources and presenting it in an accessible style
-    would be a worthy contribution to the project.
-  </para>
-  <section xml:id="sec-writing-docs-building-the-manual">
-    <title>Building the Manual</title>
-    <para>
-      The DocBook sources of the <xref linkend="book-nixos-manual" />
-      are in the
-      <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/doc/manual"><literal>nixos/doc/manual</literal></link>
-      subdirectory of the Nixpkgs repository.
-    </para>
-    <para>
-      You can quickly validate your edits with <literal>make</literal>:
-    </para>
-    <programlisting>
-$ cd /path/to/nixpkgs/nixos/doc/manual
-$ nix-shell
-nix-shell$ make
-</programlisting>
-    <para>
-      Once you are done making modifications to the manual, it's
-      important to build it before committing. You can do that as
-      follows:
-    </para>
-    <programlisting>
-nix-build nixos/release.nix -A manual.x86_64-linux
-</programlisting>
-    <para>
-      When this command successfully finishes, it will tell you where
-      the manual got generated. The HTML will be accessible through the
-      <literal>result</literal> symlink at
-      <literal>./result/share/doc/nixos/index.html</literal>.
-    </para>
-  </section>
-  <section xml:id="sec-writing-docs-editing-docbook-xml">
-    <title>Editing DocBook XML</title>
-    <para>
-      For general information on how to write in DocBook, see
-      <link xlink:href="http://www.docbook.org/tdg5/en/html/docbook.html">DocBook
-      5: The Definitive Guide</link>.
-    </para>
-    <para>
-      Emacs nXML Mode is very helpful for editing DocBook XML because it
-      validates the document as you write, and precisely locates errors.
-      To use it, see <xref linkend="sec-emacs-docbook-xml" />.
-    </para>
-    <para>
-      <link xlink:href="http://pandoc.org">Pandoc</link> can generate
-      DocBook XML from a multitude of formats, which makes a good
-      starting point. Here is an example of Pandoc invocation to convert
-      GitHub-Flavoured MarkDown to DocBook 5 XML:
-    </para>
-    <programlisting>
-pandoc -f markdown_github -t docbook5 docs.md -o my-section.md
-</programlisting>
-    <para>
-      Pandoc can also quickly convert a single
-      <literal>section.xml</literal> to HTML, which is helpful when
-      drafting.
-    </para>
-    <para>
-      Sometimes writing valid DocBook is simply too difficult. In this
-      case, submit your documentation updates in a
-      <link xlink:href="https://github.com/NixOS/nixpkgs/issues/new">GitHub
-      Issue</link> and someone will handle the conversion to XML for
-      you.
-    </para>
-  </section>
-  <section xml:id="sec-writing-docs-creating-a-topic">
-    <title>Creating a Topic</title>
-    <para>
-      You can use an existing topic as a basis for the new topic or
-      create a topic from scratch.
-    </para>
-    <para>
-      Keep the following guidelines in mind when you create and add a
-      topic:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The NixOS
-          <link xlink:href="http://www.docbook.org/tdg5/en/html/book.html"><literal>book</literal></link>
-          element is in <literal>nixos/doc/manual/manual.xml</literal>.
-          It includes several
-          <link xlink:href="http://www.docbook.org/tdg5/en/html/book.html"><literal>parts</literal></link>
-          which are in subdirectories.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Store the topic file in the same directory as the
-          <literal>part</literal> to which it belongs. If your topic is
-          about configuring a NixOS module, then the XML file can be
-          stored alongside the module definition <literal>nix</literal>
-          file.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If you include multiple words in the file name, separate the
-          words with a dash. For example:
-          <literal>ipv6-config.xml</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Make sure that the <literal>xml:id</literal> value is unique.
-          You can use abbreviations if the ID is too long. For example:
-          <literal>nixos-config</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Determine whether your topic is a chapter or a section. If you
-          are unsure, open an existing topic file and check whether the
-          main element is chapter or section.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-writing-docs-adding-a-topic">
-    <title>Adding a Topic to the Book</title>
-    <para>
-      Open the parent XML file and add an <literal>xi:include</literal>
-      element to the list of chapters with the file name of the topic
-      that you created. If you created a <literal>section</literal>, you
-      add the file to the <literal>chapter</literal> file. If you
-      created a <literal>chapter</literal>, you add the file to the
-      <literal>part</literal> file.
-    </para>
-    <para>
-      If the topic is about configuring a NixOS module, it can be
-      automatically included in the manual by using the
-      <literal>meta.doc</literal> attribute. See
-      <xref linkend="sec-meta-attributes" /> for an explanation.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/writing-modules.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/development/writing-modules.chapter.xml
deleted file mode 100644
index 367731eda090..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/writing-modules.chapter.xml
+++ /dev/null
@@ -1,245 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-writing-modules">
-  <title>Writing NixOS Modules</title>
-  <para>
-    NixOS has a modular system for declarative configuration. This
-    system combines multiple <emphasis>modules</emphasis> to produce the
-    full system configuration. One of the modules that constitute the
-    configuration is <literal>/etc/nixos/configuration.nix</literal>.
-    Most of the others live in the
-    <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules"><literal>nixos/modules</literal></link>
-    subdirectory of the Nixpkgs tree.
-  </para>
-  <para>
-    Each NixOS module is a file that handles one logical aspect of the
-    configuration, such as a specific kind of hardware, a service, or
-    network settings. A module configuration does not have to handle
-    everything from scratch; it can use the functionality provided by
-    other modules for its implementation. Thus a module can
-    <emphasis>declare</emphasis> options that can be used by other
-    modules, and conversely can <emphasis>define</emphasis> options
-    provided by other modules in its own implementation. For example,
-    the module
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/pam.nix"><literal>pam.nix</literal></link>
-    declares the option <literal>security.pam.services</literal> that
-    allows other modules (e.g.
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/ssh/sshd.nix"><literal>sshd.nix</literal></link>)
-    to define PAM services; and it defines the option
-    <literal>environment.etc</literal> (declared by
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/etc/etc.nix"><literal>etc.nix</literal></link>)
-    to cause files to be created in <literal>/etc/pam.d</literal>.
-  </para>
-  <para>
-    In <xref linkend="sec-configuration-syntax" />, we saw the following
-    structure of NixOS modules:
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{ option definitions
-}
-</programlisting>
-  <para>
-    This is actually an <emphasis>abbreviated</emphasis> form of module
-    that only defines options, but does not declare any. The structure
-    of full NixOS modules is shown in
-    <link linkend="ex-module-syntax">Example: Structure of NixOS
-    Modules</link>.
-  </para>
-  <anchor xml:id="ex-module-syntax" />
-  <para>
-    <emphasis role="strong">Example: Structure of NixOS
-    Modules</emphasis>
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ... }:
-
-{
-  imports =
-    [ paths of other modules
-    ];
-
-  options = {
-    option declarations
-  };
-
-  config = {
-    option definitions
-  };
-}
-</programlisting>
-  <para>
-    The meaning of each part is as follows.
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        The first line makes the current Nix expression a function. The
-        variable <literal>pkgs</literal> contains Nixpkgs (by default,
-        it takes the <literal>nixpkgs</literal> entry of
-        <literal>NIX_PATH</literal>, see the
-        <link xlink:href="https://nixos.org/manual/nix/stable/#sec-common-env">Nix
-        manual</link> for further details), while
-        <literal>config</literal> contains the full system
-        configuration. This line can be omitted if there is no reference
-        to <literal>pkgs</literal> and <literal>config</literal> inside
-        the module.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        This <literal>imports</literal> list enumerates the paths to
-        other NixOS modules that should be included in the evaluation of
-        the system configuration. A default set of modules is defined in
-        the file <literal>modules/module-list.nix</literal>. These don't
-        need to be added in the import list.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The attribute <literal>options</literal> is a nested set of
-        <emphasis>option declarations</emphasis> (described below).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The attribute <literal>config</literal> is a nested set of
-        <emphasis>option definitions</emphasis> (also described below).
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    <link linkend="locate-example">Example: NixOS Module for the
-    <quote>locate</quote> Service</link> shows a module that handles the
-    regular update of the <quote>locate</quote> database, an index of
-    all files in the file system. This module declares two options that
-    can be defined by other modules (typically the user’s
-    <literal>configuration.nix</literal>):
-    <literal>services.locate.enable</literal> (whether the database
-    should be updated) and <literal>services.locate.interval</literal>
-    (when the update should be done). It implements its functionality by
-    defining two options declared by other modules:
-    <literal>systemd.services</literal> (the set of all systemd
-    services) and <literal>systemd.timers</literal> (the list of
-    commands to be executed periodically by <literal>systemd</literal>).
-  </para>
-  <para>
-    Care must be taken when writing systemd services using
-    <literal>Exec*</literal> directives. By default systemd performs
-    substitution on <literal>%&lt;char&gt;</literal> specifiers in these
-    directives, expands environment variables from
-    <literal>$FOO</literal> and <literal>${FOO}</literal>, splits
-    arguments on whitespace, and splits commands on
-    <literal>;</literal>. All of these must be escaped to avoid
-    unexpected substitution or splitting when interpolating into an
-    <literal>Exec*</literal> directive, e.g. when using an
-    <literal>extraArgs</literal> option to pass additional arguments to
-    the service. The functions
-    <literal>utils.escapeSystemdExecArg</literal> and
-    <literal>utils.escapeSystemdExecArgs</literal> are provided for
-    this, see <link linkend="exec-escaping-example">Example: Escaping in
-    Exec directives</link> for an example. When using these functions
-    system environment substitution should <emphasis>not</emphasis> be
-    disabled explicitly.
-  </para>
-  <anchor xml:id="locate-example" />
-  <para>
-    <emphasis role="strong">Example: NixOS Module for the
-    <quote>locate</quote> Service</emphasis>
-  </para>
-  <programlisting language="bash">
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.locate;
-in {
-  options.services.locate = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        If enabled, NixOS will periodically update the database of
-        files used by the locate command.
-      '';
-    };
-
-    interval = mkOption {
-      type = types.str;
-      default = &quot;02:15&quot;;
-      example = &quot;hourly&quot;;
-      description = ''
-        Update the locate database at this interval. Updates by
-        default at 2:15 AM every day.
-
-        The format is described in
-        systemd.time(7).
-      '';
-    };
-
-    # Other options omitted for documentation
-  };
-
-  config = {
-    systemd.services.update-locatedb =
-      { description = &quot;Update Locate Database&quot;;
-        path  = [ pkgs.su ];
-        script =
-          ''
-            mkdir -m 0755 -p $(dirname ${toString cfg.output})
-            exec updatedb \
-              --localuser=${cfg.localuser} \
-              ${optionalString (!cfg.includeStore) &quot;--prunepaths='/nix/store'&quot;} \
-              --output=${toString cfg.output} ${concatStringsSep &quot; &quot; cfg.extraFlags}
-          '';
-      };
-
-    systemd.timers.update-locatedb = mkIf cfg.enable
-      { description = &quot;Update timer for locate database&quot;;
-        partOf      = [ &quot;update-locatedb.service&quot; ];
-        wantedBy    = [ &quot;timers.target&quot; ];
-        timerConfig.OnCalendar = cfg.interval;
-      };
-  };
-}
-</programlisting>
-  <anchor xml:id="exec-escaping-example" />
-  <para>
-    <emphasis role="strong">Example: Escaping in Exec
-    directives</emphasis>
-  </para>
-  <programlisting language="bash">
-{ config, lib, pkgs, utils, ... }:
-
-with lib;
-
-let
-  cfg = config.services.echo;
-  echoAll = pkgs.writeScript &quot;echo-all&quot; ''
-    #! ${pkgs.runtimeShell}
-    for s in &quot;$@&quot;; do
-      printf '%s\n' &quot;$s&quot;
-    done
-  '';
-  args = [ &quot;a%Nything&quot; &quot;lang=\${LANG}&quot; &quot;;&quot; &quot;/bin/sh -c date&quot; ];
-in {
-  systemd.services.echo =
-    { description = &quot;Echo to the journal&quot;;
-      wantedBy = [ &quot;multi-user.target&quot; ];
-      serviceConfig.Type = &quot;oneshot&quot;;
-      serviceConfig.ExecStart = ''
-        ${echoAll} ${utils.escapeSystemdExecArgs args}
-      '';
-    };
-}
-</programlisting>
-  <xi:include href="option-declarations.section.xml" />
-  <xi:include href="option-types.section.xml" />
-  <xi:include href="option-def.section.xml" />
-  <xi:include href="assertions.section.xml" />
-  <xi:include href="meta-attributes.section.xml" />
-  <xi:include href="importing-modules.section.xml" />
-  <xi:include href="replace-modules.section.xml" />
-  <xi:include href="freeform-modules.section.xml" />
-  <xi:include href="settings-options.section.xml" />
-</chapter>
diff --git a/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
deleted file mode 100644
index d6f4f61c0645..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ /dev/null
@@ -1,692 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-writing-nixos-tests">
-  <title>Writing Tests</title>
-  <para>
-    A NixOS test is a Nix expression that has the following structure:
-  </para>
-  <programlisting language="bash">
-import ./make-test-python.nix {
-
-  # One or more machines:
-  nodes =
-    { machine =
-        { config, pkgs, ... }: { … };
-      machine2 =
-        { config, pkgs, ... }: { … };
-      …
-    };
-
-  testScript =
-    ''
-      Python code…
-    '';
-}
-</programlisting>
-  <para>
-    The attribute <literal>testScript</literal> is a bit of Python code
-    that executes the test (described below). During the test, it will
-    start one or more virtual machines, the configuration of which is
-    described by the attribute <literal>nodes</literal>.
-  </para>
-  <para>
-    An example of a single-node test is
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix"><literal>login.nix</literal></link>.
-    It only needs a single machine to test whether users can log in on
-    the virtual console, whether device ownership is correctly
-    maintained when switching between consoles, and so on. An
-    interesting multi-node test is
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix"><literal>nfs/simple.nix</literal></link>.
-    It uses two client nodes to test correct locking across server
-    crashes.
-  </para>
-  <para>
-    There are a few special NixOS configuration options for test VMs:
-  </para>
-  <variablelist>
-    <varlistentry>
-      <term>
-        <literal>virtualisation.memorySize</literal>
-      </term>
-      <listitem>
-        <para>
-          The memory of the VM in megabytes.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>virtualisation.vlans</literal>
-      </term>
-      <listitem>
-        <para>
-          The virtual networks to which the VM is connected. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nat.nix"><literal>nat.nix</literal></link>
-          for an example.
-        </para>
-      </listitem>
-    </varlistentry>
-    <varlistentry>
-      <term>
-        <literal>virtualisation.writableStore</literal>
-      </term>
-      <listitem>
-        <para>
-          By default, the Nix store in the VM is not writable. If you
-          enable this option, a writable union file system is mounted on
-          top of the Nix store to make it appear writable. This is
-          necessary for tests that run Nix operations that modify the
-          store.
-        </para>
-      </listitem>
-    </varlistentry>
-  </variablelist>
-  <para>
-    For more options, see the module
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix"><literal>qemu-vm.nix</literal></link>.
-  </para>
-  <para>
-    The test script is a sequence of Python statements that perform
-    various actions, such as starting VMs, executing commands in the
-    VMs, and so on. Each virtual machine is represented as an object
-    stored in the variable <literal>name</literal> if this is also the
-    identifier of the machine in the declarative config. If you
-    specified a node <literal>nodes.machine</literal>, the following
-    example starts the machine, waits until it has finished booting,
-    then executes a command and checks that the output is more-or-less
-    correct:
-  </para>
-  <programlisting language="python">
-machine.start()
-machine.wait_for_unit(&quot;default.target&quot;)
-if not &quot;Linux&quot; in machine.succeed(&quot;uname&quot;):
-  raise Exception(&quot;Wrong OS&quot;)
-</programlisting>
-  <para>
-    The first line is technically unnecessary; machines are implicitly
-    started when you first execute an action on them (such as
-    <literal>wait_for_unit</literal> or <literal>succeed</literal>). If
-    you have multiple machines, you can speed up the test by starting
-    them in parallel:
-  </para>
-  <programlisting language="python">
-start_all()
-</programlisting>
-  <section xml:id="ssec-machine-objects">
-    <title>Machine objects</title>
-    <para>
-      The following methods are available on machine objects:
-    </para>
-    <variablelist>
-      <varlistentry>
-        <term>
-          <literal>start</literal>
-        </term>
-        <listitem>
-          <para>
-            Start the virtual machine. This method is asynchronous — it
-            does not wait for the machine to finish booting.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>shutdown</literal>
-        </term>
-        <listitem>
-          <para>
-            Shut down the machine, waiting for the VM to exit.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>crash</literal>
-        </term>
-        <listitem>
-          <para>
-            Simulate a sudden power failure, by telling the VM to exit
-            immediately.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>block</literal>
-        </term>
-        <listitem>
-          <para>
-            Simulate unplugging the Ethernet cable that connects the
-            machine to the other machines.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>unblock</literal>
-        </term>
-        <listitem>
-          <para>
-            Undo the effect of <literal>block</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>screenshot</literal>
-        </term>
-        <listitem>
-          <para>
-            Take a picture of the display of the virtual machine, in PNG
-            format. The screenshot is linked from the HTML log.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>get_screen_text_variants</literal>
-        </term>
-        <listitem>
-          <para>
-            Return a list of different interpretations of what is
-            currently visible on the machine's screen using optical
-            character recognition. The number and order of the
-            interpretations is not specified and is subject to change,
-            but if no exception is raised at least one will be returned.
-          </para>
-          <note>
-            <para>
-              This requires passing <literal>enableOCR</literal> to the
-              test attribute set.
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>get_screen_text</literal>
-        </term>
-        <listitem>
-          <para>
-            Return a textual representation of what is currently visible
-            on the machine's screen using optical character recognition.
-          </para>
-          <note>
-            <para>
-              This requires passing <literal>enableOCR</literal> to the
-              test attribute set.
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>send_monitor_command</literal>
-        </term>
-        <listitem>
-          <para>
-            Send a command to the QEMU monitor. This is rarely used, but
-            allows doing stuff such as attaching virtual USB disks to a
-            running machine.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>send_key</literal>
-        </term>
-        <listitem>
-          <para>
-            Simulate pressing keys on the virtual keyboard, e.g.,
-            <literal>send_key(&quot;ctrl-alt-delete&quot;)</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>send_chars</literal>
-        </term>
-        <listitem>
-          <para>
-            Simulate typing a sequence of characters on the virtual
-            keyboard, e.g.,
-            <literal>send_chars(&quot;foobar\n&quot;)</literal> will
-            type the string <literal>foobar</literal> followed by the
-            Enter key.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>send_console</literal>
-        </term>
-        <listitem>
-          <para>
-            Send keys to the kernel console. This allows interaction
-            with the systemd emergency mode, for example. Takes a string
-            that is sent, e.g.,
-            <literal>send_console(&quot;\n\nsystemctl default\n&quot;)</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>execute</literal>
-        </term>
-        <listitem>
-          <para>
-            Execute a shell command, returning a list
-            <literal>(status, stdout)</literal>.
-          </para>
-          <para>
-            Commands are run with <literal>set -euo pipefail</literal>
-            set:
-          </para>
-          <itemizedlist>
-            <listitem>
-              <para>
-                If several commands are separated by
-                <literal>;</literal> and one fails, the command as a
-                whole will fail.
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                For pipelines, the last non-zero exit status will be
-                returned (if there is one; otherwise zero will be
-                returned).
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                Dereferencing unset variables fails the command.
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                It will wait for stdout to be closed.
-              </para>
-            </listitem>
-          </itemizedlist>
-          <para>
-            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.
-          </para>
-          <para>
-            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>
-          <para>
-            A timeout for the command can be specified (in seconds)
-            using the optional <literal>timeout</literal> parameter,
-            e.g., <literal>execute(cmd, timeout=10)</literal> or
-            <literal>execute(cmd, timeout=None)</literal>. The default
-            is 900 seconds.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>succeed</literal>
-        </term>
-        <listitem>
-          <para>
-            Execute a shell command, raising an exception if the exit
-            status is not zero, otherwise returning the standard output.
-            Similar to <literal>execute</literal>, except that the
-            timeout is <literal>None</literal> by default. See
-            <literal>execute</literal> for details on command execution.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>fail</literal>
-        </term>
-        <listitem>
-          <para>
-            Like <literal>succeed</literal>, but raising an exception if
-            the command returns a zero status.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_until_succeeds</literal>
-        </term>
-        <listitem>
-          <para>
-            Repeat a shell command with 1-second intervals until it
-            succeeds. Has a default timeout of 900 seconds which can be
-            modified, e.g.
-            <literal>wait_until_succeeds(cmd, timeout=10)</literal>. See
-            <literal>execute</literal> for details on command execution.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_until_fails</literal>
-        </term>
-        <listitem>
-          <para>
-            Like <literal>wait_until_succeeds</literal>, but repeating
-            the command until it fails.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_unit</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until the specified systemd unit has reached the
-            <quote>active</quote> state.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_file</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until the specified file exists.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_open_port</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until a process is listening on the given TCP port (on
-            <literal>localhost</literal>, at least).
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_closed_port</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until nobody is listening on the given TCP port.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_x</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until the X11 server is accepting connections.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_text</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until the supplied regular expressions matches the
-            textual contents of the screen by using optical character
-            recognition (see <literal>get_screen_text</literal> and
-            <literal>get_screen_text_variants</literal>).
-          </para>
-          <note>
-            <para>
-              This requires passing <literal>enableOCR</literal> to the
-              test attribute set.
-            </para>
-          </note>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_console_text</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until the supplied regular expressions match a line of
-            the serial console output. This method is useful when OCR is
-            not possibile or accurate enough.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>wait_for_window</literal>
-        </term>
-        <listitem>
-          <para>
-            Wait until an X11 window has appeared whose name matches the
-            given regular expression, e.g.,
-            <literal>wait_for_window(&quot;Terminal&quot;)</literal>.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>copy_from_host</literal>
-        </term>
-        <listitem>
-          <para>
-            Copies a file from host to machine, e.g.,
-            <literal>copy_from_host(&quot;myfile&quot;, &quot;/etc/my/important/file&quot;)</literal>.
-          </para>
-          <para>
-            The first argument is the file on the host. The file needs
-            to be accessible while building the nix derivation. The
-            second argument is the location of the file on the machine.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>systemctl</literal>
-        </term>
-        <listitem>
-          <para>
-            Runs <literal>systemctl</literal> commands with optional
-            support for <literal>systemctl --user</literal>
-          </para>
-          <programlisting language="python">
-machine.systemctl(&quot;list-jobs --no-pager&quot;) # runs `systemctl list-jobs --no-pager`
-machine.systemctl(&quot;list-jobs --no-pager&quot;, &quot;any-user&quot;) # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
-</programlisting>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>shell_interact</literal>
-        </term>
-        <listitem>
-          <para>
-            Allows you to directly interact with the guest shell. This
-            should only be used during test development, not in
-            production tests. Killing the interactive session with
-            <literal>Ctrl-d</literal> or <literal>Ctrl-c</literal> also
-            ends the guest session.
-          </para>
-        </listitem>
-      </varlistentry>
-      <varlistentry>
-        <term>
-          <literal>console_interact</literal>
-        </term>
-        <listitem>
-          <para>
-            Allows you to directly interact with QEMU’s stdin. This
-            should only be used during test development, not in
-            production tests. Output from QEMU is only read line-wise.
-            <literal>Ctrl-c</literal> kills QEMU and
-            <literal>Ctrl-d</literal> closes console and returns to the
-            test runner.
-          </para>
-        </listitem>
-      </varlistentry>
-    </variablelist>
-    <para>
-      To test user units declared by
-      <literal>systemd.user.services</literal> the optional
-      <literal>user</literal> argument can be used:
-    </para>
-    <programlisting language="python">
-machine.start()
-machine.wait_for_x()
-machine.wait_for_unit(&quot;xautolock.service&quot;, &quot;x-session-user&quot;)
-</programlisting>
-    <para>
-      This applies to <literal>systemctl</literal>,
-      <literal>get_unit_info</literal>,
-      <literal>wait_for_unit</literal>, <literal>start_job</literal> and
-      <literal>stop_job</literal>.
-    </para>
-    <para>
-      For faster dev cycles it's also possible to disable the
-      code-linters (this shouldn't be commited though):
-    </para>
-    <programlisting language="bash">
-import ./make-test-python.nix {
-  skipLint = true;
-  nodes.machine =
-    { config, pkgs, ... }:
-    { configuration…
-    };
-
-  testScript =
-    ''
-      Python code…
-    '';
-}
-</programlisting>
-    <para>
-      This will produce a Nix warning at evaluation time. To fully
-      disable the linter, wrap the test script in comment directives to
-      disable the Black linter directly (again, don't commit this within
-      the Nixpkgs repository):
-    </para>
-    <programlisting language="bash">
-  testScript =
-    ''
-      # fmt: off
-      Python code…
-      # fmt: on
-    '';
-</programlisting>
-    <para>
-      Similarly, the type checking of test scripts can be disabled in
-      the following way:
-    </para>
-    <programlisting language="bash">
-import ./make-test-python.nix {
-  skipTypeCheck = true;
-  nodes.machine =
-    { config, pkgs, ... }:
-    { configuration…
-    };
-}
-</programlisting>
-  </section>
-  <section xml:id="ssec-failing-tests-early">
-    <title>Failing tests early</title>
-    <para>
-      To fail tests early when certain invariants are no longer met
-      (instead of waiting for the build to time out), the decorator
-      <literal>polling_condition</literal> is provided. For example, if
-      we are testing a program <literal>foo</literal> that should not
-      quit after being started, we might write the following:
-    </para>
-    <programlisting language="python">
-@polling_condition
-def foo_running():
-    machine.succeed(&quot;pgrep -x foo&quot;)
-
-
-machine.succeed(&quot;foo --start&quot;)
-machine.wait_until_succeeds(&quot;pgrep -x foo&quot;)
-
-with foo_running:
-    ...  # Put `foo` through its paces
-</programlisting>
-    <para>
-      <literal>polling_condition</literal> takes the following
-      (optional) arguments:
-    </para>
-    <para>
-      <literal>seconds_interval</literal>
-    </para>
-    <para>
-      : specifies how often the condition should be polled:
-    </para>
-    <programlisting language="python">
-@polling_condition(seconds_interval=10)
-def foo_running():
-    machine.succeed(&quot;pgrep -x foo&quot;)
-</programlisting>
-    <para>
-      <literal>description</literal>
-    </para>
-    <para>
-      : is used in the log when the condition is checked. If this is not
-      provided, the description is pulled from the docstring of the
-      function. These two are therefore equivalent:
-    </para>
-    <programlisting language="python">
-@polling_condition
-def foo_running():
-    &quot;check that foo is running&quot;
-    machine.succeed(&quot;pgrep -x foo&quot;)
-</programlisting>
-    <programlisting language="python">
-@polling_condition(description=&quot;check that foo is running&quot;)
-def foo_running():
-    machine.succeed(&quot;pgrep -x foo&quot;)
-</programlisting>
-  </section>
-  <section xml:id="ssec-python-packages-in-test-script">
-    <title>Adding Python packages to the test script</title>
-    <para>
-      When additional Python libraries are required in the test script,
-      they can be added using the parameter
-      <literal>extraPythonPackages</literal>. For example, you could add
-      <literal>numpy</literal> like this:
-    </para>
-    <programlisting language="bash">
-import ./make-test-python.nix
-{
-  extraPythonPackages = p: [ p.numpy ];
-
-  nodes = { };
-
-  # Type checking on extra packages doesn't work yet
-  skipTypeCheck = true;
-
-  testScript = ''
-    import numpy as np
-    assert str(np.zeros(4) == &quot;array([0., 0., 0., 0.])&quot;)
-  '';
-}
-</programlisting>
-    <para>
-      In that case, <literal>numpy</literal> is chosen from the generic
-      <literal>python3Packages</literal>.
-    </para>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
deleted file mode 100644
index ea2d01bebcc2..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
+++ /dev/null
@@ -1,111 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-building-image">
-  <title>Building a NixOS (Live) ISO</title>
-  <para>
-    Default live installer configurations are available inside
-    <literal>nixos/modules/installer/cd-dvd</literal>. For building
-    other system images,
-    <link xlink:href="https://github.com/nix-community/nixos-generators">nixos-generators</link>
-    is a good place to start looking at.
-  </para>
-  <para>
-    You have two options:
-  </para>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        Use any of those default configurations as is
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Combine them with (any of) your host config(s)
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    System images, such as the live installer ones, know how to enforce
-    configuration settings on wich they immediately depend in order to
-    work correctly.
-  </para>
-  <para>
-    However, if you are confident, you can opt to override those
-    enforced values with <literal>mkForce</literal>.
-  </para>
-  <section xml:id="sec-building-image-instructions">
-    <title>Practical Instructions</title>
-    <para>
-      To build an ISO image for the channel
-      <literal>nixos-unstable</literal>:
-    </para>
-    <programlisting>
-$ git clone https://github.com/NixOS/nixpkgs.git
-$ cd nixpkgs/nixos
-$ git switch nixos-unstable
-$ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-minimal.nix default.nix
-</programlisting>
-    <para>
-      To check the content of an ISO image, mount it like so:
-    </para>
-    <programlisting>
-# mount -o loop -t iso9660 ./result/iso/cd.iso /mnt/iso
-</programlisting>
-  </section>
-  <section xml:id="sec-building-image-drivers">
-    <title>Additional drivers or firmware</title>
-    <para>
-      If you need additional (non-distributable) drivers or firmware in
-      the installer, you might want to extend these configurations.
-    </para>
-    <para>
-      For example, to build the GNOME graphical installer ISO, but with
-      support for certain WiFi adapters present in some MacBooks, you
-      can create the following file at
-      <literal>modules/installer/cd-dvd/installation-cd-graphical-gnome-macbook.nix</literal>:
-    </para>
-    <programlisting language="bash">
-{ config, ... }:
-
-{
-  imports = [ ./installation-cd-graphical-gnome.nix ];
-
-  boot.initrd.kernelModules = [ &quot;wl&quot; ];
-
-  boot.kernelModules = [ &quot;kvm-intel&quot; &quot;wl&quot; ];
-  boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];
-}
-</programlisting>
-    <para>
-      Then build it like in the example above:
-    </para>
-    <programlisting>
-$ git clone https://github.com/NixOS/nixpkgs.git
-$ cd nixpkgs/nixos
-$ export NIXPKGS_ALLOW_UNFREE=1
-$ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-graphical-gnome-macbook.nix default.nix
-</programlisting>
-  </section>
-  <section xml:id="sec-building-image-tech-notes">
-    <title>Technical Notes</title>
-    <para>
-      The config value enforcement is implemented via
-      <literal>mkImageMediaOverride = mkOverride 60;</literal> and
-      therefore primes over simple value assignments, but also yields to
-      <literal>mkForce</literal>.
-    </para>
-    <para>
-      This property allows image designers to implement in semantically
-      correct ways those configuration values upon which the correct
-      functioning of the image depends.
-    </para>
-    <para>
-      For example, the iso base image overrides those file systems which
-      it needs at a minimum for correct functioning, while the installer
-      base image overrides the entire file system layout because there
-      can’t be any other guarantees on a live medium than those given by
-      the live medium itself. The latter is especially true befor
-      formatting the target block device(s). On the other hand, the
-      netboot iso only overrides its minimum dependencies since netboot
-      images are always made-to-target.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/changing-config.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/changing-config.chapter.xml
deleted file mode 100644
index 86f0b15b41c5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/changing-config.chapter.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-changing-config">
-  <title>Changing the Configuration</title>
-  <para>
-    The file <literal>/etc/nixos/configuration.nix</literal> contains
-    the current configuration of your machine. Whenever you’ve
-    <link linkend="ch-configuration">changed something</link> in that
-    file, you should do
-  </para>
-  <programlisting>
-# nixos-rebuild switch
-</programlisting>
-  <para>
-    to build the new configuration, make it the default configuration
-    for booting, and try to realise the configuration in the running
-    system (e.g., by restarting system services).
-  </para>
-  <warning>
-    <para>
-      This command doesn't start/stop
-      <link linkend="opt-systemd.user.services">user services</link>
-      automatically. <literal>nixos-rebuild</literal> only runs a
-      <literal>daemon-reload</literal> for each user with running user
-      services.
-    </para>
-  </warning>
-  <warning>
-    <para>
-      These commands must be executed as root, so you should either run
-      them from a root shell or by prefixing them with
-      <literal>sudo -i</literal>.
-    </para>
-  </warning>
-  <para>
-    You can also do
-  </para>
-  <programlisting>
-# nixos-rebuild test
-</programlisting>
-  <para>
-    to build the configuration and switch the running system to it, but
-    without making it the boot default. So if (say) the configuration
-    locks up your machine, you can just reboot to get back to a working
-    configuration.
-  </para>
-  <para>
-    There is also
-  </para>
-  <programlisting>
-# nixos-rebuild boot
-</programlisting>
-  <para>
-    to build the configuration and make it the boot default, but not
-    switch to it now (so it will only take effect after the next
-    reboot).
-  </para>
-  <para>
-    You can make your configuration show up in a different submenu of
-    the GRUB 2 boot screen by giving it a different <emphasis>profile
-    name</emphasis>, e.g.
-  </para>
-  <programlisting>
-# nixos-rebuild switch -p test
-</programlisting>
-  <para>
-    which causes the new configuration (and previous ones created using
-    <literal>-p test</literal>) to show up in the GRUB submenu
-    <quote>NixOS - Profile 'test'</quote>. This can be useful to
-    separate test configurations from <quote>stable</quote>
-    configurations.
-  </para>
-  <para>
-    Finally, you can do
-  </para>
-  <programlisting>
-$ nixos-rebuild build
-</programlisting>
-  <para>
-    to build the configuration but nothing more. This is useful to see
-    whether everything compiles cleanly.
-  </para>
-  <para>
-    If you have a machine that supports hardware virtualisation, you can
-    also test the new configuration in a sandbox by building and running
-    a QEMU <emphasis>virtual machine</emphasis> that contains the
-    desired configuration. Just do
-  </para>
-  <programlisting>
-$ nixos-rebuild build-vm
-$ ./result/bin/run-*-vm
-</programlisting>
-  <para>
-    The VM does not have any data from your host system, so your
-    existing user accounts and home directories will not be available
-    unless you have set <literal>mutableUsers = false</literal>. Another
-    way is to temporarily add the following to your configuration:
-  </para>
-  <programlisting language="bash">
-users.users.your-user.initialHashedPassword = &quot;test&quot;;
-</programlisting>
-  <para>
-    <emphasis>Important:</emphasis> delete the $hostname.qcow2 file if
-    you have started the virtual machine at least once without the right
-    users, otherwise the changes will not get picked up. You can forward
-    ports on the host to the guest. For instance, the following will
-    forward host port 2222 to guest port 22 (SSH):
-  </para>
-  <programlisting>
-$ QEMU_NET_OPTS=&quot;hostfwd=tcp::2222-:22&quot; ./result/bin/run-*-vm
-</programlisting>
-  <para>
-    allowing you to log in via SSH (assuming you have set the
-    appropriate passwords or SSH authorized keys):
-  </para>
-  <programlisting>
-$ ssh -p 2222 localhost
-</programlisting>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing-behind-a-proxy.section.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing-behind-a-proxy.section.xml
deleted file mode 100644
index a551807cd47c..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing-behind-a-proxy.section.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-installing-behind-proxy">
-  <title>Installing behind a proxy</title>
-  <para>
-    To install NixOS behind a proxy, do the following before running
-    <literal>nixos-install</literal>.
-  </para>
-  <orderedlist numeration="arabic">
-    <listitem>
-      <para>
-        Update proxy configuration in
-        <literal>/mnt/etc/nixos/configuration.nix</literal> to keep the
-        internet accessible after reboot.
-      </para>
-      <programlisting language="bash">
-networking.proxy.default = &quot;http://user:password@proxy:port/&quot;;
-networking.proxy.noProxy = &quot;127.0.0.1,localhost,internal.domain&quot;;
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        Setup the proxy environment variables in the shell where you are
-        running <literal>nixos-install</literal>.
-      </para>
-      <programlisting>
-# proxy_url=&quot;http://user:password@proxy:port/&quot;
-# export http_proxy=&quot;$proxy_url&quot;
-# export HTTP_PROXY=&quot;$proxy_url&quot;
-# export https_proxy=&quot;$proxy_url&quot;
-# export HTTPS_PROXY=&quot;$proxy_url&quot;
-</programlisting>
-    </listitem>
-  </orderedlist>
-  <note>
-    <para>
-      If you are switching networks with different proxy configurations,
-      use the <literal>specialisation</literal> option in
-      <literal>configuration.nix</literal> to switch proxies at runtime.
-      Refer to <xref linkend="ch-options" /> for more information.
-    </para>
-  </note>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml
deleted file mode 100644
index 024a24379dd6..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml
+++ /dev/null
@@ -1,388 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-installing-from-other-distro">
-  <title>Installing from another Linux distribution</title>
-  <para>
-    Because Nix (the package manager) &amp; Nixpkgs (the Nix packages
-    collection) can both be installed on any (most?) Linux
-    distributions, they can be used to install NixOS in various creative
-    ways. You can, for instance:
-  </para>
-  <orderedlist numeration="arabic">
-    <listitem>
-      <para>
-        Install NixOS on another partition, from your existing Linux
-        distribution (without the use of a USB or optical device!)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Install NixOS on the same partition (in place!), from your
-        existing non-NixOS Linux distribution using
-        <literal>NIXOS_LUSTRATE</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Install NixOS on your hard drive from the Live CD of any Linux
-        distribution.
-      </para>
-    </listitem>
-  </orderedlist>
-  <para>
-    The first steps to all these are the same:
-  </para>
-  <orderedlist numeration="arabic">
-    <listitem>
-      <para>
-        Install the Nix package manager:
-      </para>
-      <para>
-        Short version:
-      </para>
-      <programlisting>
-$ curl -L https://nixos.org/nix/install | sh
-$ . $HOME/.nix-profile/etc/profile.d/nix.sh # …or open a fresh shell
-</programlisting>
-      <para>
-        More details in the
-        <link xlink:href="https://nixos.org/nix/manual/#chap-quick-start">
-        Nix manual</link>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Switch to the NixOS channel:
-      </para>
-      <para>
-        If you've just installed Nix on a non-NixOS distribution, you
-        will be on the <literal>nixpkgs</literal> channel by default.
-      </para>
-      <programlisting>
-$ nix-channel --list
-nixpkgs https://nixos.org/channels/nixpkgs-unstable
-</programlisting>
-      <para>
-        As that channel gets released without running the NixOS tests,
-        it will be safer to use the <literal>nixos-*</literal> channels
-        instead:
-      </para>
-      <programlisting>
-$ nix-channel --add https://nixos.org/channels/nixos-version nixpkgs
-</programlisting>
-      <para>
-        You may want to throw in a
-        <literal>nix-channel --update</literal> for good measure.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Install the NixOS installation tools:
-      </para>
-      <para>
-        You'll need <literal>nixos-generate-config</literal> and
-        <literal>nixos-install</literal>, but this also makes some man
-        pages and <literal>nixos-enter</literal> available, just in case
-        you want to chroot into your NixOS partition. NixOS installs
-        these by default, but you don't have NixOS yet..
-      </para>
-      <programlisting>
-$ nix-env -f '&lt;nixpkgs&gt;' -iA nixos-install-tools
-</programlisting>
-    </listitem>
-    <listitem>
-      <note>
-        <para>
-          The following 5 steps are only for installing NixOS to another
-          partition. For installing NixOS in place using
-          <literal>NIXOS_LUSTRATE</literal>, skip ahead.
-        </para>
-      </note>
-      <para>
-        Prepare your target partition:
-      </para>
-      <para>
-        At this point it is time to prepare your target partition.
-        Please refer to the partitioning, file-system creation, and
-        mounting steps of <xref linkend="sec-installation" />
-      </para>
-      <para>
-        If you're about to install NixOS in place using
-        <literal>NIXOS_LUSTRATE</literal> there is nothing to do for
-        this step.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Generate your NixOS configuration:
-      </para>
-      <programlisting>
-$ sudo `which nixos-generate-config` --root /mnt
-</programlisting>
-      <para>
-        You'll probably want to edit the configuration files. Refer to
-        the <literal>nixos-generate-config</literal> step in
-        <xref linkend="sec-installation" /> for more information.
-      </para>
-      <para>
-        Consider setting up the NixOS bootloader to give you the ability
-        to boot on your existing Linux partition. For instance, if
-        you're using GRUB and your existing distribution is running
-        Ubuntu, you may want to add something like this to your
-        <literal>configuration.nix</literal>:
-      </para>
-      <programlisting language="bash">
-boot.loader.grub.extraEntries = ''
-  menuentry &quot;Ubuntu&quot; {
-    search --set=ubuntu --fs-uuid 3cc3e652-0c1f-4800-8451-033754f68e6e
-    configfile &quot;($ubuntu)/boot/grub/grub.cfg&quot;
-  }
-'';
-</programlisting>
-      <para>
-        (You can find the appropriate UUID for your partition in
-        <literal>/dev/disk/by-uuid</literal>)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Create the <literal>nixbld</literal> group and user on your
-        original distribution:
-      </para>
-      <programlisting>
-$ sudo groupadd -g 30000 nixbld
-$ sudo useradd -u 30000 -g nixbld -G nixbld nixbld
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        Download/build/install NixOS:
-      </para>
-      <warning>
-        <para>
-          Once you complete this step, you might no longer be able to
-          boot on existing systems without the help of a rescue USB
-          drive or similar.
-        </para>
-      </warning>
-      <note>
-        <para>
-          On some distributions there are separate PATHS for programs
-          intended only for root. In order for the installation to
-          succeed, you might have to use
-          <literal>PATH=&quot;$PATH:/usr/sbin:/sbin&quot;</literal> in
-          the following command.
-        </para>
-      </note>
-      <programlisting>
-$ sudo PATH=&quot;$PATH&quot; NIX_PATH=&quot;$NIX_PATH&quot; `which nixos-install` --root /mnt
-</programlisting>
-      <para>
-        Again, please refer to the <literal>nixos-install</literal> step
-        in <xref linkend="sec-installation" /> for more information.
-      </para>
-      <para>
-        That should be it for installation to another partition!
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Optionally, you may want to clean up your non-NixOS
-        distribution:
-      </para>
-      <programlisting>
-$ sudo userdel nixbld
-$ sudo groupdel nixbld
-</programlisting>
-      <para>
-        If you do not wish to keep the Nix package manager installed
-        either, run something like
-        <literal>sudo rm -rv ~/.nix-* /nix</literal> and remove the line
-        that the Nix installer added to your
-        <literal>~/.profile</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <note>
-        <para>
-          The following steps are only for installing NixOS in place
-          using <literal>NIXOS_LUSTRATE</literal>:
-        </para>
-      </note>
-      <para>
-        Generate your NixOS configuration:
-      </para>
-      <programlisting>
-$ sudo `which nixos-generate-config` --root /
-</programlisting>
-      <para>
-        Note that this will place the generated configuration files in
-        <literal>/etc/nixos</literal>. You'll probably want to edit the
-        configuration files. Refer to the
-        <literal>nixos-generate-config</literal> step in
-        <xref linkend="sec-installation" /> for more information.
-      </para>
-      <para>
-        You'll likely want to set a root password for your first boot
-        using the configuration files because you won't have a chance to
-        enter a password until after you reboot. You can initalize the
-        root password to an empty one with this line: (and of course
-        don't forget to set one once you've rebooted or to lock the
-        account with <literal>sudo passwd -l root</literal> if you use
-        <literal>sudo</literal>)
-      </para>
-      <programlisting language="bash">
-users.users.root.initialHashedPassword = &quot;&quot;;
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        Build the NixOS closure and install it in the
-        <literal>system</literal> profile:
-      </para>
-      <programlisting>
-$ nix-env -p /nix/var/nix/profiles/system -f '&lt;nixpkgs/nixos&gt;' -I nixos-config=/etc/nixos/configuration.nix -iA system
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        Change ownership of the <literal>/nix</literal> tree to root
-        (since your Nix install was probably single user):
-      </para>
-      <programlisting>
-$ sudo chown -R 0:0 /nix
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        Set up the <literal>/etc/NIXOS</literal> and
-        <literal>/etc/NIXOS_LUSTRATE</literal> files:
-      </para>
-      <para>
-        <literal>/etc/NIXOS</literal> officializes that this is now a
-        NixOS partition (the bootup scripts require its presence).
-      </para>
-      <para>
-        <literal>/etc/NIXOS_LUSTRATE</literal> tells the NixOS bootup
-        scripts to move <emphasis>everything</emphasis> that's in the
-        root partition to <literal>/old-root</literal>. This will move
-        your existing distribution out of the way in the very early
-        stages of the NixOS bootup. There are exceptions (we do need to
-        keep NixOS there after all), so the NixOS lustrate process will
-        not touch:
-      </para>
-      <itemizedlist>
-        <listitem>
-          <para>
-            The <literal>/nix</literal> directory
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            The <literal>/boot</literal> directory
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            Any file or directory listed in
-            <literal>/etc/NIXOS_LUSTRATE</literal> (one per line)
-          </para>
-        </listitem>
-      </itemizedlist>
-      <note>
-        <para>
-          Support for <literal>NIXOS_LUSTRATE</literal> was added in
-          NixOS 16.09. The act of &quot;lustrating&quot; refers to the
-          wiping of the existing distribution. Creating
-          <literal>/etc/NIXOS_LUSTRATE</literal> can also be used on
-          NixOS to remove all mutable files from your root partition
-          (anything that's not in <literal>/nix</literal> or
-          <literal>/boot</literal> gets &quot;lustrated&quot; on the
-          next boot.
-        </para>
-        <para>
-          lustrate /ˈlʌstreɪt/ verb.
-        </para>
-        <para>
-          purify by expiatory sacrifice, ceremonial washing, or some
-          other ritual action.
-        </para>
-      </note>
-      <para>
-        Let's create the files:
-      </para>
-      <programlisting>
-$ sudo touch /etc/NIXOS
-$ sudo touch /etc/NIXOS_LUSTRATE
-</programlisting>
-      <para>
-        Let's also make sure the NixOS configuration files are kept once
-        we reboot on NixOS:
-      </para>
-      <programlisting>
-$ echo etc/nixos | sudo tee -a /etc/NIXOS_LUSTRATE
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        Finally, move the <literal>/boot</literal> directory of your
-        current distribution out of the way (the lustrate process will
-        take care of the rest once you reboot, but this one must be
-        moved out now because NixOS needs to install its own boot files:
-      </para>
-      <warning>
-        <para>
-          Once you complete this step, your current distribution will no
-          longer be bootable! If you didn't get all the NixOS
-          configuration right, especially those settings pertaining to
-          boot loading and root partition, NixOS may not be bootable
-          either. Have a USB rescue device ready in case this happens.
-        </para>
-      </warning>
-      <programlisting>
-$ sudo mv -v /boot /boot.bak &amp;&amp;
-sudo /nix/var/nix/profiles/system/bin/switch-to-configuration boot
-</programlisting>
-      <para>
-        Cross your fingers, reboot, hopefully you should get a NixOS
-        prompt!
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        If for some reason you want to revert to the old distribution,
-        you'll need to boot on a USB rescue disk and do something along
-        these lines:
-      </para>
-      <programlisting>
-# mkdir root
-# mount /dev/sdaX root
-# mkdir root/nixos-root
-# mv -v root/* root/nixos-root/
-# mv -v root/nixos-root/old-root/* root/
-# mv -v root/boot.bak root/boot  # We had renamed this by hand earlier
-# umount root
-# reboot
-</programlisting>
-      <para>
-        This may work as is or you might also need to reinstall the boot
-        loader.
-      </para>
-      <para>
-        And of course, if you're happy with NixOS and no longer need the
-        old distribution:
-      </para>
-      <programlisting>
-sudo rm -rf /old-root
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        It's also worth noting that this whole process can be automated.
-        This is especially useful for Cloud VMs, where provider do not
-        provide NixOS. For instance,
-        <link xlink:href="https://github.com/elitak/nixos-infect">nixos-infect</link>
-        uses the lustrate process to convert Digital Ocean droplets to
-        NixOS from other distributions automatically.
-      </para>
-    </listitem>
-  </orderedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing-kexec.section.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing-kexec.section.xml
deleted file mode 100644
index 46ea0d59b6c3..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing-kexec.section.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-booting-via-kexec">
-  <title><quote>Booting</quote> into NixOS via kexec</title>
-  <para>
-    In some cases, your system might already be booted into/preinstalled
-    with another Linux distribution, and booting NixOS by attaching an
-    installation image is quite a manual process.
-  </para>
-  <para>
-    This is particularly useful for (cloud) providers where you can’t
-    boot a custom image, but get some Debian or Ubuntu installation.
-  </para>
-  <para>
-    In these cases, it might be easier to use <literal>kexec</literal>
-    to <quote>jump into NixOS</quote> from the running system, which
-    only assumes <literal>bash</literal> and <literal>kexec</literal> to
-    be installed on the machine.
-  </para>
-  <para>
-    Note that kexec may not work correctly on some hardware, as devices
-    are not fully re-initialized in the process. In practice, this
-    however is rarely the case.
-  </para>
-  <para>
-    To build the necessary files from your current version of nixpkgs,
-    you can run:
-  </para>
-  <programlisting>
-nix-build -A kexec.x86_64-linux '&lt;nixpkgs/nixos/release.nix&gt;'
-</programlisting>
-  <para>
-    This will create a <literal>result</literal> directory containing
-    the following:
-  </para>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        <literal>bzImage</literal> (the Linux kernel)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>initrd</literal> (the initrd file)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>kexec-boot</literal> (a shellscript invoking
-        <literal>kexec</literal>)
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    These three files are meant to be copied over to the other already
-    running Linux Distribution.
-  </para>
-  <para>
-    Note it’s symlinks pointing elsewhere, so <literal>cd</literal> in,
-    and use <literal>scp * root@$destination</literal> to copy it over,
-    rather than rsync.
-  </para>
-  <para>
-    Once you finished copying, execute <literal>kexec-boot</literal>
-    <emphasis>on the destination</emphasis>, and after some seconds, the
-    machine should be booting into an (ephemeral) NixOS installation
-    medium.
-  </para>
-  <para>
-    In case you want to describe your own system closure to kexec into,
-    instead of the default installer image, you can build your own
-    <literal>configuration.nix</literal>:
-  </para>
-  <programlisting language="bash">
-{ modulesPath, ... }: {
-  imports = [
-    (modulesPath + &quot;/installer/netboot/netboot-minimal.nix&quot;)
-  ];
-
-  services.openssh.enable = true;
-  users.users.root.openssh.authorizedKeys.keys = [
-    &quot;my-ssh-pubkey&quot;
-  ];
-}
-</programlisting>
-  <programlisting>
-nix-build '&lt;nixpkgs/nixos&gt;' \
-  --arg configuration ./configuration.nix
-  --attr config.system.build.kexecTree
-</programlisting>
-  <para>
-    Make sure your <literal>configuration.nix</literal> does still
-    import <literal>netboot-minimal.nix</literal> (or
-    <literal>netboot-base.nix</literal>).
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing-pxe.section.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing-pxe.section.xml
deleted file mode 100644
index 94172de65ea0..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing-pxe.section.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-booting-from-pxe">
-  <title>Booting from the <quote>netboot</quote> media (PXE)</title>
-  <para>
-    Advanced users may wish to install NixOS using an existing PXE or
-    iPXE setup.
-  </para>
-  <para>
-    These instructions assume that you have an existing PXE or iPXE
-    infrastructure and simply want to add the NixOS installer as another
-    option. To build the necessary files from your current version of
-    nixpkgs, you can run:
-  </para>
-  <programlisting>
-nix-build -A netboot.x86_64-linux '&lt;nixpkgs/nixos/release.nix&gt;'
-</programlisting>
-  <para>
-    This will create a <literal>result</literal> directory containing: *
-    <literal>bzImage</literal> – the Linux kernel *
-    <literal>initrd</literal> – the initrd file *
-    <literal>netboot.ipxe</literal> – an example ipxe script
-    demonstrating the appropriate kernel command line arguments for this
-    image
-  </para>
-  <para>
-    If you’re using plain PXE, configure your boot loader to use the
-    <literal>bzImage</literal> and <literal>initrd</literal> files and
-    have it provide the same kernel command line arguments found in
-    <literal>netboot.ipxe</literal>.
-  </para>
-  <para>
-    If you’re using iPXE, depending on how your HTTP/FTP/etc. server is
-    configured you may be able to use <literal>netboot.ipxe</literal>
-    unmodified, or you may need to update the paths to the files to
-    match your server’s directory layout.
-  </para>
-  <para>
-    In the future we may begin making these files available as build
-    products from hydra at which point we will update this documentation
-    with instructions on how to obtain them either for placing on a
-    dedicated TFTP server or to boot them directly over the internet.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing-usb.section.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing-usb.section.xml
deleted file mode 100644
index df266eb16800..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing-usb.section.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-booting-from-usb">
-  <title>Booting from a USB Drive</title>
-  <para>
-    For systems without CD drive, the NixOS live CD can be booted from a
-    USB stick. You can use the <literal>dd</literal> utility to write
-    the image: <literal>dd if=path-to-image of=/dev/sdX</literal>. Be
-    careful about specifying the correct drive; you can use the
-    <literal>lsblk</literal> command to get a list of block devices.
-  </para>
-  <note>
-    <title>On macOS</title>
-    <programlisting>
-$ diskutil list
-[..]
-/dev/diskN (external, physical):
-   #:                       TYPE NAME                    SIZE       IDENTIFIER
-[..]
-$ diskutil unmountDisk diskN
-Unmount of all volumes on diskN was successful
-$ sudo dd if=nix.iso of=/dev/rdiskN bs=1M
-</programlisting>
-    <para>
-      Using the 'raw' <literal>rdiskN</literal> device instead of
-      <literal>diskN</literal> completes in minutes instead of hours.
-      After <literal>dd</literal> completes, a GUI dialog &quot;The disk
-      you inserted was not readable by this computer&quot; will pop up,
-      which can be ignored.
-    </para>
-  </note>
-  <para>
-    The <literal>dd</literal> utility will write the image verbatim to
-    the drive, making it the recommended option for both UEFI and
-    non-UEFI installations.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml
deleted file mode 100644
index c8bb286c8f33..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-instaling-virtualbox-guest">
-  <title>Installing in a VirtualBox guest</title>
-  <para>
-    Installing NixOS into a VirtualBox guest is convenient for users who
-    want to try NixOS without installing it on bare metal. If you want
-    to use a pre-made VirtualBox appliance, it is available at
-    <link xlink:href="https://nixos.org/nixos/download.html">the
-    downloads page</link>. If you want to set up a VirtualBox guest
-    manually, follow these instructions:
-  </para>
-  <orderedlist numeration="arabic">
-    <listitem>
-      <para>
-        Add a New Machine in VirtualBox with OS Type &quot;Linux / Other
-        Linux&quot;
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Base Memory Size: 768 MB or higher.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        New Hard Disk of 8 GB or higher.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Mount the CD-ROM with the NixOS ISO (by clicking on CD/DVD-ROM)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Click on Settings / System / Processor and enable PAE/NX
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Click on Settings / System / Acceleration and enable
-        &quot;VT-x/AMD-V&quot; acceleration
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Click on Settings / Display / Screen and select VMSVGA as
-        Graphics Controller
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Save the settings, start the virtual machine, and continue
-        installation like normal
-      </para>
-    </listitem>
-  </orderedlist>
-  <para>
-    There are a few modifications you should make in configuration.nix.
-    Enable booting:
-  </para>
-  <programlisting language="bash">
-boot.loader.grub.device = &quot;/dev/sda&quot;;
-</programlisting>
-  <para>
-    Also remove the fsck that runs at startup. It will always fail to
-    run, stopping your boot until you press <literal>*</literal>.
-  </para>
-  <programlisting language="bash">
-boot.initrd.checkJournalingFS = false;
-</programlisting>
-  <para>
-    Shared folders can be given a name and a path in the host system in
-    the VirtualBox settings (Machine / Settings / Shared Folders, then
-    click on the &quot;Add&quot; icon). Add the following to the
-    <literal>/etc/nixos/configuration.nix</literal> to auto-mount them.
-    If you do not add <literal>&quot;nofail&quot;</literal>, the system
-    will not boot properly.
-  </para>
-  <programlisting language="bash">
-{ config, pkgs, ...} :
-{
-  fileSystems.&quot;/virtualboxshare&quot; = {
-    fsType = &quot;vboxsf&quot;;
-    device = &quot;nameofthesharedfolder&quot;;
-    options = [ &quot;rw&quot; &quot;nofail&quot; ];
-  };
-}
-</programlisting>
-  <para>
-    The folder will be available directly under the root directory.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml
deleted file mode 100644
index 0112458674b5..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml
+++ /dev/null
@@ -1,665 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-installation">
-  <title>Installing NixOS</title>
-  <section xml:id="sec-installation-booting">
-    <title>Booting the system</title>
-    <para>
-      NixOS can be installed on BIOS or UEFI systems. The procedure for
-      a UEFI installation is by and large the same as a BIOS
-      installation. The differences are mentioned in the steps that
-      follow.
-    </para>
-    <para>
-      The installation media can be burned to a CD, or now more
-      commonly, <quote>burned</quote> to a USB drive (see
-      <xref linkend="sec-booting-from-usb" />).
-    </para>
-    <para>
-      The installation media contains a basic NixOS installation. When
-      it’s finished booting, it should have detected most of your
-      hardware.
-    </para>
-    <para>
-      The NixOS manual is available by running
-      <literal>nixos-help</literal>.
-    </para>
-    <para>
-      You are logged-in automatically as <literal>nixos</literal>. The
-      <literal>nixos</literal> user account has an empty password so you
-      can use <literal>sudo</literal> without a password:
-    </para>
-    <programlisting>
-$ sudo -i
-</programlisting>
-    <para>
-      If you downloaded the graphical ISO image, you can run
-      <literal>systemctl start display-manager</literal> to start the
-      desktop environment. If you want to continue on the terminal, you
-      can use <literal>loadkeys</literal> to switch to your preferred
-      keyboard layout. (We even provide neo2 via
-      <literal>loadkeys de neo</literal>!)
-    </para>
-    <para>
-      If the text is too small to be legible, try
-      <literal>setfont ter-v32n</literal> to increase the font size.
-    </para>
-    <para>
-      To install over a serial port connect with
-      <literal>115200n8</literal> (e.g.
-      <literal>picocom -b 115200 /dev/ttyUSB0</literal>). When the
-      bootloader lists boot entries, select the serial console boot
-      entry.
-    </para>
-    <section xml:id="sec-installation-booting-networking">
-      <title>Networking in the installer</title>
-      <para>
-        The boot process should have brought up networking (check
-        <literal>ip a</literal>). Networking is necessary for the
-        installer, since it will download lots of stuff (such as source
-        tarballs or Nixpkgs channel binaries). It’s best if you have a
-        DHCP server on your network. Otherwise configure networking
-        manually using <literal>ifconfig</literal>.
-      </para>
-      <para>
-        On the graphical installer, you can configure the network, wifi
-        included, through NetworkManager. Using the
-        <literal>nmtui</literal> program, you can do so even in a
-        non-graphical session. If you prefer to configure the network
-        manually, disable NetworkManager with
-        <literal>systemctl stop NetworkManager</literal>.
-      </para>
-      <para>
-        On the minimal installer, NetworkManager is not available, so
-        configuration must be perfomed manually. To configure the wifi,
-        first start wpa_supplicant with
-        <literal>sudo systemctl start wpa_supplicant</literal>, then run
-        <literal>wpa_cli</literal>. For most home networks, you need to
-        type in the following commands:
-      </para>
-      <programlisting>
-&gt; add_network
-0
-&gt; set_network 0 ssid &quot;myhomenetwork&quot;
-OK
-&gt; set_network 0 psk &quot;mypassword&quot;
-OK
-&gt; set_network 0 key_mgmt WPA-PSK
-OK
-&gt; enable_network 0
-OK
-</programlisting>
-      <para>
-        For enterprise networks, for example
-        <emphasis>eduroam</emphasis>, instead do:
-      </para>
-      <programlisting>
-&gt; add_network
-0
-&gt; set_network 0 ssid &quot;eduroam&quot;
-OK
-&gt; set_network 0 identity &quot;myname@example.com&quot;
-OK
-&gt; set_network 0 password &quot;mypassword&quot;
-OK
-&gt; set_network 0 key_mgmt WPA-EAP
-OK
-&gt; enable_network 0
-OK
-</programlisting>
-      <para>
-        When successfully connected, you should see a line such as this
-        one
-      </para>
-      <programlisting>
-&lt;3&gt;CTRL-EVENT-CONNECTED - Connection to 32:85:ab:ef:24:5c completed [id=0 id_str=]
-</programlisting>
-      <para>
-        you can now leave <literal>wpa_cli</literal> by typing
-        <literal>quit</literal>.
-      </para>
-      <para>
-        If you would like to continue the installation from a different
-        machine you can use activated SSH daemon. You need to copy your
-        ssh key to either
-        <literal>/home/nixos/.ssh/authorized_keys</literal> or
-        <literal>/root/.ssh/authorized_keys</literal> (Tip: For
-        installers with a modifiable filesystem such as the sd-card
-        installer image a key can be manually placed by mounting the
-        image on a different machine). Alternatively you must set a
-        password for either <literal>root</literal> or
-        <literal>nixos</literal> with <literal>passwd</literal> to be
-        able to login.
-      </para>
-    </section>
-  </section>
-  <section xml:id="sec-installation-partitioning">
-    <title>Partitioning and formatting</title>
-    <para>
-      The NixOS installer doesn’t do any partitioning or formatting, so
-      you need to do that yourself.
-    </para>
-    <para>
-      The NixOS installer ships with multiple partitioning tools. The
-      examples below use <literal>parted</literal>, but also provides
-      <literal>fdisk</literal>, <literal>gdisk</literal>,
-      <literal>cfdisk</literal>, and <literal>cgdisk</literal>.
-    </para>
-    <para>
-      The recommended partition scheme differs depending if the computer
-      uses <emphasis>Legacy Boot</emphasis> or
-      <emphasis>UEFI</emphasis>.
-    </para>
-    <section xml:id="sec-installation-partitioning-UEFI">
-      <title>UEFI (GPT)</title>
-      <para>
-        Here's an example partition scheme for UEFI, using
-        <literal>/dev/sda</literal> as the device.
-      </para>
-      <note>
-        <para>
-          You can safely ignore <literal>parted</literal>'s
-          informational message about needing to update /etc/fstab.
-        </para>
-      </note>
-      <orderedlist numeration="arabic">
-        <listitem>
-          <para>
-            Create a <emphasis>GPT</emphasis> partition table.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mklabel gpt
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            Add the <emphasis>root</emphasis> partition. This will fill
-            the disk except for the end part, where the swap will live,
-            and the space left in front (512MiB) which will be used by
-            the boot partition.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart primary 512MB -8GB
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            Next, add a <emphasis>swap</emphasis> partition. The size
-            required will vary according to needs, here a 8GB one is
-            created.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart primary linux-swap -8GB 100%
-</programlisting>
-          <note>
-            <para>
-              The swap partition size rules are no different than for
-              other Linux distributions.
-            </para>
-          </note>
-        </listitem>
-        <listitem>
-          <para>
-            Finally, the <emphasis>boot</emphasis> partition. NixOS by
-            default uses the ESP (EFI system partition) as its
-            <emphasis>/boot</emphasis> partition. It uses the initially
-            reserved 512MiB at the start of the disk.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart ESP fat32 1MB 512MB
-# parted /dev/sda -- set 3 esp on
-</programlisting>
-        </listitem>
-      </orderedlist>
-      <para>
-        Once complete, you can follow with
-        <xref linkend="sec-installation-partitioning-formatting" />.
-      </para>
-    </section>
-    <section xml:id="sec-installation-partitioning-MBR">
-      <title>Legacy Boot (MBR)</title>
-      <para>
-        Here's an example partition scheme for Legacy Boot, using
-        <literal>/dev/sda</literal> as the device.
-      </para>
-      <note>
-        <para>
-          You can safely ignore <literal>parted</literal>'s
-          informational message about needing to update /etc/fstab.
-        </para>
-      </note>
-      <orderedlist numeration="arabic">
-        <listitem>
-          <para>
-            Create a <emphasis>MBR</emphasis> partition table.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mklabel msdos
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            Add the <emphasis>root</emphasis> partition. This will fill
-            the the disk except for the end part, where the swap will
-            live.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart primary 1MB -8GB
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            Finally, add a <emphasis>swap</emphasis> partition. The size
-            required will vary according to needs, here a 8GiB one is
-            created.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart primary linux-swap -8GB 100%
-</programlisting>
-          <note>
-            <para>
-              The swap partition size rules are no different than for
-              other Linux distributions.
-            </para>
-          </note>
-        </listitem>
-      </orderedlist>
-      <para>
-        Once complete, you can follow with
-        <xref linkend="sec-installation-partitioning-formatting" />.
-      </para>
-    </section>
-    <section xml:id="sec-installation-partitioning-formatting">
-      <title>Formatting</title>
-      <para>
-        Use the following commands:
-      </para>
-      <itemizedlist>
-        <listitem>
-          <para>
-            For initialising Ext4 partitions:
-            <literal>mkfs.ext4</literal>. It is recommended that you
-            assign a unique symbolic label to the file system using the
-            option <literal>-L label</literal>, since this makes the
-            file system configuration independent from device changes.
-            For example:
-          </para>
-          <programlisting>
-# mkfs.ext4 -L nixos /dev/sda1
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            For creating swap partitions: <literal>mkswap</literal>.
-            Again it’s recommended to assign a label to the swap
-            partition: <literal>-L label</literal>. For example:
-          </para>
-          <programlisting>
-# mkswap -L swap /dev/sda2
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            <emphasis role="strong">UEFI systems</emphasis>
-          </para>
-          <para>
-            For creating boot partitions: <literal>mkfs.fat</literal>.
-            Again it’s recommended to assign a label to the boot
-            partition: <literal>-n label</literal>. For example:
-          </para>
-          <programlisting>
-# mkfs.fat -F 32 -n boot /dev/sda3
-</programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            For creating LVM volumes, the LVM commands, e.g.,
-            <literal>pvcreate</literal>, <literal>vgcreate</literal>,
-            and <literal>lvcreate</literal>.
-          </para>
-        </listitem>
-        <listitem>
-          <para>
-            For creating software RAID devices, use
-            <literal>mdadm</literal>.
-          </para>
-        </listitem>
-      </itemizedlist>
-    </section>
-  </section>
-  <section xml:id="sec-installation-installing">
-    <title>Installing</title>
-    <orderedlist numeration="arabic">
-      <listitem>
-        <para>
-          Mount the target file system on which NixOS should be
-          installed on <literal>/mnt</literal>, e.g.
-        </para>
-        <programlisting>
-# mount /dev/disk/by-label/nixos /mnt
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <emphasis role="strong">UEFI systems</emphasis>
-        </para>
-        <para>
-          Mount the boot file system on <literal>/mnt/boot</literal>,
-          e.g.
-        </para>
-        <programlisting>
-# mkdir -p /mnt/boot
-# mount /dev/disk/by-label/boot /mnt/boot
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          If your machine has a limited amount of memory, you may want
-          to activate swap devices now
-          (<literal>swapon device</literal>). The installer (or rather,
-          the build actions that it may spawn) may need quite a bit of
-          RAM, depending on your configuration.
-        </para>
-        <programlisting>
-# swapon /dev/sda2
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          You now need to create a file
-          <literal>/mnt/etc/nixos/configuration.nix</literal> that
-          specifies the intended configuration of the system. This is
-          because NixOS has a <emphasis>declarative</emphasis>
-          configuration model: you create or edit a description of the
-          desired configuration of your system, and then NixOS takes
-          care of making it happen. The syntax of the NixOS
-          configuration file is described in
-          <xref linkend="sec-configuration-syntax" />, while a list of
-          available configuration options appears in
-          <xref linkend="ch-options" />. A minimal example is shown in
-          <link linkend="ex-config">Example: NixOS Configuration</link>.
-        </para>
-        <para>
-          The command <literal>nixos-generate-config</literal> can
-          generate an initial configuration file for you:
-        </para>
-        <programlisting>
-# nixos-generate-config --root /mnt
-</programlisting>
-        <para>
-          You should then edit
-          <literal>/mnt/etc/nixos/configuration.nix</literal> to suit
-          your needs:
-        </para>
-        <programlisting>
-# nano /mnt/etc/nixos/configuration.nix
-</programlisting>
-        <para>
-          If you’re using the graphical ISO image, other editors may be
-          available (such as <literal>vim</literal>). If you have
-          network access, you can also install other editors – for
-          instance, you can install Emacs by running
-          <literal>nix-env -f '&lt;nixpkgs&gt;' -iA emacs</literal>.
-        </para>
-        <variablelist>
-          <varlistentry>
-            <term>
-              BIOS systems
-            </term>
-            <listitem>
-              <para>
-                You <emphasis>must</emphasis> set the option
-                <xref linkend="opt-boot.loader.grub.device" /> to
-                specify on which disk the GRUB boot loader is to be
-                installed. Without it, NixOS cannot boot.
-              </para>
-              <para>
-                If there are other operating systems running on the
-                machine before installing NixOS, the
-                <xref linkend="opt-boot.loader.grub.useOSProber" />
-                option can be set to <literal>true</literal> to
-                automatically add them to the grub menu.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term>
-              UEFI systems
-            </term>
-            <listitem>
-              <para>
-                You must select a boot-loader, either system-boot or
-                GRUB. The recommended option is systemd-boot: set the
-                option
-                <xref linkend="opt-boot.loader.systemd-boot.enable" />
-                to <literal>true</literal>.
-                <literal>nixos-generate-config</literal> should do this
-                automatically for new configurations when booted in UEFI
-                mode.
-              </para>
-              <para>
-                You may want to look at the options starting with
-                <link linkend="opt-boot.loader.efi.canTouchEfiVariables"><literal>boot.loader.efi</literal></link>
-                and
-                <link linkend="opt-boot.loader.systemd-boot.enable"><literal>boot.loader.systemd-boot</literal></link>
-                as well.
-              </para>
-              <para>
-                If you want to use GRUB, set
-                <xref linkend="opt-boot.loader.grub.device" /> to
-                <literal>nodev</literal> and
-                <xref linkend="opt-boot.loader.grub.efiSupport" /> to
-                <literal>true</literal>.
-              </para>
-              <para>
-                With system-boot, you should not need any special
-                configuration to detect other installed systems. With
-                GRUB, set
-                <xref linkend="opt-boot.loader.grub.useOSProber" /> to
-                <literal>true</literal>, but this will only detect
-                windows partitions, not other linux distributions. If
-                you dual boot another linux distribution, use
-                system-boot instead.
-              </para>
-            </listitem>
-          </varlistentry>
-        </variablelist>
-        <para>
-          If you need to configure networking for your machine the
-          configuration options are described in
-          <xref linkend="sec-networking" />. In particular, while wifi
-          is supported on the installation image, it is not enabled by
-          default in the configuration generated by
-          <literal>nixos-generate-config</literal>.
-        </para>
-        <para>
-          Another critical option is <literal>fileSystems</literal>,
-          specifying the file systems that need to be mounted by NixOS.
-          However, you typically don’t need to set it yourself, because
-          <literal>nixos-generate-config</literal> sets it automatically
-          in
-          <literal>/mnt/etc/nixos/hardware-configuration.nix</literal>
-          from your currently mounted file systems. (The configuration
-          file <literal>hardware-configuration.nix</literal> is included
-          from <literal>configuration.nix</literal> and will be
-          overwritten by future invocations of
-          <literal>nixos-generate-config</literal>; thus, you generally
-          should not modify it.) Additionally, you may want to look at
-          <link xlink:href="https://github.com/NixOS/nixos-hardware">Hardware
-          configuration for known-hardware</link> at this point or after
-          installation.
-        </para>
-        <note>
-          <para>
-            Depending on your hardware configuration or type of file
-            system, you may need to set the option
-            <literal>boot.initrd.kernelModules</literal> to include the
-            kernel modules that are necessary for mounting the root file
-            system, otherwise the installed system will not be able to
-            boot. (If this happens, boot from the installation media
-            again, mount the target file system on
-            <literal>/mnt</literal>, fix
-            <literal>/mnt/etc/nixos/configuration.nix</literal> and
-            rerun <literal>nixos-install</literal>.) In most cases,
-            <literal>nixos-generate-config</literal> will figure out the
-            required modules.
-          </para>
-        </note>
-      </listitem>
-      <listitem>
-        <para>
-          Do the installation:
-        </para>
-        <programlisting>
-# nixos-install
-</programlisting>
-        <para>
-          This will install your system based on the configuration you
-          provided. If anything fails due to a configuration problem or
-          any other issue (such as a network outage while downloading
-          binaries from the NixOS binary cache), you can re-run
-          <literal>nixos-install</literal> after fixing your
-          <literal>configuration.nix</literal>.
-        </para>
-        <para>
-          As the last step, <literal>nixos-install</literal> will ask
-          you to set the password for the <literal>root</literal> user,
-          e.g.
-        </para>
-        <programlisting>
-setting root password...
-New password: ***
-Retype new password: ***
-</programlisting>
-        <note>
-          <para>
-            For unattended installations, it is possible to use
-            <literal>nixos-install --no-root-passwd</literal> in order
-            to disable the password prompt entirely.
-          </para>
-        </note>
-      </listitem>
-      <listitem>
-        <para>
-          If everything went well:
-        </para>
-        <programlisting>
-# reboot
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          You should now be able to boot into the installed NixOS. The
-          GRUB boot menu shows a list of <emphasis>available
-          configurations</emphasis> (initially just one). Every time you
-          change the NixOS configuration (see
-          <link linkend="sec-changing-config">Changing
-          Configuration</link>), a new item is added to the menu. This
-          allows you to easily roll back to a previous configuration if
-          something goes wrong.
-        </para>
-        <para>
-          You should log in and change the <literal>root</literal>
-          password with <literal>passwd</literal>.
-        </para>
-        <para>
-          You’ll probably want to create some user accounts as well,
-          which can be done with <literal>useradd</literal>:
-        </para>
-        <programlisting>
-$ useradd -c 'Eelco Dolstra' -m eelco
-$ passwd eelco
-</programlisting>
-        <para>
-          You may also want to install some software. This will be
-          covered in <xref linkend="sec-package-management" />.
-        </para>
-      </listitem>
-    </orderedlist>
-  </section>
-  <section xml:id="sec-installation-summary">
-    <title>Installation summary</title>
-    <para>
-      To summarise, <link linkend="ex-install-sequence">Example:
-      Commands for Installing NixOS on
-      <literal>/dev/sda</literal></link> shows a typical sequence of
-      commands for installing NixOS on an empty hard drive (here
-      <literal>/dev/sda</literal>). <link linkend="ex-config">Example:
-      NixOS Configuration</link> shows a corresponding configuration Nix
-      expression.
-    </para>
-    <anchor xml:id="ex-partition-scheme-MBR" />
-    <para>
-      <emphasis role="strong">Example: Example partition schemes for
-      NixOS on <literal>/dev/sda</literal> (MBR)</emphasis>
-    </para>
-    <programlisting>
-# parted /dev/sda -- mklabel msdos
-# parted /dev/sda -- mkpart primary 1MiB -8GiB
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
-</programlisting>
-    <anchor xml:id="ex-partition-scheme-UEFI" />
-    <para>
-      <emphasis role="strong">Example: Example partition schemes for
-      NixOS on <literal>/dev/sda</literal> (UEFI)</emphasis>
-    </para>
-    <programlisting>
-# parted /dev/sda -- mklabel gpt
-# parted /dev/sda -- mkpart primary 512MiB -8GiB
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
-# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-# parted /dev/sda -- set 3 esp on
-</programlisting>
-    <anchor xml:id="ex-install-sequence" />
-    <para>
-      <emphasis role="strong">Example: Commands for Installing NixOS on
-      <literal>/dev/sda</literal></emphasis>
-    </para>
-    <para>
-      With a partitioned disk.
-    </para>
-    <programlisting>
-# mkfs.ext4 -L nixos /dev/sda1
-# mkswap -L swap /dev/sda2
-# swapon /dev/sda2
-# mkfs.fat -F 32 -n boot /dev/sda3        # (for UEFI systems only)
-# mount /dev/disk/by-label/nixos /mnt
-# mkdir -p /mnt/boot                      # (for UEFI systems only)
-# mount /dev/disk/by-label/boot /mnt/boot # (for UEFI systems only)
-# nixos-generate-config --root /mnt
-# nano /mnt/etc/nixos/configuration.nix
-# nixos-install
-# reboot
-</programlisting>
-    <anchor xml:id="ex-config" />
-    <para>
-      <emphasis role="strong">Example: NixOS Configuration</emphasis>
-    </para>
-    <programlisting>
-{ config, pkgs, ... }: {
-  imports = [
-    # Include the results of the hardware scan.
-    ./hardware-configuration.nix
-  ];
-
-  boot.loader.grub.device = &quot;/dev/sda&quot;;   # (for BIOS systems only)
-  boot.loader.systemd-boot.enable = true; # (for UEFI systems only)
-
-  # Note: setting fileSystems is generally not
-  # necessary, since nixos-generate-config figures them out
-  # automatically in hardware-configuration.nix.
-  #fileSystems.&quot;/&quot;.device = &quot;/dev/disk/by-label/nixos&quot;;
-
-  # Enable the OpenSSH server.
-  services.sshd.enable = true;
-}
-</programlisting>
-  </section>
-  <section xml:id="sec-installation-additional-notes">
-    <title>Additional installation notes</title>
-    <xi:include href="installing-usb.section.xml" />
-    <xi:include href="installing-pxe.section.xml" />
-    <xi:include href="installing-kexec.section.xml" />
-    <xi:include href="installing-virtualbox-guest.section.xml" />
-    <xi:include href="installing-from-other-distro.section.xml" />
-    <xi:include href="installing-behind-a-proxy.section.xml" />
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/obtaining.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/obtaining.chapter.xml
deleted file mode 100644
index a922feda2536..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/obtaining.chapter.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-obtaining">
-  <title>Obtaining NixOS</title>
-  <para>
-    NixOS ISO images can be downloaded from the
-    <link xlink:href="https://nixos.org/nixos/download.html">NixOS
-    download page</link>. There are a number of installation options. If
-    you happen to have an optical drive and a spare CD, burning the
-    image to CD and booting from that is probably the easiest option.
-    Most people will need to prepare a USB stick to boot from.
-    <xref linkend="sec-booting-from-usb" /> describes the preferred
-    method to prepare a USB stick. A number of alternative methods are
-    presented in the
-    <link xlink:href="https://nixos.wiki/wiki/NixOS_Installation_Guide#Making_the_installation_media">NixOS
-    Wiki</link>.
-  </para>
-  <para>
-    As an alternative to installing NixOS yourself, you can get a
-    running NixOS system through several other means:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Using virtual appliances in Open Virtualization Format (OVF)
-        that can be imported into VirtualBox. These are available from
-        the
-        <link xlink:href="https://nixos.org/nixos/download.html">NixOS
-        download page</link>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Using AMIs for Amazon’s EC2. To find one for your region and
-        instance type, please refer to the
-        <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/ec2-amis.nix">list
-        of most recent AMIs</link>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Using NixOps, the NixOS-based cloud deployment tool, which
-        allows you to provision VirtualBox and EC2 NixOS instances from
-        declarative specifications. Check out the
-        <link xlink:href="https://nixos.org/nixops">NixOps
-        homepage</link> for details.
-      </para>
-    </listitem>
-  </itemizedlist>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
deleted file mode 100644
index 11fe1d317ccd..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
+++ /dev/null
@@ -1,152 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-upgrading">
-  <title>Upgrading NixOS</title>
-  <para>
-    The best way to keep your NixOS installation up to date is to use
-    one of the NixOS <emphasis>channels</emphasis>. A channel is a Nix
-    mechanism for distributing Nix 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:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <emphasis>Stable channels</emphasis>, such as
-        <link xlink:href="https://nixos.org/channels/nixos-22.05"><literal>nixos-22.05</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),
-        but not from 4.19.x to 4.20.x (a major change that has the
-        potential to break things). Stable channels are generally
-        maintained until the next stable branch is created.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <emphasis>unstable channel</emphasis>,
-        <link xlink:href="https://nixos.org/channels/nixos-unstable"><literal>nixos-unstable</literal></link>.
-        This corresponds to NixOS’s main development branch, and may
-        thus see radical changes between channel updates. It’s not
-        recommended for production systems.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <emphasis>Small channels</emphasis>, such as
-        <link xlink:href="https://nixos.org/channels/nixos-22.05-small"><literal>nixos-22.05-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
-        described above, except that they contain fewer binary packages.
-        This means they get updated faster than the regular channels
-        (for instance, when a critical security patch is committed to
-        NixOS’s source tree), but may require more packages to be built
-        from source than usual. They’re mostly intended for server
-        environments and as such contain few GUI applications.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    To see what channels are available, go to
-    <link xlink:href="https://nixos.org/channels">https://nixos.org/channels</link>.
-    (Note that the URIs of the various channels redirect to a directory
-    that contains the channel’s latest version and includes ISO images
-    and VirtualBox appliances.) Please note that during the release
-    process, channels that are not yet released will be present here as
-    well. See the Getting NixOS page
-    <link xlink:href="https://nixos.org/nixos/download.html">https://nixos.org/nixos/download.html</link>
-    to find the newest supported stable release.
-  </para>
-  <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 22.05 ISO, you will be subscribed
-    to the <literal>nixos-22.05</literal> channel. To see which NixOS
-    channel you’re subscribed to, run the following as root:
-  </para>
-  <programlisting>
-# nix-channel --list | grep nixos
-nixos https://nixos.org/channels/nixos-unstable
-</programlisting>
-  <para>
-    To switch to a different NixOS channel, do
-  </para>
-  <programlisting>
-# nix-channel --add https://nixos.org/channels/channel-name nixos
-</programlisting>
-  <para>
-    (Be sure to include the <literal>nixos</literal> parameter at the
-    end.) For instance, to use the NixOS 22.05 stable channel:
-  </para>
-  <programlisting>
-# nix-channel --add https://nixos.org/channels/nixos-22.05 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-22.05-small nixos
-</programlisting>
-  <para>
-    And if you want to live on the bleeding edge:
-  </para>
-  <programlisting>
-# nix-channel --add https://nixos.org/channels/nixos-unstable nixos
-</programlisting>
-  <para>
-    You can then upgrade NixOS to the latest version in your chosen
-    channel by running
-  </para>
-  <programlisting>
-# nixos-rebuild switch --upgrade
-</programlisting>
-  <para>
-    which is equivalent to the more verbose
-    <literal>nix-channel --update nixos; nixos-rebuild switch</literal>.
-  </para>
-  <note>
-    <para>
-      Channels are set per user. This means that running
-      <literal>nix-channel --add</literal> as a non root user (or
-      without sudo) will not affect configuration in
-      <literal>/etc/nixos/configuration.nix</literal>
-    </para>
-  </note>
-  <warning>
-    <para>
-      It is generally safe to switch back and forth between channels.
-      The only exception is that a newer NixOS may also have a newer Nix
-      version, which may involve an upgrade of Nix’s database schema.
-      This cannot be undone easily, so in that case you will not be able
-      to go back to your original channel.
-    </para>
-  </warning>
-  <section xml:id="sec-upgrading-automatic">
-    <title>Automatic Upgrades</title>
-    <para>
-      You can keep a NixOS system up-to-date automatically by adding the
-      following to <literal>configuration.nix</literal>:
-    </para>
-    <programlisting language="bash">
-system.autoUpgrade.enable = true;
-system.autoUpgrade.allowReboot = true;
-</programlisting>
-    <para>
-      This enables a periodically executed systemd service named
-      <literal>nixos-upgrade.service</literal>. If the
-      <literal>allowReboot</literal> option is <literal>false</literal>,
-      it runs <literal>nixos-rebuild switch --upgrade</literal> to
-      upgrade NixOS to the latest version in the current channel. (To
-      see when the service runs, see
-      <literal>systemctl list-timers</literal>.) If
-      <literal>allowReboot</literal> is <literal>true</literal>, then
-      the system will automatically reboot if the new generation
-      contains a different kernel, initrd or kernel modules. You can
-      also specify a channel explicitly, e.g.
-    </para>
-    <programlisting language="bash">
-system.autoUpgrade.channel = https://nixos.org/channels/nixos-22.05;
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1310.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1310.section.xml
deleted file mode 100644
index b4f3657b4b88..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1310.section.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-13.10">
-  <title>Release 13.10 (<quote>Aardvark</quote>, 2013/10/31)</title>
-  <para>
-    This is the first stable release branch of NixOS.
-  </para>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1404.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1404.section.xml
deleted file mode 100644
index 8771623b468a..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1404.section.xml
+++ /dev/null
@@ -1,189 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-14.04">
-  <title>Release 14.04 (<quote>Baboon</quote>, 2014/04/30)</title>
-  <para>
-    This is the second stable release branch of NixOS. In addition to
-    numerous new and upgraded packages and modules, this release has the
-    following highlights:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Installation on UEFI systems is now supported. See
-        <xref linkend="sec-installation" /> for details.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Systemd has been updated to version 212, which has
-        <link xlink:href="http://cgit.freedesktop.org/systemd/systemd/plain/NEWS?id=v212">numerous
-        improvements</link>. NixOS now automatically starts systemd user
-        instances when you log in. You can define global user units
-        through the <literal>systemd.unit.*</literal> options.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        NixOS is now based on Glibc 2.19 and GCC 4.8.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The default Linux kernel has been updated to 3.12.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        KDE has been updated to 4.12.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        GNOME 3.10 experimental support has been added.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Nix has been updated to 1.7
-        (<link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-1.7">details</link>).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        NixOS now supports fully declarative management of users and
-        groups. If you set <literal>users.mutableUsers</literal> to
-        <literal>false</literal>, then the contents of
-        <literal>/etc/passwd</literal> and <literal>/etc/group</literal>
-        will be
-        <link xlink:href="https://www.usenix.org/legacy/event/lisa02/tech/full_papers/traugott/traugott_html/">congruent</link>
-        to your NixOS configuration. For instance, if you remove a user
-        from <literal>users.extraUsers</literal> and run
-        <literal>nixos-rebuild</literal>, the user account will cease to
-        exist. Also, imperative commands for managing users and groups,
-        such as <literal>useradd</literal>, are no longer available. If
-        <literal>users.mutableUsers</literal> is <literal>true</literal>
-        (the default), then behaviour is unchanged from NixOS 13.10.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        NixOS now has basic container support, meaning you can easily
-        run a NixOS instance as a container in a NixOS host system.
-        These containers are suitable for testing and experimentation
-        but not production use, since they’re not fully isolated from
-        the host. See <xref linkend="ch-containers" /> for details.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Systemd units provided by packages can now be overridden from
-        the NixOS configuration. For instance, if a package
-        <literal>foo</literal> provides systemd units, you can say:
-      </para>
-      <programlisting language="bash">
-{
-  systemd.packages = [ pkgs.foo ];
-}
-</programlisting>
-      <para>
-        to enable those units. You can then set or override unit options
-        in the usual way, e.g.
-      </para>
-      <programlisting language="bash">
-{
-  systemd.services.foo.wantedBy = [ &quot;multi-user.target&quot; ];
-  systemd.services.foo.serviceConfig.MemoryLimit = &quot;512M&quot;;
-}
-</programlisting>
-      <para>
-        When upgrading from a previous release, please be aware of the
-        following incompatible changes:
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Nixpkgs no longer exposes unfree packages by default. If your
-        NixOS configuration requires unfree packages from Nixpkgs, you
-        need to enable support for them explicitly by setting:
-      </para>
-      <programlisting language="bash">
-{
-  nixpkgs.config.allowUnfree = true;
-}
-</programlisting>
-      <para>
-        Otherwise, you get an error message such as:
-      </para>
-      <programlisting>
-    error: package ‘nvidia-x11-331.49-3.12.17’ in ‘…/nvidia-x11/default.nix:56’
-      has an unfree license, refusing to evaluate
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        The Adobe Flash player is no longer enabled by default in the
-        Firefox and Chromium wrappers. To enable it, you must set:
-      </para>
-      <programlisting language="bash">
-{
-  nixpkgs.config.allowUnfree = true;
-  nixpkgs.config.firefox.enableAdobeFlash = true; # for Firefox
-  nixpkgs.config.chromium.enableAdobeFlash = true; # for Chromium
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        The firewall is now enabled by default. If you don’t want this,
-        you need to disable it explicitly:
-      </para>
-      <programlisting language="bash">
-{
-  networking.firewall.enable = false;
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        The option <literal>boot.loader.grub.memtest86</literal> has
-        been renamed to
-        <literal>boot.loader.grub.memtest86.enable</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>mysql55</literal> service has been merged into the
-        <literal>mysql</literal> service, which no longer sets a default
-        for the option <literal>services.mysql.package</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Package variants are now differentiated by suffixing the name,
-        rather than the version. For instance,
-        <literal>sqlite-3.8.4.3-interactive</literal> is now called
-        <literal>sqlite-interactive-3.8.4.3</literal>. This ensures that
-        <literal>nix-env -i sqlite</literal> is unambiguous, and that
-        <literal>nix-env -u</literal> won’t <quote>upgrade</quote>
-        <literal>sqlite</literal> to
-        <literal>sqlite-interactive</literal> or vice versa. Notably,
-        this change affects the Firefox wrapper (which provides
-        plugins), as it is now called
-        <literal>firefox-wrapper</literal>. So when using
-        <literal>nix-env</literal>, you should do
-        <literal>nix-env -e firefox; nix-env -i firefox-wrapper</literal>
-        if you want to keep using the wrapper. This change does not
-        affect declarative package management, since attribute names
-        like <literal>pkgs.firefoxWrapper</literal> were already
-        unambiguous.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The symlink <literal>/etc/ca-bundle.crt</literal> is gone.
-        Programs should instead use the environment variable
-        <literal>OPENSSL_X509_CERT_FILE</literal> (which points to
-        <literal>/etc/ssl/certs/ca-bundle.crt</literal>).
-      </para>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1412.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1412.section.xml
deleted file mode 100644
index 3b6af73359d6..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1412.section.xml
+++ /dev/null
@@ -1,466 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-14.12">
-  <title>Release 14.12 (<quote>Caterpillar</quote>, 2014/12/30)</title>
-  <para>
-    In addition to numerous new and upgraded packages, this release has
-    the following highlights:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Systemd has been updated to version 217, which has numerous
-        <link xlink:href="http://lists.freedesktop.org/archives/systemd-devel/2014-October/024662.html">improvements.</link>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <link xlink:href="https://www.mail-archive.com/nix-dev@lists.science.uu.nl/msg13957.html">Nix
-        has been updated to 1.8.</link>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        NixOS is now based on Glibc 2.20.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        KDE has been updated to 4.14.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The default Linux kernel has been updated to 3.14.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        If <literal>users.mutableUsers</literal> is enabled (the
-        default), changes made to the declaration of a user or group
-        will be correctly realised when running
-        <literal>nixos-rebuild</literal>. For instance, removing a user
-        specification from <literal>configuration.nix</literal> will
-        cause the actual user account to be deleted. If
-        <literal>users.mutableUsers</literal> is disabled, it is no
-        longer necessary to specify UIDs or GIDs; if omitted, they are
-        allocated dynamically.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    Following new services were added since the last release:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <literal>atftpd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>bosun</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>bspwm</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>chronos</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>collectd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>consul</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>cpuminer-cryptonight</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>crashplan</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>dnscrypt-proxy</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>docker-registry</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>docker</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>etcd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>fail2ban</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>fcgiwrap</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>fleet</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>fluxbox</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gdm</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>geoclue2</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gitlab</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gitolite</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gnome3.gnome-documents</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gnome3.gnome-online-miners</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gnome3.gvfs</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>gnome3.seahorse</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>hbase</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>i2pd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>influxdb</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>kubernetes</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>liquidsoap</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>lxc</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>mailpile</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>mesos</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>mlmmj</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>monetdb</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>mopidy</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>neo4j</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>nsd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>openntpd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>opentsdb</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>openvswitch</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>parallels-guest</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>peerflix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>phd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>polipo</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>prosody</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>radicale</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>redmine</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>riemann</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>scollector</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>seeks</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>siproxd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>strongswan</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>tcsd</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>teamspeak3</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>thermald</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>torque/mrom</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>torque/server</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>uhub</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>unifi</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>znc</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>zookeeper</literal>
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    When upgrading from a previous release, please be aware of the
-    following incompatible changes:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        The default version of Apache httpd is now 2.4. If you use the
-        <literal>extraConfig</literal> option to pass literal Apache
-        configuration text, you may need to update it — see
-        <link xlink:href="http://httpd.apache.org/docs/2.4/upgrading.html">Apache’s
-        documentation</link> for details. If you wish to continue to use
-        httpd 2.2, add the following line to your NixOS configuration:
-      </para>
-      <programlisting language="bash">
-{
-  services.httpd.package = pkgs.apacheHttpd_2_2;
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        PHP 5.3 has been removed because it is no longer supported by
-        the PHP project. A
-        <link xlink:href="http://php.net/migration54">migration
-        guide</link> is available.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The host side of a container virtual Ethernet pair is now called
-        <literal>ve-container-name</literal> rather than
-        <literal>c-container-name</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        GNOME 3.10 support has been dropped. The default GNOME version
-        is now 3.12.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        VirtualBox has been upgraded to 4.3.20 release. Users may be
-        required to run <literal>rm -rf /tmp/.vbox*</literal>. The line
-        <literal>imports = [ &lt;nixpkgs/nixos/modules/programs/virtualbox.nix&gt; ]</literal>
-        is no longer necessary, use
-        <literal>services.virtualboxHost.enable = true</literal>
-        instead.
-      </para>
-      <para>
-        Also, hardening mode is now enabled by default, which means that
-        unless you want to use USB support, you no longer need to be a
-        member of the <literal>vboxusers</literal> group.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Chromium has been updated to 39.0.2171.65.
-        <literal>enablePepperPDF</literal> is now enabled by default.
-        <literal>chromium*Wrapper</literal> packages no longer exist,
-        because upstream removed NSAPI support.
-        <literal>chromium-stable</literal> has been renamed to
-        <literal>chromium</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Python packaging documentation is now part of nixpkgs manual. To
-        override the python packages available to a custom python you
-        now use <literal>pkgs.pythonFull.buildEnv.override</literal>
-        instead of <literal>pkgs.pythonFull.override</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>boot.resumeDevice = &quot;8:6&quot;</literal> is no
-        longer supported. Most users will want to leave it undefined,
-        which takes the swap partitions automatically. There is an
-        evaluation assertion to ensure that the string starts with a
-        slash.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The system-wide default timezone for NixOS installations changed
-        from <literal>CET</literal> to <literal>UTC</literal>. To choose
-        a different timezone for your system, configure
-        <literal>time.timeZone</literal> in
-        <literal>configuration.nix</literal>. A fairly complete list of
-        possible values for that setting is available at
-        <link xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">https://en.wikipedia.org/wiki/List_of_tz_database_time_zones</link>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        GNU screen has been updated to 4.2.1, which breaks the ability
-        to connect to sessions created by older versions of screen.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The Intel GPU driver was updated to the 3.x prerelease version
-        (used by most distributions) and supports DRI3 now.
-      </para>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1509.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1509.section.xml
deleted file mode 100644
index 68d2ab389e8f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1509.section.xml
+++ /dev/null
@@ -1,776 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-15.09">
-  <title>Release 15.09 (<quote>Dingo</quote>, 2015/09/30)</title>
-  <para>
-    In addition to numerous new and upgraded packages, this release has
-    the following highlights:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        The <link xlink:href="http://haskell.org/">Haskell</link>
-        packages infrastructure has been re-designed from the ground up
-        (&quot;Haskell NG&quot;). NixOS now distributes the latest
-        version of every single package registered on
-        <link xlink:href="http://hackage.haskell.org/">Hackage</link> --
-        well in excess of 8,000 Haskell packages. Detailed instructions
-        on how to use that infrastructure can be found in the
-        <link xlink:href="https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure">User's
-        Guide to the Haskell Infrastructure</link>. Users migrating from
-        an earlier release may find helpful information below, in the
-        list of backwards-incompatible changes. Furthermore, we
-        distribute 51(!) additional Haskell package sets that provide
-        every single <link xlink:href="http://www.stackage.org/">LTS
-        Haskell</link> release since version 0.0 as well as the most
-        recent <link xlink:href="http://www.stackage.org/">Stackage
-        Nightly</link> snapshot. The announcement
-        <link xlink:href="https://nixos.org/nix-dev/2015-September/018138.html">&quot;Full
-        Stackage Support in Nixpkgs&quot;</link> gives additional
-        details.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Nix has been updated to version 1.10, which among other
-        improvements enables cryptographic signatures on binary caches
-        for improved security.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        You can now keep your NixOS system up to date automatically by
-        setting
-      </para>
-    </listitem>
-  </itemizedlist>
-  <programlisting language="bash">
-{
-  system.autoUpgrade.enable = true;
-}
-</programlisting>
-  <para>
-    This will cause the system to periodically check for updates in your
-    current channel and run <literal>nixos-rebuild</literal>.
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        This release is based on Glibc 2.21, GCC 4.9 and Linux 3.18.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        GNOME has been upgraded to 3.16.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Xfce has been upgraded to 4.12.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        KDE 5 has been upgraded to KDE Frameworks 5.10, Plasma 5.3.2 and
-        Applications 15.04.3. KDE 4 has been updated to kdelibs-4.14.10.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        E19 has been upgraded to 0.16.8.15.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    The following new services were added since the last release:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <literal>services/mail/exim.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/apache-kafka.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/canto-daemon.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/confd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/devmon.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/gitit.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/ihaskell.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/mbpfan.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/mediatomb.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/mwlib.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/parsoid.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/plex.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/ripple-rest.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/ripple-data-api.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/subsonic.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/sundtek.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/cadvisor.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/das_watchdog.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/grafana.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/riemann-tools.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/teamviewer.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/network-filesystems/u9fs.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/aiccu.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/asterisk.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/bird.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/charybdis.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/docker-registry-server.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/fan.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/firefox/sync-server.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/gateone.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/heyefi.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/i2p.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/lambdabot.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/mstpd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/nix-serve.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/nylon.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/racoon.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/skydns.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/shout.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/softether.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/sslh.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/tinc.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/tlsdated.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/tox-bootstrapd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/tvheadend.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/zerotierone.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/scheduling/marathon.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/security/fprintd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/security/hologram.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/security/munge.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/system/cloud-init.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/web-servers/shellinabox.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/web-servers/uwsgi.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/x11/unclutter.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/x11/display-managers/sddm.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/coredump.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/loader/loader.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/loader/generic-extlinux-compatible</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/networkd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/resolved.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/timesyncd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>tasks/filesystems/exfat.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>tasks/filesystems/ntfs.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>tasks/filesystems/vboxsf.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>virtualisation/virtualbox-host.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>virtualisation/vmware-guest.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>virtualisation/xen-dom0.nix</literal>
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    When upgrading from a previous release, please be aware of the
-    following incompatible changes:
-  </para>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        <literal>sshd</literal> no longer supports DSA and ECDSA host
-        keys by default. If you have existing systems with such host
-        keys and want to continue to use them, please set
-      </para>
-    </listitem>
-  </itemizedlist>
-  <programlisting language="bash">
-{
-  system.stateVersion = &quot;14.12&quot;;
-}
-</programlisting>
-  <para>
-    The new option <literal>system.stateVersion</literal> ensures that
-    certain configuration changes that could break existing systems
-    (such as the <literal>sshd</literal> host key setting) will maintain
-    compatibility with the specified NixOS release. NixOps sets the
-    state version of existing deployments automatically.
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <literal>cron</literal> is no longer enabled by default, unless
-        you have a non-empty
-        <literal>services.cron.systemCronJobs</literal>. To force
-        <literal>cron</literal> to be enabled, set
-        <literal>services.cron.enable = true</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Nix now requires binary caches to be cryptographically signed.
-        If you have unsigned binary caches that you want to continue to
-        use, you should set
-        <literal>nix.requireSignedBinaryCaches = false</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Steam now doesn't need root rights to work. Instead of using
-        <literal>*-steam-chrootenv</literal>, you should now just run
-        <literal>steam</literal>. <literal>steamChrootEnv</literal>
-        package was renamed to <literal>steam</literal>, and old
-        <literal>steam</literal> package -- to
-        <literal>steamOriginal</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        CMPlayer has been renamed to bomi upstream. Package
-        <literal>cmplayer</literal> was accordingly renamed to
-        <literal>bomi</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Atom Shell has been renamed to Electron upstream. Package
-        <literal>atom-shell</literal> was accordingly renamed to
-        <literal>electron</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Elm is not released on Hackage anymore. You should now use
-        <literal>elmPackages.elm</literal> which contains the latest Elm
-        platform.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The CUPS printing service has been updated to version
-        <literal>2.0.2</literal>. Furthermore its systemd service has
-        been renamed to <literal>cups.service</literal>.
-      </para>
-      <para>
-        Local printers are no longer shared or advertised by default.
-        This behavior can be changed by enabling
-        <literal>services.printing.defaultShared</literal> or
-        <literal>services.printing.browsing</literal> respectively.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The VirtualBox host and guest options have been named more
-        consistently. They can now found in
-        <literal>virtualisation.virtualbox.host.*</literal> instead of
-        <literal>services.virtualboxHost.*</literal> and
-        <literal>virtualisation.virtualbox.guest.*</literal> instead of
-        <literal>services.virtualboxGuest.*</literal>.
-      </para>
-      <para>
-        Also, there now is support for the <literal>vboxsf</literal>
-        file system using the <literal>fileSystems</literal>
-        configuration attribute. An example of how this can be used in a
-        configuration:
-      </para>
-    </listitem>
-  </itemizedlist>
-  <programlisting language="bash">
-{
-  fileSystems.&quot;/shiny&quot; = {
-    device = &quot;myshinysharedfolder&quot;;
-    fsType = &quot;vboxsf&quot;;
-  };
-}
-</programlisting>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        &quot;<literal>nix-env -qa</literal>&quot; no longer discovers
-        Haskell packages by name. The only packages visible in the
-        global scope are <literal>ghc</literal>,
-        <literal>cabal-install</literal>, and <literal>stack</literal>,
-        but all other packages are hidden. The reason for this
-        inconvenience is the sheer size of the Haskell package set.
-        Name-based lookups are expensive, and most
-        <literal>nix-env -qa</literal> operations would become much
-        slower if we'd add the entire Hackage database into the top
-        level attribute set. Instead, the list of Haskell packages can
-        be displayed by running:
-      </para>
-    </listitem>
-  </itemizedlist>
-  <programlisting>
-nix-env -f &quot;&lt;nixpkgs&gt;&quot; -qaP -A haskellPackages
-</programlisting>
-  <para>
-    Executable programs written in Haskell can be installed with:
-  </para>
-  <programlisting>
-nix-env -f &quot;&lt;nixpkgs&gt;&quot; -iA haskellPackages.pandoc
-</programlisting>
-  <para>
-    Installing Haskell <emphasis>libraries</emphasis> this way, however,
-    is no longer supported. See the next item for more details.
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Previous versions of NixOS came with a feature called
-        <literal>ghc-wrapper</literal>, a small script that allowed GHC
-        to transparently pick up on libraries installed in the user's
-        profile. This feature has been deprecated;
-        <literal>ghc-wrapper</literal> was removed from the
-        distribution. The proper way to register Haskell libraries with
-        the compiler now is the
-        <literal>haskellPackages.ghcWithPackages</literal> function. The
-        <link xlink:href="https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure">User's
-        Guide to the Haskell Infrastructure</link> provides more
-        information about this subject.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        All Haskell builds that have been generated with version 1.x of
-        the <literal>cabal2nix</literal> utility are now invalid and
-        need to be re-generated with a current version of
-        <literal>cabal2nix</literal> to function. The most recent
-        version of this tool can be installed by running
-        <literal>nix-env -i cabal2nix</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>haskellPackages</literal> set in Nixpkgs used to
-        have a function attribute called <literal>extension</literal>
-        that users could override in their
-        <literal>~/.nixpkgs/config.nix</literal> files to configure
-        additional attributes, etc. That function still exists, but it's
-        now called <literal>overrides</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The OpenBLAS library has been updated to version
-        <literal>0.2.14</literal>. Support for the
-        <literal>x86_64-darwin</literal> platform was added. Dynamic
-        architecture detection was enabled; OpenBLAS now selects
-        microarchitecture-optimized routines at runtime, so optimal
-        performance is achieved without the need to rebuild OpenBLAS
-        locally. OpenBLAS has replaced ATLAS in most packages which use
-        an optimized BLAS or LAPACK implementation.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>phpfpm</literal> is now using the default PHP
-        version (<literal>pkgs.php</literal>) instead of PHP 5.4
-        (<literal>pkgs.php54</literal>).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>locate</literal> service no longer indexes the Nix
-        store by default, preventing packages with potentially numerous
-        versions from cluttering the output. Indexing the store can be
-        activated by setting
-        <literal>services.locate.includeStore = true</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The Nix expression search path (<literal>NIX_PATH</literal>) no
-        longer contains <literal>/etc/nixos/nixpkgs</literal> by
-        default. You can override <literal>NIX_PATH</literal> by setting
-        <literal>nix.nixPath</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Python 2.6 has been marked as broken (as it no longer receives
-        security updates from upstream).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Any use of module arguments such as <literal>pkgs</literal> to
-        access library functions, or to define
-        <literal>imports</literal> attributes will now lead to an
-        infinite loop at the time of the evaluation.
-      </para>
-      <para>
-        In case of an infinite loop, use the
-        <literal>--show-trace</literal> command line argument and read
-        the line just above the error message.
-      </para>
-      <programlisting>
-$ nixos-rebuild build --show-trace
-…
-while evaluating the module argument `pkgs' in &quot;/etc/nixos/my-module.nix&quot;:
-infinite recursion encountered
-</programlisting>
-      <para>
-        Any use of <literal>pkgs.lib</literal>, should be replaced by
-        <literal>lib</literal>, after adding it as argument of the
-        module. The following module
-      </para>
-      <programlisting language="bash">
-{ config, pkgs, ... }:
-
-with pkgs.lib;
-
-{
-  options = {
-    foo = mkOption { … };
-  };
-  config = mkIf config.foo { … };
-}
-</programlisting>
-      <para>
-        should be modified to look like:
-      </para>
-      <programlisting language="bash">
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-{
-  options = {
-    foo = mkOption { option declaration };
-  };
-  config = mkIf config.foo { option definition };
-}
-</programlisting>
-      <para>
-        When <literal>pkgs</literal> is used to download other projects
-        to import their modules, and only in such cases, it should be
-        replaced by <literal>(import &lt;nixpkgs&gt; {})</literal>. The
-        following module
-      </para>
-      <programlisting language="bash">
-{ config, pkgs, ... }:
-
-let
-  myProject = pkgs.fetchurl {
-    src = url;
-    sha256 = hash;
-  };
-in
-
-{
-  imports = [ &quot;${myProject}/module.nix&quot; ];
-}
-</programlisting>
-      <para>
-        should be modified to look like:
-      </para>
-      <programlisting language="bash">
-{ config, pkgs, ... }:
-
-let
-  myProject = (import &lt;nixpkgs&gt; {}).fetchurl {
-    src = url;
-    sha256 = hash;
-  };
-in
-
-{
-  imports = [ &quot;${myProject}/module.nix&quot; ];
-}
-</programlisting>
-    </listitem>
-  </itemizedlist>
-  <para>
-    Other notable improvements:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        The nixos and nixpkgs channels were unified, so one
-        <emphasis>can</emphasis> use
-        <literal>nix-env -iA nixos.bash</literal> instead of
-        <literal>nix-env -iA nixos.pkgs.bash</literal>. See
-        <link xlink:href="https://github.com/NixOS/nixpkgs/commit/2cd7c1f198">the
-        commit</link> for details.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Users running an SSH server who worry about the quality of their
-        <literal>/etc/ssh/moduli</literal> file with respect to the
-        <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html">vulnerabilities
-        discovered in the Diffie-Hellman key exchange</link> can now
-        replace OpenSSH's default version with one they generated
-        themselves using the new
-        <literal>services.openssh.moduliFile</literal> option.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        A newly packaged TeX Live 2015 is provided in
-        <literal>pkgs.texlive</literal>, split into 6500 nix packages.
-        For basic user documentation see
-        <link xlink:href="https://github.com/NixOS/nixpkgs/blob/release-15.09/pkgs/tools/typesetting/tex/texlive/default.nix#L1">the
-        source</link>. Beware of
-        <link xlink:href="https://github.com/NixOS/nixpkgs/issues/9757">an
-        issue</link> when installing a too large package set. The plan
-        is to deprecate and maybe delete the original TeX packages until
-        the next release.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>buildEnv.env</literal> on all Python interpreters is
-        now available for nix-shell interoperability.
-      </para>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml
deleted file mode 100644
index 172b800b5992..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml
+++ /dev/null
@@ -1,695 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-16.03">
-  <title>Release 16.03 (<quote>Emu</quote>, 2016/03/31)</title>
-  <para>
-    In addition to numerous new and upgraded packages, this release has
-    the following highlights:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Systemd 229, bringing
-        <link xlink:href="https://github.com/systemd/systemd/blob/v229/NEWS">numerous
-        improvements</link> over 217.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Linux 4.4 (was 3.18).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        GCC 5.3 (was 4.9). Note that GCC 5
-        <link xlink:href="https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html">changes
-        the C++ ABI in an incompatible way</link>; this may cause
-        problems if you try to link objects compiled with different
-        versions of GCC.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Glibc 2.23 (was 2.21).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Binutils 2.26 (was 2.23.1). See #909
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Improved support for ensuring
-        <link xlink:href="https://reproducible-builds.org/">bitwise
-        reproducible builds</link>. For example,
-        <literal>stdenv</literal> now sets the environment variable
-        <literal>SOURCE_DATE_EPOCH</literal> to a deterministic value,
-        and Nix has
-        <link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-1.11">gained
-        an option</link> to repeat a build a number of times to test
-        determinism. An ongoing project, the goal of exact
-        reproducibility is to allow binaries to be verified
-        independently (e.g., a user might only trust binaries that
-        appear in three independent binary caches).
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Perl 5.22.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    The following new services were added since the last release:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <literal>services/monitoring/longview.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>hardware/video/webcam/facetimehd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>i18n/input-method/default.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>i18n/input-method/fcitx.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>i18n/input-method/ibus.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>i18n/input-method/nabi.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>i18n/input-method/uim.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>programs/fish.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>security/acme.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>security/audit.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>security/oath.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/hardware/irqbalance.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/mail/dspam.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/mail/opendkim.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/mail/postsrsd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/mail/rspamd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/mail/rmilter.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/autofs.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/bepasty.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/calibre-server.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/cfdyndns.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/gammu-smsd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/mathics.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/matrix-synapse.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/misc/octoprint.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/hdaps.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/heapster.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/monitoring/longview.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/network-filesystems/netatalk.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/network-filesystems/xtreemfs.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/autossh.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/dnschain.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/gale.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/miniupnpd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/namecoind.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/ostinato.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/pdnsd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/shairport-sync.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/networking/supplicant.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/search/kibana.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/security/haka.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/security/physlock.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/web-apps/pump.io.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/x11/hardware/libinput.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services/x11/window-managers/windowlab.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/initrd-network.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/initrd-ssh.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/loader/loader.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/networkd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>system/boot/resolved.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>virtualisation/lxd.nix</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>virtualisation/rkt.nix</literal>
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    When upgrading from a previous release, please be aware of the
-    following incompatible changes:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        We no longer produce graphical ISO images and VirtualBox images
-        for <literal>i686-linux</literal>. A minimal ISO image is still
-        provided.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Firefox and similar browsers are now <emphasis>wrapped by
-        default</emphasis>. The package and attribute names are plain
-        <literal>firefox</literal> or <literal>midori</literal>, etc.
-        Backward-compatibility attributes were set up, but note that
-        <literal>nix-env -u</literal> will <emphasis>not</emphasis>
-        update your current <literal>firefox-with-plugins</literal>; you
-        have to uninstall it and install <literal>firefox</literal>
-        instead.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>wmiiSnap</literal> has been replaced with
-        <literal>wmii_hg</literal>, but
-        <literal>services.xserver.windowManager.wmii.enable</literal>
-        has been updated respectively so this only affects you if you
-        have explicitly installed <literal>wmiiSnap</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>jobs</literal> NixOS option has been removed. It served
-        as compatibility layer between Upstart jobs and SystemD
-        services. All services have been rewritten to use
-        <literal>systemd.services</literal>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>wmiimenu</literal> is removed, as it has been removed
-        by the developers upstream. Use <literal>wimenu</literal> from
-        the <literal>wmii-hg</literal> package.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Gitit is no longer automatically added to the module list in
-        NixOS and as such there will not be any manual entries for it.
-        You will need to add an import statement to your NixOS
-        configuration in order to use it, e.g.
-      </para>
-      <programlisting language="bash">
-{
-  imports = [ &lt;nixpkgs/nixos/modules/services/misc/gitit.nix&gt; ];
-}
-</programlisting>
-      <para>
-        will include the Gitit service configuration options.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>nginx</literal> does not accept flags for enabling and
-        disabling modules anymore. Instead it accepts
-        <literal>modules</literal> argument, which is a list of modules
-        to be built in. All modules now reside in
-        <literal>nginxModules</literal> set. Example configuration:
-      </para>
-      <programlisting language="bash">
-nginx.override {
-  modules = [ nginxModules.rtmp nginxModules.dav nginxModules.moreheaders ];
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>s3sync</literal> is removed, as it hasn't been
-        developed by upstream for 4 years and only runs with ruby 1.8.
-        For an actively-developer alternative look at
-        <literal>tarsnap</literal> and others.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>ruby_1_8</literal> has been removed as it's not
-        supported from upstream anymore and probably contains security
-        issues.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>tidy-html5</literal> package is removed. Upstream only
-        provided <literal>(lib)tidy5</literal> during development, and
-        now they went back to <literal>(lib)tidy</literal> to work as a
-        drop-in replacement of the original package that has been
-        unmaintained for years. You can (still) use the
-        <literal>html-tidy</literal> package, which got updated to a
-        stable release from this new upstream.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>extraDeviceOptions</literal> argument is removed from
-        <literal>bumblebee</literal> package. Instead there are now two
-        separate arguments: <literal>extraNvidiaDeviceOptions</literal>
-        and <literal>extraNouveauDeviceOptions</literal> for setting
-        extra X11 options for nvidia and nouveau drivers, respectively.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>Ctrl+Alt+Backspace</literal> key combination no
-        longer kills the X server by default. There's a new option
-        <literal>services.xserver.enableCtrlAltBackspace</literal>
-        allowing to enable the combination again.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>emacsPackagesNg</literal> now contains all packages
-        from the ELPA, MELPA, and MELPA Stable repositories.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Data directory for Postfix MTA server is moved from
-        <literal>/var/postfix</literal> to
-        <literal>/var/lib/postfix</literal>. Old configurations are
-        migrated automatically. <literal>service.postfix</literal>
-        module has also received many improvements, such as correct
-        directories' access rights, new <literal>aliasFiles</literal>
-        and <literal>mapFiles</literal> options and more.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Filesystem options should now be configured as a list of
-        strings, not a comma-separated string. The old style will
-        continue to work, but print a warning, until the 16.09 release.
-        An example of the new style:
-      </para>
-      <programlisting language="bash">
-{
-  fileSystems.&quot;/example&quot; = {
-    device = &quot;/dev/sdc&quot;;
-    fsType = &quot;btrfs&quot;;
-    options = [ &quot;noatime&quot; &quot;compress=lzo&quot; &quot;space_cache&quot; &quot;autodefrag&quot; ];
-  };
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        CUPS, installed by <literal>services.printing</literal> module,
-        now has its data directory in <literal>/var/lib/cups</literal>.
-        Old configurations from <literal>/etc/cups</literal> are moved
-        there automatically, but there might be problems. Also
-        configuration options
-        <literal>services.printing.cupsdConf</literal> and
-        <literal>services.printing.cupsdFilesConf</literal> were removed
-        because they had been allowing one to override configuration
-        variables required for CUPS to work at all on NixOS. For most
-        use cases, <literal>services.printing.extraConf</literal> and
-        new option <literal>services.printing.extraFilesConf</literal>
-        should be enough; if you encounter a situation when they are
-        not, please file a bug.
-      </para>
-      <para>
-        There are also Gutenprint improvements; in particular, a new
-        option <literal>services.printing.gutenprint</literal> is added
-        to enable automatic updating of Gutenprint PPMs; it's greatly
-        recommended to enable it instead of adding
-        <literal>gutenprint</literal> to the <literal>drivers</literal>
-        list.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services.xserver.vaapiDrivers</literal> has been
-        removed. Use
-        <literal>hardware.opengl.extraPackages{,32}</literal> instead.
-        You can also specify VDPAU drivers there.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>programs.ibus</literal> moved to
-        <literal>i18n.inputMethod.ibus</literal>. The option
-        <literal>programs.ibus.plugins</literal> changed to
-        <literal>i18n.inputMethod.ibus.engines</literal> and the option
-        to enable ibus changed from
-        <literal>programs.ibus.enable</literal> to
-        <literal>i18n.inputMethod.enabled</literal>.
-        <literal>i18n.inputMethod.enabled</literal> should be set to the
-        used input method name, <literal>&quot;ibus&quot;</literal> for
-        ibus. An example of the new style:
-      </para>
-      <programlisting language="bash">
-{
-  i18n.inputMethod.enabled = &quot;ibus&quot;;
-  i18n.inputMethod.ibus.engines = with pkgs.ibus-engines; [ anthy mozc ];
-}
-</programlisting>
-      <para>
-        That is equivalent to the old version:
-      </para>
-      <programlisting language="bash">
-{
-  programs.ibus.enable = true;
-  programs.ibus.plugins = with pkgs; [ ibus-anthy mozc ];
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services.udev.extraRules</literal> option now writes
-        rules to <literal>99-local.rules</literal> instead of
-        <literal>10-local.rules</literal>. This makes all the user rules
-        apply after others, so their results wouldn't be overriden by
-        anything else.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Large parts of the <literal>services.gitlab</literal> module has
-        been been rewritten. There are new configuration options
-        available. The <literal>stateDir</literal> option was renamned
-        to <literal>statePath</literal> and the
-        <literal>satellitesDir</literal> option was removed. Please
-        review the currently available options.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The option
-        <literal>services.nsd.zones.&lt;name&gt;.data</literal> no
-        longer interpret the dollar sign ($) as a shell variable, as
-        such it should not be escaped anymore. Thus the following zone
-        data:
-      </para>
-      <programlisting>
-$ORIGIN example.com.
-$TTL 1800
-@       IN      SOA     ns1.vpn.nbp.name.      admin.example.com. (
-</programlisting>
-      <para>
-        Should modified to look like the actual file expected by nsd:
-      </para>
-      <programlisting>
-$ORIGIN example.com.
-$TTL 1800
-@       IN      SOA     ns1.vpn.nbp.name.      admin.example.com. (
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>service.syncthing.dataDir</literal> options now has to
-        point to exact folder where syncthing is writing to. Example
-        configuration should look something like:
-      </para>
-      <programlisting language="bash">
-{
-  services.syncthing = {
-      enable = true;
-      dataDir = &quot;/home/somebody/.syncthing&quot;;
-      user = &quot;somebody&quot;;
-  };
-}
-</programlisting>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>networking.firewall.allowPing</literal> is now enabled
-        by default. Users are encouraged to configure an appropriate
-        rate limit for their machines using the Kernel interface at
-        <literal>/proc/sys/net/ipv4/icmp_ratelimit</literal> and
-        <literal>/proc/sys/net/ipv6/icmp/ratelimit</literal> or using
-        the firewall itself, i.e. by setting the NixOS option
-        <literal>networking.firewall.pingLimit</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Systems with some broadcom cards used to result into a generated
-        config that is no longer accepted. If you get errors like
-      </para>
-      <programlisting>
-error: path ‘/nix/store/*-broadcom-sta-*’ does not exist and cannot be created
-</programlisting>
-      <para>
-        you should either re-run
-        <literal>nixos-generate-config</literal> or manually replace
-        <literal>&quot;${config.boot.kernelPackages.broadcom_sta}&quot;</literal>
-        by <literal>config.boot.kernelPackages.broadcom_sta</literal> in
-        your <literal>/etc/nixos/hardware-configuration.nix</literal>.
-        More discussion is on
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/12595">
-        the github issue</link>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>services.xserver.startGnuPGAgent</literal> option
-        has been removed. GnuPG 2.1.x changed the way the gpg-agent
-        works, and that new approach no longer requires (or even
-        supports) the &quot;start everything as a child of the
-        agent&quot; scheme we've implemented in NixOS for older
-        versions. To configure the gpg-agent for your X session, add the
-        following code to <literal>~/.bashrc</literal> or some file
-        that’s sourced when your shell is started:
-      </para>
-      <programlisting>
-GPG_TTY=$(tty)
-export GPG_TTY
-</programlisting>
-      <para>
-        If you want to use gpg-agent for SSH, too, add the following to
-        your session initialization (e.g.
-        <literal>displayManager.sessionCommands</literal>)
-      </para>
-      <programlisting>
-    gpg-connect-agent /bye
-    unset SSH_AGENT_PID
-    export SSH_AUTH_SOCK=&quot;''${HOME}/.gnupg/S.gpg-agent.ssh&quot;
-</programlisting>
-      <para>
-        and make sure that
-      </para>
-      <programlisting>
-    enable-ssh-support
-</programlisting>
-      <para>
-        is included in your <literal>~/.gnupg/gpg-agent.conf</literal>.
-        You will need to use <literal>ssh-add</literal> to re-add your
-        ssh keys. If gpg’s automatic transformation of the private keys
-        to the new format fails, you will need to re-import your private
-        keyring as well:
-      </para>
-      <programlisting>
-    gpg --import ~/.gnupg/secring.gpg
-</programlisting>
-      <para>
-        The <literal>gpg-agent(1)</literal> man page has more details
-        about this subject, i.e. in the &quot;EXAMPLES&quot; section.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    Other notable improvements:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        <literal>ejabberd</literal> module is brought back and now works
-        on NixOS.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Input method support was improved. New NixOS modules (fcitx,
-        nabi and uim), fcitx engines (chewing, hangul, m17n, mozc and
-        table-other) and ibus engines (hangul and m17n) have been added.
-      </para>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1609.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1609.section.xml
deleted file mode 100644
index 0fba40a0e78d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1609.section.xml
+++ /dev/null
@@ -1,273 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-16.09">
-  <title>Release 16.09 (<quote>Flounder</quote>, 2016/09/30)</title>
-  <para>
-    In addition to numerous new and upgraded packages, this release has
-    the following highlights:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Many NixOS configurations and Nix packages now use significantly
-        less disk space, thanks to the
-        <link xlink:href="https://github.com/NixOS/nixpkgs/issues/7117">extensive
-        work on closure size reduction</link>. For example, the closure
-        size of a minimal NixOS container went down from ~424 MiB in
-        16.03 to ~212 MiB in 16.09, while the closure size of Firefox
-        went from ~651 MiB to ~259 MiB.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        To improve security, packages are now
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/12895">built
-        using various hardening features</link>. See the Nixpkgs manual
-        for more information.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Support for PXE netboot. See
-        <xref linkend="sec-booting-from-pxe" /> for documentation.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        X.org server 1.18. If you use the <literal>ati_unfree</literal>
-        driver, 1.17 is still used due to an ABI incompatibility.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        This release is based on Glibc 2.24, GCC 5.4.0 and systemd 231.
-        The default Linux kernel remains 4.4.
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    The following new services were added since the last release:
-  </para>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        <literal>(this will get automatically generated at release time)</literal>
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    When upgrading from a previous release, please be aware of the
-    following incompatible changes:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        A large number of packages have been converted to use the
-        multiple outputs feature of Nix to greatly reduce the amount of
-        required disk space, as mentioned above. This may require
-        changes to any custom packages to make them build again; see the
-        relevant chapter in the Nixpkgs manual for more information.
-        (Additional caveat to packagers: some packaging conventions
-        related to multiple-output packages
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/14766">were
-        changed</link> late (August 2016) in the release cycle and
-        differ from the initial introduction of multiple outputs.)
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Previous versions of Nixpkgs had support for all versions of the
-        LTS Haskell package set. That support has been dropped. The
-        previously provided <literal>haskell.packages.lts-x_y</literal>
-        package sets still exist in name to aviod breaking user code,
-        but these package sets don't actually contain the versions
-        mandated by the corresponding LTS release. Instead, our package
-        set it loosely based on the latest available LTS release, i.e.
-        LTS 7.x at the time of this writing. New releases of NixOS and
-        Nixpkgs will drop those old names entirely.
-        <link xlink:href="https://nixos.org/nix-dev/2016-June/020585.html">The
-        motivation for this change</link> has been discussed at length
-        on the <literal>nix-dev</literal> mailing list and in
-        <link xlink:href="https://github.com/NixOS/nixpkgs/issues/14897">Github
-        issue #14897</link>. Development strategies for Haskell hackers
-        who want to rely on Nix and NixOS have been described in
-        <link xlink:href="https://nixos.org/nix-dev/2016-June/020642.html">another
-        nix-dev article</link>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Shell aliases for systemd sub-commands
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/15598">were
-        dropped</link>: <literal>start</literal>,
-        <literal>stop</literal>, <literal>restart</literal>,
-        <literal>status</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Redis now binds to 127.0.0.1 only instead of listening to all
-        network interfaces. This is the default behavior of Redis 3.2
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>/var/empty</literal> is now immutable. Activation
-        script runs <literal>chattr +i</literal> to forbid any
-        modifications inside the folder. See
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/18365">
-        the pull request</link> for what bugs this caused.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Gitlab's maintainance script <literal>gitlab-runner</literal>
-        was removed and split up into the more clearer
-        <literal>gitlab-run</literal> and <literal>gitlab-rake</literal>
-        scripts, because <literal>gitlab-runner</literal> is a component
-        of Gitlab CI.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services.xserver.libinput.accelProfile</literal>
-        default changed from <literal>flat</literal> to
-        <literal>adaptive</literal>, as per
-        <link xlink:href="https://wayland.freedesktop.org/libinput/doc/latest/group__config.html#gad63796972347f318b180e322e35cee79">
-        official documentation</link>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>fonts.fontconfig.ultimate.rendering</literal> was
-        removed because our presets were obsolete for some time. New
-        presets are hardcoded into FreeType; you can select a preset via
-        <literal>fonts.fontconfig.ultimate.preset</literal>. You can
-        customize those presets via ordinary environment variables,
-        using <literal>environment.variables</literal>.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The <literal>audit</literal> service is no longer enabled by
-        default. Use <literal>security.audit.enable = true</literal> to
-        explicitly enable it.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>pkgs.linuxPackages.virtualbox</literal> now contains
-        only the kernel modules instead of the VirtualBox user space
-        binaries. If you want to reference the user space binaries, you
-        have to use the new <literal>pkgs.virtualbox</literal> instead.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>goPackages</literal> was replaced with separated Go
-        applications in appropriate <literal>nixpkgs</literal>
-        categories. Each Go package uses its own dependency set. There's
-        also a new <literal>go2nix</literal> tool introduced to generate
-        a Go package definition from its Go source automatically.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services.mongodb.extraConfig</literal> configuration
-        format was changed to YAML.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        PHP has been upgraded to 7.0
-      </para>
-    </listitem>
-  </itemizedlist>
-  <para>
-    Other notable improvements:
-  </para>
-  <itemizedlist>
-    <listitem>
-      <para>
-        Revamped grsecurity/PaX support. There is now only a single
-        general-purpose distribution kernel and the configuration
-        interface has been streamlined. Desktop users should be able to
-        simply set
-      </para>
-      <programlisting language="bash">
-{
-  security.grsecurity.enable = true;
-}
-</programlisting>
-      <para>
-        to get a reasonably secure system without having to sacrifice
-        too much functionality.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Special filesystems, like <literal>/proc</literal>,
-        <literal>/run</literal> and others, now have the same mount
-        options as recommended by systemd and are unified across
-        different places in NixOS. Mount options are updated during
-        <literal>nixos-rebuild switch</literal> if possible. One benefit
-        from this is improved security — most such filesystems are now
-        mounted with <literal>noexec</literal>, <literal>nodev</literal>
-        and/or <literal>nosuid</literal> options.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The reverse path filter was interfering with DHCPv4 server
-        operation in the past. An exception for DHCPv4 and a new option
-        to log packets that were dropped due to the reverse path filter
-        was added
-        (<literal>networking.firewall.logReversePathDrops</literal>) for
-        easier debugging.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Containers configuration within
-        <literal>containers.&lt;name&gt;.config</literal> is
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/17365">now
-        properly typed and checked</link>. In particular, partial
-        configurations are merged correctly.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        The directory container setuid wrapper programs,
-        <literal>/var/setuid-wrappers</literal>,
-        <link xlink:href="https://github.com/NixOS/nixpkgs/pull/18124">is
-        now updated atomically to prevent failures if the switch to a
-        new configuration is interrupted.</link>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <literal>services.xserver.startGnuPGAgent</literal> has been
-        removed due to GnuPG 2.1.x bump. See
-        <link xlink:href="https://github.com/NixOS/nixpkgs/commit/5391882ebd781149e213e8817fba6ac3c503740c">
-        how to achieve similar behavior</link>. You might need to
-        <literal>pkill gpg-agent</literal> after the upgrade to prevent
-        a stale agent being in the way.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        <link xlink:href="https://github.com/NixOS/nixpkgs/commit/e561edc322d275c3687fec431935095cfc717147">
-        Declarative users could share the uid due to the bug in the
-        script handling conflict resolution. </link>
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Gummi boot has been replaced using systemd-boot.
-      </para>
-    </listitem>
-    <listitem>
-      <para>
-        Hydra package and NixOS module were added for convenience.
-      </para>
-    </listitem>
-  </itemizedlist>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1703.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1703.section.xml
deleted file mode 100644
index 1119ec53dfc9..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1703.section.xml
+++ /dev/null
@@ -1,818 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-17.03">
-  <title>Release 17.03 (<quote>Gorilla</quote>, 2017/03/31)</title>
-  <section xml:id="sec-release-17.03-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Nixpkgs is now extensible through overlays. See the
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-overlays-install">Nixpkgs
-          manual</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          This release is based on Glibc 2.25, GCC 5.4.0 and systemd
-          232. The default Linux kernel is 4.9 and Nix is at 1.11.8.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default desktop environment now is KDE's Plasma 5. KDE 4
-          has been removed
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The setuid wrapper functionality now supports setting
-          capabilities.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          X.org server uses branch 1.19. Due to ABI incompatibilities,
-          <literal>ati_unfree</literal> keeps forcing 1.17 and
-          <literal>amdgpu-pro</literal> starts forcing 1.18.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Cross compilation has been rewritten. See the nixpkgs manual
-          for details. The most obvious breaking change is that in
-          derivations there is no <literal>.nativeDrv</literal> nor
-          <literal>.crossDrv</literal> are now cross by default, not
-          native.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>overridePackages</literal> function has been
-          rewritten to be replaced by
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-overlays-install">
-          overlays</link>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Packages in nixpkgs can be marked as insecure through listed
-          vulnerabilities. See the
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-allow-insecure">Nixpkgs
-          manual</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP now defaults to PHP 7.1
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-17.03-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>hardware/ckb.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware/mcelog.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware/usb-wwan.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware/video/capture/mwprocapture.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/adb.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/chromium.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/gphoto2.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/java.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/mtr.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/oblogout.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/vim.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/wireshark.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security/dhparams.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/audio/ympd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/computing/boinc/client.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/continuous-integration/buildbot/master.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/continuous-integration/buildbot/worker.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/continuous-integration/gitlab-runner.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/databases/riak-cs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/databases/stanchion.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/desktops/gnome3/gnome-terminal-server.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/editors/infinoted.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/hardware/illum.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/hardware/trezord.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/logging/journalbeat.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/mail/offlineimap.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/mail/postgrey.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/couchpotato.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/docker-registry.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/errbot.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/geoip-updater.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/gogs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/leaps.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/nix-optimise.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/ssm-agent.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/sssd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/arbtt.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/netdata.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/alertmanager.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/blackbox-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/json-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/nginx-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/node-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/snmp-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/unifi-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/varnish-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/sysstat.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/telegraf.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/vnstat.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/network-filesystems/cachefilesd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/network-filesystems/glusterfs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/network-filesystems/ipfs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/dante.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/dnscrypt-wrapper.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/fakeroute.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/flannel.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/htpdate.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/miredo.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/nftables.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/powerdns.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/pdns-recursor.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/quagga.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/redsocks.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/wireguard.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/system/cgmanager.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/torrent/opentracker.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/atlassian/confluence.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/atlassian/crowd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/atlassian/jira.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/frab.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/nixbot.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/selfoss.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/quassel-webserver.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/x11/unclutter-xfixes.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/x11/urxvtd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>system/boot/systemd-nspawn.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>virtualisation/ecs-agent.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>virtualisation/lxcfs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>virtualisation/openstack/keystone.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>virtualisation/openstack/glance.nix</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-17.03-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Derivations have no <literal>.nativeDrv</literal> nor
-          <literal>.crossDrv</literal> and are now cross by default, not
-          native.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>stdenv.overrides</literal> is now expected to take
-          <literal>self</literal> and <literal>super</literal>
-          arguments. See <literal>lib.trivial.extends</literal> for what
-          those parameters represent.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>ansible</literal> now defaults to ansible version 2
-          as version 1 has been removed due to a serious
-          <link xlink:href="https://www.computest.nl/advisories/CT-2017-0109_Ansible.txt">
-          vulnerability</link> unpatched by upstream.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>gnome</literal> alias has been removed along with
-          <literal>gtk</literal>, <literal>gtkmm</literal> and several
-          others. Now you need to use versioned attributes, like
-          <literal>gnome3</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The attribute name of the Radicale daemon has been changed
-          from <literal>pythonPackages.radicale</literal> to
-          <literal>radicale</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>stripHash</literal> bash function in
-          <literal>stdenv</literal> changed according to its
-          documentation; it now outputs the stripped name to
-          <literal>stdout</literal> instead of putting it in the
-          variable <literal>strippedName</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP now scans for extra configuration .ini files in /etc/php.d
-          instead of /etc. This prevents accidentally loading non-PHP
-          .ini files that may be in /etc.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Two lone top-level dict dbs moved into
-          <literal>dictdDBs</literal>. This affects:
-          <literal>dictdWordnet</literal> which is now at
-          <literal>dictdDBs.wordnet</literal> and
-          <literal>dictdWiktionary</literal> which is now at
-          <literal>dictdDBs.wiktionary</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Parsoid service now uses YAML configuration format.
-          <literal>service.parsoid.interwikis</literal> is now called
-          <literal>service.parsoid.wikis</literal> and is a list of
-          either API URLs or attribute sets as specified in parsoid's
-          documentation.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>Ntpd</literal> was replaced by
-          <literal>systemd-timesyncd</literal> as the default service to
-          synchronize system time with a remote NTP server. The old
-          behavior can be restored by setting
-          <literal>services.ntp.enable</literal> to
-          <literal>true</literal>. Upstream time servers for all NTP
-          implementations are now configured using
-          <literal>networking.timeServers</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>service.nylon</literal> is now declared using named
-          instances. As an example:
-        </para>
-        <programlisting language="bash">
-{
-  services.nylon = {
-    enable = true;
-    acceptInterface = &quot;br0&quot;;
-    bindInterface = &quot;tun1&quot;;
-    port = 5912;
-  };
-}
-</programlisting>
-        <para>
-          should be replaced with:
-        </para>
-        <programlisting language="bash">
-{
-  services.nylon.myvpn = {
-    enable = true;
-    acceptInterface = &quot;br0&quot;;
-    bindInterface = &quot;tun1&quot;;
-    port = 5912;
-  };
-}
-</programlisting>
-        <para>
-          this enables you to declare a SOCKS proxy for each uplink.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>overridePackages</literal> function no longer exists.
-          It is replaced by
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-overlays-install">
-          overlays</link>. For example, the following code:
-        </para>
-        <programlisting language="bash">
-let
-  pkgs = import &lt;nixpkgs&gt; {};
-in
-  pkgs.overridePackages (self: super: ...)
-</programlisting>
-        <para>
-          should be replaced by:
-        </para>
-        <programlisting language="bash">
-let
-  pkgs = import &lt;nixpkgs&gt; {};
-in
-  import pkgs.path { overlays = [(self: super: ...)]; }
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          Autoloading connection tracking helpers is now disabled by
-          default. This default was also changed in the Linux kernel and
-          is considered insecure if not configured properly in your
-          firewall. If you need connection tracking helpers (i.e. for
-          active FTP) please enable
-          <literal>networking.firewall.autoLoadConntrackHelpers</literal>
-          and tune
-          <literal>networking.firewall.connectionTrackingModules</literal>
-          to suit your needs.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>local_recipient_maps</literal> is not set to empty
-          value by Postfix service. It's an insecure default as stated
-          by Postfix documentation. Those who want to retain this
-          setting need to set it via
-          <literal>services.postfix.extraConfig</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Iputils no longer provide ping6 and traceroute6. The
-          functionality of these tools has been integrated into ping and
-          traceroute respectively. To enforce an address family the new
-          flags <literal>-4</literal> and <literal>-6</literal> have
-          been added. One notable incompatibility is that specifying an
-          interface (for link-local IPv6 for instance) is no longer done
-          with the <literal>-I</literal> flag, but by encoding the
-          interface into the address
-          (<literal>ping fe80::1%eth0</literal>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The socket handling of the <literal>services.rmilter</literal>
-          module has been fixed and refactored. As rmilter doesn't
-          support binding to more than one socket, the options
-          <literal>bindUnixSockets</literal> and
-          <literal>bindInetSockets</literal> have been replaced by
-          <literal>services.rmilter.bindSocket.*</literal>. The default
-          is still a unix socket in
-          <literal>/run/rmilter/rmilter.sock</literal>. Refer to the
-          options documentation for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>fetch*</literal> functions no longer support md5,
-          please use sha256 instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The dnscrypt-proxy module interface has been streamlined
-          around the <literal>extraArgs</literal> option. Where
-          possible, legacy option declarations are mapped to
-          <literal>extraArgs</literal> but will emit warnings. The
-          <literal>resolverList</literal> has been outright removed: to
-          use an unlisted resolver, use the
-          <literal>customResolver</literal> option.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          torbrowser now stores local state under
-          <literal>~/.local/share/tor-browser</literal> by default. Any
-          browser profile data from the old location,
-          <literal>~/.torbrowser4</literal>, must be migrated manually.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The ihaskell, monetdb, offlineimap and sitecopy services have
-          been removed.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-17.03-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Module type system have a new extensible option types feature
-          that allow to extend certain types, such as enum, through
-          multiple option declarations of the same option across
-          multiple modules.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>jre</literal> now defaults to GTK UI by default. This
-          improves visual consistency and makes Java follow system font
-          style, improving the situation on HighDPI displays. This has a
-          cost of increased closure size; for server and other headless
-          workloads it's recommended to use
-          <literal>jre_headless</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Python 2.6 interpreter and package set have been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Python 2.7 interpreter does not use modules anymore.
-          Instead, all CPython interpreters now include the whole
-          standard library except for `tkinter`, which is available in
-          the Python package set.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Python 2.7, 3.5 and 3.6 are now built deterministically and
-          3.4 mostly. Minor modifications had to be made to the
-          interpreters in order to generate deterministic bytecode. This
-          has security implications and is relevant for those using
-          Python in a <literal>nix-shell</literal>. See the Nixpkgs
-          manual for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Python package sets now use a fixed-point combinator and
-          the sets are available as attributes of the interpreters.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Python function <literal>buildPythonPackage</literal> has
-          been improved and can be used to build from Setuptools source,
-          Flit source, and precompiled Wheels.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When adding new or updating current Python libraries, the
-          expressions should be put in separate files in
-          <literal>pkgs/development/python-modules</literal> and called
-          from <literal>python-packages.nix</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The dnscrypt-proxy service supports synchronizing the list of
-          public resolvers without working DNS resolution. This fixes
-          issues caused by the resolver list becoming outdated. It also
-          improves the viability of DNSCrypt only configurations.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Containers using bridged networking no longer lose their
-          connection after changes to the host networking.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          ZFS supports pool auto scrubbing.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The bind DNS utilities (e.g. dig) have been split into their
-          own output and are now also available in
-          <literal>pkgs.dnsutils</literal> and it is no longer necessary
-          to pull in all of <literal>bind</literal> to use them.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Per-user configuration was moved from
-          <literal>~/.nixpkgs</literal> to
-          <literal>~/.config/nixpkgs</literal>. The former is still
-          valid for <literal>config.nix</literal> for backwards
-          compatibility.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml
deleted file mode 100644
index 8f0efe816e51..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml
+++ /dev/null
@@ -1,922 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-17.09">
-  <title>Release 17.09 (<quote>Hummingbird</quote>, 2017/09/??)</title>
-  <section xml:id="sec-release-17.09-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The GNOME version is now 3.24. KDE Plasma was upgraded to
-          5.10, KDE Applications to 17.08.1 and KDE Frameworks to 5.37.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The user handling now keeps track of deallocated UIDs/GIDs.
-          When a user or group is revived, this allows it to be
-          allocated the UID/GID it had before. A consequence is that
-          UIDs and GIDs are no longer reused.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The module option
-          <literal>services.xserver.xrandrHeads</literal> now causes the
-          first head specified in this list to be set as the primary
-          head. Apart from that, it's now possible to also set
-          additional options by using an attribute set, for example:
-        </para>
-        <programlisting language="bash">
-{ services.xserver.xrandrHeads = [
-    &quot;HDMI-0&quot;
-    {
-      output = &quot;DVI-0&quot;;
-      primary = true;
-      monitorConfig = ''
-        Option &quot;Rotate&quot; &quot;right&quot;
-      '';
-    }
-  ];
-}
-</programlisting>
-        <para>
-          This will set the <literal>DVI-0</literal> output to be the
-          primary head, even though <literal>HDMI-0</literal> is the
-          first head in the list.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The handling of SSL in the <literal>services.nginx</literal>
-          module has been cleaned up, renaming the misnamed
-          <literal>enableSSL</literal> to <literal>onlySSL</literal>
-          which reflects its original intention. This is not to be used
-          with the already existing <literal>forceSSL</literal> which
-          creates a second non-SSL virtual host redirecting to the SSL
-          virtual host. This by chance had worked earlier due to
-          specific implementation details. In case you had specified
-          both please remove the <literal>enableSSL</literal> option to
-          keep the previous behaviour.
-        </para>
-        <para>
-          Another <literal>addSSL</literal> option has been introduced
-          to configure both a non-SSL virtual host and an SSL virtual
-          host with the same configuration.
-        </para>
-        <para>
-          Options to configure <literal>resolver</literal> options and
-          <literal>upstream</literal> blocks have been introduced. See
-          their information for further details.
-        </para>
-        <para>
-          The <literal>port</literal> option has been replaced by a more
-          generic <literal>listen</literal> option which makes it
-          possible to specify multiple addresses, ports and SSL configs
-          dependant on the new SSL handling mentioned above.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-17.09-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>config/fonts/fontconfig-penultimate.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>config/fonts/fontconfig-ultimate.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>config/terminfo.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware/sensor/iio.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware/nitrokey.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware/raid/hpsa.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/browserpass.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/gnupg.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/qt5ct.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/slock.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs/thefuck.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security/auditd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security/lock-kernel-modules.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>service-managers/docker.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>service-managers/trivial.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/admin/salt/master.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/admin/salt/minion.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/audio/slimserver.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/cluster/kubernetes/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/cluster/kubernetes/dns.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/cluster/kubernetes/dashboard.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/continuous-integration/hail.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/databases/clickhouse.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/databases/postage.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/desktops/gnome3/gnome-disks.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/desktops/gnome3/gpaste.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/logging/SystemdJournal2Gelf.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/logging/heartbeat.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/logging/journalwatch.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/logging/syslogd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/mail/mailhog.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/mail/nullmailer.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/airsonic.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/autorandr.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/exhibitor.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/fstrim.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/gollum.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/irkerd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/jackett.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/radarr.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/misc/snapper.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/osquery.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/collectd-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/monitoring/prometheus/fritzbox-exporter.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/network-filesystems/kbfs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/dnscache.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/fireqos.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/iwd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/keepalived/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/keybase.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/lldpd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/matterbridge.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/squid.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/tinydns.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/networking/xrdp.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/security/shibboleth-sp.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/security/sks.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/security/sshguard.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/security/torify.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/security/usbguard.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/security/vault.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/system/earlyoom.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/system/saslauthd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/nexus.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/pgpkeyserver-lite.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-apps/piwik.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-servers/lighttpd/collectd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/web-servers/minio.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/x11/display-managers/xpra.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services/x11/xautolock.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>tasks/filesystems/bcachefs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>tasks/powertop.nix</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-17.09-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <emphasis role="strong">In an Qemu-based virtualization
-          environment, the network interface names changed from i.e.
-          <literal>enp0s3</literal> to
-          <literal>ens3</literal>.</emphasis>
-        </para>
-        <para>
-          This is due to a kernel configuration change. The new naming
-          is consistent with those of other Linux distributions with
-          systemd. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/29197">#29197</link>
-          for more information.
-        </para>
-        <para>
-          A machine is affected if the <literal>virt-what</literal> tool
-          either returns <literal>qemu</literal> or
-          <literal>kvm</literal> <emphasis>and</emphasis> has interface
-          names used in any part of its NixOS configuration, in
-          particular if a static network configuration with
-          <literal>networking.interfaces</literal> is used.
-        </para>
-        <para>
-          Before rebooting affected machines, please ensure:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              Change the interface names in your NixOS configuration.
-              The first interface will be called
-              <literal>ens3</literal>, the second one
-              <literal>ens8</literal> and starting from there
-              incremented by 1.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              After changing the interface names, rebuild your system
-              with <literal>nixos-rebuild boot</literal> to activate the
-              new configuration after a reboot. If you switch to the new
-              configuration right away you might lose network
-              connectivity! If using <literal>nixops</literal>, deploy
-              with <literal>nixops deploy --force-reboot</literal>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The following changes apply if the
-          <literal>stateVersion</literal> is changed to 17.09 or higher.
-          For <literal>stateVersion = &quot;17.03&quot;</literal> or
-          lower the old behavior is preserved.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The <literal>postgres</literal> default version was
-              changed from 9.5 to 9.6.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>postgres</literal> superuser name has changed
-              from <literal>root</literal> to
-              <literal>postgres</literal> to more closely follow what
-              other Linux distributions are doing.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>postgres</literal> default
-              <literal>dataDir</literal> has changed from
-              <literal>/var/db/postgres</literal> to
-              <literal>/var/lib/postgresql/$psqlSchema</literal> where
-              $psqlSchema is 9.6 for example.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>mysql</literal> default
-              <literal>dataDir</literal> has changed from
-              <literal>/var/mysql</literal> to
-              <literal>/var/lib/mysql</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Radicale's default package has changed from 1.x to 2.x.
-              Instructions to migrate can be found
-              <link xlink:href="http://radicale.org/1to2/"> here
-              </link>. It is also possible to use the newer version by
-              setting the <literal>package</literal> to
-              <literal>radicale2</literal>, which is done automatically
-              when <literal>stateVersion</literal> is 17.09 or higher.
-              The <literal>extraArgs</literal> option has been added to
-              allow passing the data migration arguments specified in
-              the instructions; see the <literal>radicale.nix</literal>
-              NixOS test for an example migration.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>aiccu</literal> package was removed. This is due
-          to SixXS <link xlink:href="https://www.sixxs.net/main/">
-          sunsetting</link> its IPv6 tunnel.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>fanctl</literal> package and
-          <literal>fan</literal> module have been removed due to the
-          developers not upstreaming their iproute2 patches and lagging
-          with compatibility to recent iproute2 versions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Top-level <literal>idea</literal> package collection was
-          renamed. All JetBrains IDEs are now at
-          <literal>jetbrains</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>flexget</literal>'s state database cannot be upgraded
-          to its new internal format, requiring removal of any existing
-          <literal>db-config.sqlite</literal> which will be
-          automatically recreated.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>ipfs</literal> service now doesn't ignore the
-          <literal>dataDir</literal> option anymore. If you've ever set
-          this option to anything other than the default you'll have to
-          either unset it (so the default gets used) or migrate the old
-          data manually with
-        </para>
-        <programlisting>
-dataDir=&lt;valueOfDataDir&gt;
-mv /var/lib/ipfs/.ipfs/* $dataDir
-rmdir /var/lib/ipfs/.ipfs
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>caddy</literal> service was previously using an
-          extra <literal>.caddy</literal> directory in the data
-          directory specified with the <literal>dataDir</literal>
-          option. The contents of the <literal>.caddy</literal>
-          directory are now expected to be in the
-          <literal>dataDir</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>ssh-agent</literal> user service is not started
-          by default anymore. Use
-          <literal>programs.ssh.startAgent</literal> to enable it if
-          needed. There is also a new
-          <literal>programs.gnupg.agent</literal> module that creates a
-          <literal>gpg-agent</literal> user service. It can also serve
-          as a SSH agent if <literal>enableSSHSupport</literal> is set.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <literal>services.tinc.networks.&lt;name&gt;.listenAddress</literal>
-          option had a misleading name that did not correspond to its
-          behavior. It now correctly defines the ip to listen for
-          incoming connections on. To keep the previous behaviour, use
-          <literal>services.tinc.networks.&lt;name&gt;.bindToAddress</literal>
-          instead. Refer to the description of the options for more
-          details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>tlsdate</literal> package and module were removed.
-          This is due to the project being dead and not building with
-          openssl 1.1.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>wvdial</literal> package and module were removed.
-          This is due to the project being dead and not building with
-          openssl 1.1.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>cc-wrapper</literal>'s setup-hook now exports a
-          number of environment variables corresponding to binutils
-          binaries, (e.g. <literal>LD</literal>,
-          <literal>STRIP</literal>, <literal>RANLIB</literal>, etc).
-          This is done to prevent packages' build systems guessing,
-          which is harder to predict, especially when cross-compiling.
-          However, some packages have broken due to this—their build
-          systems either not supporting, or claiming to support without
-          adequate testing, taking such environment variables as
-          parameters.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.firefox.syncserver</literal> now runs by
-          default as a non-root user. To accomodate this change, the
-          default sqlite database location has also been changed.
-          Migration should work automatically. Refer to the description
-          of the options for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>compiz</literal> window manager and package was
-          removed. The system support had been broken for several years.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Touchpad support should now be enabled through
-          <literal>libinput</literal> as <literal>synaptics</literal> is
-          now deprecated. See the option
-          <literal>services.xserver.libinput.enable</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          grsecurity/PaX support has been dropped, following upstream's
-          decision to cease free support. See
-          <link xlink:href="https://grsecurity.net/passing_the_baton.php">
-          upstream's announcement</link> for more information. No
-          complete replacement for grsecurity/PaX is available
-          presently.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.mysql</literal> now has declarative
-          configuration of databases and users with the
-          <literal>ensureDatabases</literal> and
-          <literal>ensureUsers</literal> options.
-        </para>
-        <para>
-          These options will never delete existing databases and users,
-          especially not when the value of the options are changed.
-        </para>
-        <para>
-          The MySQL users will be identified using
-          <link xlink:href="https://mariadb.com/kb/en/library/authentication-plugin-unix-socket/">
-          Unix socket authentication</link>. This authenticates the Unix
-          user with the same name only, and that without the need for a
-          password.
-        </para>
-        <para>
-          If you have previously created a MySQL <literal>root</literal>
-          user <emphasis>with a password</emphasis>, you will need to
-          add <literal>root</literal> user for unix socket
-          authentication before using the new options. This can be done
-          by running the following SQL script:
-        </para>
-        <programlisting language="SQL">
-CREATE USER 'root'@'%' IDENTIFIED BY '';
-GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
-FLUSH PRIVILEGES;
-
--- Optionally, delete the password-authenticated user:
--- DROP USER 'root'@'localhost';
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.mysqlBackup</literal> now works by default
-          without any user setup, including for users other than
-          <literal>mysql</literal>.
-        </para>
-        <para>
-          By default, the <literal>mysql</literal> user is no longer the
-          user which performs the backup. Instead a system account
-          <literal>mysqlbackup</literal> is used.
-        </para>
-        <para>
-          The <literal>mysqlBackup</literal> service is also now using
-          systemd timers instead of <literal>cron</literal>.
-        </para>
-        <para>
-          Therefore, the <literal>services.mysqlBackup.period</literal>
-          option no longer exists, and has been replaced with
-          <literal>services.mysqlBackup.calendar</literal>, which is in
-          the format of
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events">systemd.time(7)</link>.
-        </para>
-        <para>
-          If you expect to be sent an e-mail when the backup fails,
-          consider using a script which monitors the systemd journal for
-          errors. Regretfully, at present there is no built-in
-          functionality for this.
-        </para>
-        <para>
-          You can check that backups still work by running
-          <literal>systemctl start mysql-backup</literal> then
-          <literal>systemctl status mysql-backup</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Templated systemd services e.g
-          <literal>container@name</literal> are now handled currectly
-          when switching to a new configuration, resulting in them being
-          reloaded.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Steam: the <literal>newStdcpp</literal> parameter was removed
-          and should not be needed anymore.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Redis has been updated to version 4 which mandates a cluster
-          mass-restart, due to changes in the network handling, in order
-          to ensure compatibility with networks NATing traffic.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-17.09-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Modules can now be disabled by using
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-replace-modules">
-          disabledModules</link>, allowing another to take it's place.
-          This can be used to import a set of modules from another
-          channel while keeping the rest of the system on a stable
-          release.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Updated to FreeType 2.7.1, including a new TrueType engine.
-          The new engine replaces the Infinality engine which was the
-          default in NixOS. The default font rendering settings are now
-          provided by fontconfig-penultimate, replacing
-          fontconfig-ultimate; the new defaults are less invasive and
-          provide rendering that is more consistent with other systems
-          and hopefully with each font designer's intent. Some
-          system-wide configuration has been removed from the Fontconfig
-          NixOS module where user Fontconfig settings are available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          ZFS/SPL have been updated to 0.7.0,
-          <literal>zfsUnstable, splUnstable</literal> have therefore
-          been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>time.timeZone</literal> option now allows the
-          value <literal>null</literal> in addition to timezone strings.
-          This value allows changing the timezone of a system
-          imperatively using
-          <literal>timedatectl set-timezone</literal>. The default
-          timezone is still UTC.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nixpkgs overlays may now be specified with a file as well as a
-          directory. The value of
-          <literal>&lt;nixpkgs-overlays&gt;</literal> may be a file, and
-          <literal>~/.config/nixpkgs/overlays.nix</literal> can be used
-          instead of the <literal>~/.config/nixpkgs/overlays</literal>
-          directory.
-        </para>
-        <para>
-          See the overlays chapter of the Nixpkgs manual for more
-          details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Definitions for <literal>/etc/hosts</literal> can now be
-          specified declaratively with
-          <literal>networking.hosts</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Two new options have been added to the installer loader, in
-          addition to the default having changed. The kernel log
-          verbosity has been lowered to the upstream default for the
-          default options, in order to not spam the console when e.g.
-          joining a network.
-        </para>
-        <para>
-          This therefore leads to adding a new <literal>debug</literal>
-          option to set the log level to the previous verbose mode, to
-          make debugging easier, but still accessible easily.
-        </para>
-        <para>
-          Additionally a <literal>copytoram</literal> option has been
-          added, which makes it possible to remove the install medium
-          after booting. This allows tethering from your phone after
-          booting from it.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.gitlab-runner.configOptions</literal> has
-          been added to specify the configuration of gitlab-runners
-          declaratively.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.jenkins.plugins</literal> has been added to
-          install plugins easily, this can be generated with
-          jenkinsPlugins2nix.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.postfix.config</literal> has been added to
-          specify the main.cf with NixOS options. Additionally other
-          options have been added to the postfix module and has been
-          improved further.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The GitLab package and module have been updated to the latest
-          10.0 release.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>systemd-boot</literal> boot loader now lists the
-          NixOS version, kernel version and build date of all bootable
-          generations.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The dnscrypt-proxy service now defaults to using a random
-          upstream resolver, selected from the list of public
-          non-logging resolvers with DNSSEC support. Existing
-          configurations can be migrated to this mode of operation by
-          omitting the
-          <literal>services.dnscrypt-proxy.resolverName</literal> option
-          or setting it to <literal>&quot;random&quot;</literal>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1803.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1803.section.xml
deleted file mode 100644
index 910cad467e9d..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1803.section.xml
+++ /dev/null
@@ -1,879 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-18.03">
-  <title>Release 18.03 (<quote>Impala</quote>, 2018/04/04)</title>
-  <section xml:id="sec-release-18.03-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          End of support is planned for end of October 2018, handing
-          over to 18.09.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Platform support: x86_64-linux and x86_64-darwin since release
-          time (the latter isn't NixOS, really). Binaries for
-          aarch64-linux are available, but no channel exists yet, as
-          it's waiting for some test fixes, etc.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nix now defaults to 2.0; see its
-          <link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-2.0">release
-          notes</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Core version changes: linux: 4.9 -&gt; 4.14, glibc: 2.25 -&gt;
-          2.26, gcc: 6 -&gt; 7, systemd: 234 -&gt; 237.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Desktop version changes: gnome: 3.24 -&gt; 3.26, (KDE)
-          plasma-desktop: 5.10 -&gt; 5.12.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          MariaDB 10.2, updated from 10.1, is now the default MySQL
-          implementation. While upgrading a few changes have been made
-          to the infrastructure involved:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>libmysql</literal> has been deprecated, please
-              use <literal>mysql.connector-c</literal> instead, a
-              compatibility passthru has been added to the MySQL
-              packages.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>mysql57</literal> package has a new
-              <literal>static</literal> output containing the static
-              libraries including <literal>libmysqld.a</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          PHP now defaults to PHP 7.2, updated from 7.1.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-18.03-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>./config/krb5/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./hardware/digitalbitbox.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./misc/label.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/ccache.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/criu.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/digitalbitbox/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/less.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/npm.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/plotinus.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/rootston.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/systemtap.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/sway.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/udevil.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/way-cooler.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/yabar.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/zsh/zsh-autoenv.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/borgbackup.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/crashplan-small-business.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/dleyna-renderer.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/dleyna-server.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/pipewire.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/gnome3/chrome-gnome-shell.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/gnome3/tracker-miners.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/fwupd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/interception-tools.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/u2f.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/usbmuxd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/mail/clamsmtp.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/mail/dkimproxy-out.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/mail/pfix-srsd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/gitea.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/home-assistant.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/ihaskell.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/logkeys.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/novacomd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/osrm.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/plexpy.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/pykms.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/tzupdate.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/monitoring/fusion-inventory.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/monitoring/prometheus/exporters.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/beegfs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/davfs2.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/openafs/client.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/openafs/server.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/ceph.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/aria2.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/monero.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/nghttpx/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/nixops-dns.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/rxe.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/stunnel.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/matomo.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/restya-board.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-servers/mighttpd2.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/x11/fractalart.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./system/boot/binfmt.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./system/boot/grow-partition.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./tasks/filesystems/ecryptfs.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./virtualisation/hyperv-guest.nix</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-18.03-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>sound.enable</literal> now defaults to false.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Dollar signs in options under
-          <literal>services.postfix</literal> are passed verbatim to
-          Postfix, which will interpret them as the beginning of a
-          parameter expression. This was already true for string-valued
-          options in the previous release, but not for list-valued
-          options. If you need to pass literal dollar signs through
-          Postfix, double them.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>postage</literal> package (for web-based
-          PostgreSQL administration) has been renamed to
-          <literal>pgmanage</literal>. The corresponding module has also
-          been renamed. To migrate please rename all
-          <literal>services.postage</literal> options to
-          <literal>services.pgmanage</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Package attributes starting with a digit have been prefixed
-          with an underscore sign. This is to avoid quoting in the
-          configuration and other issues with command-line tools like
-          <literal>nix-env</literal>. The change affects the following
-          packages:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>2048-in-terminal</literal> →
-              <literal>_2048-in-terminal</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>90secondportraits</literal> →
-              <literal>_90secondportraits</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>2bwm</literal> → <literal>_2bwm</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>389-ds-base</literal> →
-              <literal>_389-ds-base</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <emphasis role="strong">The OpenSSH service no longer enables
-          support for DSA keys by default, which could cause a system
-          lock out. Update your keys or, unfavorably, re-enable DSA
-          support manually.</emphasis>
-        </para>
-        <para>
-          DSA support was
-          <link xlink:href="https://www.openssh.com/legacy.html">deprecated
-          in OpenSSH 7.0</link>, due to it being too weak. To re-enable
-          support, add
-          <literal>PubkeyAcceptedKeyTypes +ssh-dss</literal> to the end
-          of your <literal>services.openssh.extraConfig</literal>.
-        </para>
-        <para>
-          After updating the keys to be stronger, anyone still on a
-          pre-17.03 version is safe to jump to 17.03, as vetted
-          <link xlink:href="https://search.nix.gsc.io/?q=stateVersion">here</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>openssh</literal> package now includes Kerberos
-          support by default; the
-          <literal>openssh_with_kerberos</literal> package is now a
-          deprecated alias. If you do not want Kerberos support, you can
-          do
-          <literal>openssh.override { withKerberos = false; }</literal>.
-          Note, this also applies to the <literal>openssh_hpn</literal>
-          package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>cc-wrapper</literal> has been split in two; there is
-          now also a <literal>bintools-wrapper</literal>. The most
-          commonly used files in <literal>nix-support</literal> are now
-          split between the two wrappers. Some commonly used ones, like
-          <literal>nix-support/dynamic-linker</literal>, are duplicated
-          for backwards compatability, even though they rightly belong
-          only in <literal>bintools-wrapper</literal>. Other more
-          obscure ones are just moved.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The propagation logic has been changed. The new logic, along
-          with new types of dependencies that go with, is thoroughly
-          documented in the &quot;Specifying dependencies&quot; section
-          of the &quot;Standard Environment&quot; chapter of the nixpkgs
-          manual. The old logic isn't but is easy to describe:
-          dependencies were propagated as the same type of dependency no
-          matter what. In practice, that means that many
-          <literal>propagatedNativeBuildInputs</literal> should instead
-          be <literal>propagatedBuildInputs</literal>. Thankfully, that
-          was and is the least used type of dependency. Also, it means
-          that some <literal>propagatedBuildInputs</literal> should
-          instead be <literal>depsTargetTargetPropagated</literal>.
-          Other types dependencies should be unaffected.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.addPassthru drv passthru</literal> is removed.
-          Use <literal>lib.extendDerivation true passthru drv</literal>
-          instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>memcached</literal> service no longer accept
-          dynamic socket paths via
-          <literal>services.memcached.socket</literal>. Unix sockets can
-          be still enabled by
-          <literal>services.memcached.enableUnixSocket</literal> and
-          will be accessible at
-          <literal>/run/memcached/memcached.sock</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>hardware.amdHybridGraphics.disable</literal>
-          option was removed for lack of a maintainer. If you still need
-          this module, you may wish to include a copy of it from an
-          older version of nixos in your imports.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The merging of config options for
-          <literal>services.postfix.config</literal> was buggy.
-          Previously, if other options in the Postfix module like
-          <literal>services.postfix.useSrs</literal> were set and the
-          user set config options that were also set by such options,
-          the resulting config wouldn't include all options that were
-          needed. They are now merged correctly. If config options need
-          to be overridden, <literal>lib.mkForce</literal> or
-          <literal>lib.mkOverride</literal> can be used.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The following changes apply if the
-          <literal>stateVersion</literal> is changed to 18.03 or higher.
-          For <literal>stateVersion = &quot;17.09&quot;</literal> or
-          lower the old behavior is preserved.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>matrix-synapse</literal> uses postgresql by
-              default instead of sqlite. Migration instructions can be
-              found
-              <link xlink:href="https://github.com/matrix-org/synapse/blob/master/docs/postgres.rst#porting-from-sqlite">
-              here </link>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>jid</literal> package has been removed, due to
-          maintenance overhead of a go package having non-versioned
-          dependencies.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When using <literal>services.xserver.libinput</literal>
-          (enabled by default in GNOME), it now handles all input
-          devices, not just touchpads. As a result, you might need to
-          re-evaluate any custom Xorg configuration. In particular,
-          <literal>Option &quot;XkbRules&quot; &quot;base&quot;</literal>
-          may result in broken keyboard layout.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>attic</literal> package was removed. A maintained
-          fork called
-          <link xlink:href="https://www.borgbackup.org/">Borg</link>
-          should be used instead. Migration instructions can be found
-          <link xlink:href="http://borgbackup.readthedocs.io/en/stable/usage/upgrade.html#attic-and-borg-0-xx-to-borg-1-x">here</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Piwik analytics software was renamed to Matomo:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The package <literal>pkgs.piwik</literal> was renamed to
-              <literal>pkgs.matomo</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The service <literal>services.piwik</literal> was renamed
-              to <literal>services.matomo</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The data directory <literal>/var/lib/piwik</literal> was
-              renamed to <literal>/var/lib/matomo</literal>. All files
-              will be moved automatically on first startup, but you
-              might need to adjust your backup scripts.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The default <literal>serverName</literal> for the nginx
-              configuration changed from
-              <literal>piwik.${config.networking.hostName}</literal> to
-              <literal>matomo.${config.networking.hostName}.${config.networking.domain}</literal>
-              if <literal>config.networking.domain</literal> is set,
-              <literal>matomo.${config.networking.hostName}</literal> if
-              it is not set. If you change your
-              <literal>serverName</literal>, remember you'll need to
-              update the <literal>trustedHosts[]</literal> array in
-              <literal>/var/lib/matomo/config/config.ini.php</literal>
-              as well.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>piwik</literal> user was renamed to
-              <literal>matomo</literal>. The service will adjust
-              ownership automatically for files in the data directory.
-              If you use unix socket authentication, remember to give
-              the new <literal>matomo</literal> user access to the
-              database and to change the <literal>username</literal> to
-              <literal>matomo</literal> in the
-              <literal>[database]</literal> section of
-              <literal>/var/lib/matomo/config/config.ini.php</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              If you named your database `piwik`, you might want to
-              rename it to `matomo` to keep things clean, but this is
-              neither enforced nor required.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nodejs-4_x</literal> is end-of-life.
-          <literal>nodejs-4_x</literal>,
-          <literal>nodejs-slim-4_x</literal> and
-          <literal>nodePackages_4_x</literal> are removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pump.io</literal> NixOS module was removed. It is
-          now maintained as an
-          <link xlink:href="https://github.com/rvl/pump.io-nixos">external
-          module</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Prosody XMPP server has received a major update. The
-          following modules were renamed:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>services.prosody.modules.httpserver</literal> is
-              now <literal>services.prosody.modules.http_files</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.prosody.modules.console</literal> is now
-              <literal>services.prosody.modules.admin_telnet</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          Many new modules are now core modules, most notably
-          <literal>services.prosody.modules.carbons</literal> and
-          <literal>services.prosody.modules.mam</literal>.
-        </para>
-        <para>
-          The better-performing <literal>libevent</literal> backend is
-          now enabled by default.
-        </para>
-        <para>
-          <literal>withCommunityModules</literal> now passes through the
-          modules to <literal>services.prosody.extraModules</literal>.
-          Use <literal>withOnlyInstalledCommunityModules</literal> for
-          modules that should not be enabled directly, e.g
-          <literal>lib_ldap</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          All prometheus exporter modules are now defined as submodules.
-          The exporters are configured using
-          <literal>services.prometheus.exporters</literal>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-18.03-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          ZNC option <literal>services.znc.mutable</literal> now
-          defaults to <literal>true</literal>. That means that old
-          configuration is not overwritten by default when update to the
-          znc options are made.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>networking.wireless.networks.&lt;name&gt;.auth</literal>
-          has been added for wireless networks with WPA-Enterprise
-          authentication. There is also a new
-          <literal>extraConfig</literal> option to directly configure
-          <literal>wpa_supplicant</literal> and
-          <literal>hidden</literal> to connect to hidden networks.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In the module
-          <literal>networking.interfaces.&lt;name&gt;</literal> the
-          following options have been removed:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>ipAddress</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>ipv6Address</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>prefixLength</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>ipv6PrefixLength</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>subnetMask</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          To assign static addresses to an interface the options
-          <literal>ipv4.addresses</literal> and
-          <literal>ipv6.addresses</literal> should be used instead. The
-          options <literal>ip4</literal> and <literal>ip6</literal> have
-          been renamed to <literal>ipv4.addresses</literal>
-          <literal>ipv6.addresses</literal> respectively. The new
-          options <literal>ipv4.routes</literal> and
-          <literal>ipv6.routes</literal> have been added to set up
-          static routing.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>services.logstash.listenAddress</literal>
-          is now <literal>127.0.0.1</literal> by default. Previously the
-          default behaviour was to listen on all interfaces.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.btrfs.autoScrub</literal> has been added, to
-          periodically check btrfs filesystems for data corruption. If
-          there's a correct copy available, it will automatically repair
-          corrupted blocks.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>displayManager.lightdm.greeters.gtk.clock-format.</literal>
-          has been added, the clock format string (as expected by
-          strftime, e.g. <literal>%H:%M</literal>) to use with the
-          lightdm gtk greeter panel.
-        </para>
-        <para>
-          If set to null the default clock format is used.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>displayManager.lightdm.greeters.gtk.indicators</literal>
-          has been added, a list of allowed indicator modules to use
-          with the lightdm gtk greeter panel.
-        </para>
-        <para>
-          Built-in indicators include <literal>~a11y</literal>,
-          <literal>~language</literal>, <literal>~session</literal>,
-          <literal>~power</literal>, <literal>~clock</literal>,
-          <literal>~host</literal>, <literal>~spacer</literal>. Unity
-          indicators can be represented by short name (e.g.
-          <literal>sound</literal>, <literal>power</literal>), service
-          file name, or absolute path.
-        </para>
-        <para>
-          If set to <literal>null</literal> the default indicators are
-          used.
-        </para>
-        <para>
-          In order to have the previous default configuration add
-        </para>
-        <programlisting language="bash">
-{
-  services.xserver.displayManager.lightdm.greeters.gtk.indicators = [
-    &quot;~host&quot; &quot;~spacer&quot;
-    &quot;~clock&quot; &quot;~spacer&quot;
-    &quot;~session&quot;
-    &quot;~language&quot;
-    &quot;~a11y&quot;
-    &quot;~power&quot;
-  ];
-}
-</programlisting>
-        <para>
-          to your <literal>configuration.nix</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The NixOS test driver supports user services declared by
-          <literal>systemd.user.services</literal>. The methods
-          <literal>waitForUnit</literal>,
-          <literal>getUnitInfo</literal>, <literal>startJob</literal>
-          and <literal>stopJob</literal> provide an optional
-          <literal>$user</literal> argument for that purpose.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Enabling bash completion on NixOS,
-          <literal>programs.bash.enableCompletion</literal>, will now
-          also enable completion for the Nix command line tools by
-          installing the
-          <link xlink:href="https://github.com/hedning/nix-bash-completions">nix-bash-completions</link>
-          package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The vim/kakoune plugin updater now reads from a CSV file:
-          check
-          <literal>pkgs/applications/editors/vim/plugins/vim-plugin-names</literal>
-          out to see the new format
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1809.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1809.section.xml
deleted file mode 100644
index aa4637a99b60..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1809.section.xml
+++ /dev/null
@@ -1,941 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-18.09">
-  <title>Release 18.09 (<quote>Jellyfish</quote>, 2018/10/05)</title>
-  <section xml:id="sec-release-18.09-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following notable updates:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          End of support is planned for end of April 2019, handing over
-          to 19.03.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Platform support: x86_64-linux and x86_64-darwin as always.
-          Support for aarch64-linux is as with the previous releases,
-          not equivalent to the x86-64-linux release, but with efforts
-          to reach parity.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nix has been updated to 2.1; see its
-          <link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-2.1">release
-          notes</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Core versions: linux: 4.14 LTS (unchanged), glibc: 2.26 →
-          2.27, gcc: 7 (unchanged), systemd: 237 → 239.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Desktop version changes: gnome: 3.26 → 3.28, (KDE)
-          plasma-desktop: 5.12 → 5.13.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      Notable changes and additions for 18.09 include:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Support for wrapping binaries using
-          <literal>firejail</literal> has been added through
-          <literal>programs.firejail.wrappedBinaries</literal>.
-        </para>
-        <para>
-          For example
-        </para>
-        <programlisting language="bash">
-{
-  programs.firejail = {
-    enable = true;
-    wrappedBinaries = {
-      firefox = &quot;${lib.getBin pkgs.firefox}/bin/firefox&quot;;
-      mpv = &quot;${lib.getBin pkgs.mpv}/bin/mpv&quot;;
-    };
-  };
-}
-</programlisting>
-        <para>
-          This will place <literal>firefox</literal> and
-          <literal>mpv</literal> binaries in the global path wrapped by
-          firejail.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          User channels are now in the default
-          <literal>NIX_PATH</literal>, allowing users to use their
-          personal <literal>nix-channel</literal> defined channels in
-          <literal>nix-build</literal> and <literal>nix-shell</literal>
-          commands, as well as in imports like
-          <literal>import &lt;mychannel&gt;</literal>.
-        </para>
-        <para>
-          For example
-        </para>
-        <programlisting>
-$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgsunstable
-$ nix-channel --update
-$ nix-build '&lt;nixpkgsunstable&gt;' -A gitFull
-$ nix run -f '&lt;nixpkgsunstable&gt;' gitFull
-$ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
-</programlisting>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-18.09-new-services">
-    <title>New Services</title>
-    <para>
-      A curated selection of new services that were added since the last
-      release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The <literal>services.cassandra</literal> module has been
-          reworked and was rewritten from scratch. The service has
-          succeeding tests for the versions 2.1, 2.2, 3.0 and 3.11 of
-          <link xlink:href="https://cassandra.apache.org/">Apache
-          Cassandra</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new <literal>services.foundationdb</literal> module
-          for deploying
-          <link xlink:href="https://www.foundationdb.org">FoundationDB</link>
-          clusters.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When enabled the <literal>iproute2</literal> will copy the
-          files expected by ip route (e.g.,
-          <literal>rt_tables</literal>) in
-          <literal>/etc/iproute2</literal>. This allows to write aliases
-          for routing tables for instance.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.strongswan-swanctl</literal> is a modern
-          replacement for <literal>services.strongswan</literal>. You
-          can use either one of them to setup IPsec VPNs but not both at
-          the same time.
-        </para>
-        <para>
-          <literal>services.strongswan-swanctl</literal> uses the
-          <link xlink:href="https://wiki.strongswan.org/projects/strongswan/wiki/swanctl">swanctl</link>
-          command which uses the modern
-          <link xlink:href="https://github.com/strongswan/strongswan/blob/master/src/libcharon/plugins/vici/README.md">vici</link>
-          <emphasis>Versatile IKE Configuration Interface</emphasis>.
-          The deprecated <literal>ipsec</literal> command used in
-          <literal>services.strongswan</literal> is using the legacy
-          <link xlink:href="https://github.com/strongswan/strongswan/blob/master/README_LEGACY.md">stroke
-          configuration interface</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The new <literal>services.elasticsearch-curator</literal>
-          service periodically curates or manages, your Elasticsearch
-          indices and snapshots.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      Every new services:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>./config/xdg/autostart.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./config/xdg/icons.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./config/xdg/menus.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./config/xdg/mime.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./hardware/brightnessctl.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./hardware/onlykey.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./hardware/video/uvcvideo/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./misc/documentation.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/firejail.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/iftop.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/sedutil.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/singularity.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/xss-lock.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/zsh/zsh-autosuggestions.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/admin/oxidized.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/duplicati.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/restic.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/restic-rest-server.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/cluster/hadoop/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/databases/aerospike.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/databases/monetdb.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/bamf.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/flatpak.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/zeitgeist.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/development/bloop.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/development/jupyter/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/lcd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/undervolt.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/clipmenu.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/gitweb.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/serviio.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/safeeyes.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/sysprof.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/weechat.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/monitoring/datadog-agent.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/monitoring/incron.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/dnsdist.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/freeradius.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/hans.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/morty.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/ndppd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/ocserv.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/owamp.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/quagga.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/shadowsocks.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/stubby.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/zeronet.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/security/certmgr.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/security/cfssl.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/security/oauth2_proxy_nginx.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/virtlyst.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/youtrack.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-servers/hitch/default.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-servers/hydron.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-servers/meguca.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-servers/nginx/gitweb.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./virtualisation/kvmgt.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./virtualisation/qemu-guest-agent.nix</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-18.09-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Some licenses that were incorrectly not marked as unfree now
-          are. This is the case for:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              cc-by-nc-sa-20: Creative Commons Attribution Non
-              Commercial Share Alike 2.0
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              cc-by-nc-sa-25: Creative Commons Attribution Non
-              Commercial Share Alike 2.5
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              cc-by-nc-sa-30: Creative Commons Attribution Non
-              Commercial Share Alike 3.0
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              cc-by-nc-sa-40: Creative Commons Attribution Non
-              Commercial Share Alike 4.0
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              cc-by-nd-30: Creative Commons Attribution-No Derivative
-              Works v3.00
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              msrla: Microsoft Research License Agreement
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The deprecated <literal>services.cassandra</literal> module
-          has seen a complete rewrite. (See above.)
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.strict</literal> is removed. Use
-          <literal>builtins.seq</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>clementine</literal> package points now to the
-          free derivation. <literal>clementineFree</literal> is removed
-          now and <literal>clementineUnfree</literal> points to the
-          package which is bundled with the unfree
-          <literal>libspotify</literal> package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>netcat</literal> package is now taken directly
-          from OpenBSD's <literal>libressl</literal>, instead of relying
-          on Debian's fork. The new version should be very close to the
-          old version, but there are some minor differences.
-          Importantly, flags like -b, -q, -C, and -Z are no longer
-          accepted by the nc command.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.docker-registry.extraConfig</literal>
-          object doesn't contain environment variables anymore. Instead
-          it needs to provide an object structure that can be mapped
-          onto the YAML configuration defined in
-          <link xlink:href="https://github.com/docker/distribution/blob/v2.6.2/docs/configuration.md">the
-          <literal>docker/distribution</literal> docs</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>gnucash</literal> has changed from version 2.4 to
-          3.x. If you've been using <literal>gnucash</literal> (version
-          2.4) instead of <literal>gnucash26</literal> (version 2.6) you
-          must open your Gnucash data file(s) with
-          <literal>gnucash26</literal> and then save them to upgrade the
-          file format. Then you may use your data file(s) with Gnucash
-          3.x. See the upgrade
-          <link xlink:href="https://wiki.gnucash.org/wiki/FAQ#Using_Different_Versions.2C_Up_And_Downgrade">documentation</link>.
-          Gnucash 2.4 is still available under the attribute
-          <literal>gnucash24</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.munge</literal> now runs as user (and group)
-          <literal>munge</literal> instead of root. Make sure the key
-          file is accessible to the daemon.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>dockerTools.buildImage</literal> now uses
-          <literal>null</literal> as default value for
-          <literal>tag</literal>, which indicates that the nix output
-          hash will be used as tag.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The ELK stack: <literal>elasticsearch</literal>,
-          <literal>logstash</literal> and <literal>kibana</literal> has
-          been upgraded from 2.* to 6.3.*. The 2.* versions have been
-          <link xlink:href="https://www.elastic.co/support/eol">unsupported
-          since last year</link> so they have been removed. You can
-          still use the 5.* versions under the names
-          <literal>elasticsearch5</literal>,
-          <literal>logstash5</literal> and <literal>kibana5</literal>.
-        </para>
-        <para>
-          The elastic beats: <literal>filebeat</literal>,
-          <literal>heartbeat</literal>, <literal>metricbeat</literal>
-          and <literal>packetbeat</literal> have had the same treatment:
-          they now target 6.3.* as well. The 5.* versions are available
-          under the names: <literal>filebeat5</literal>,
-          <literal>heartbeat5</literal>, <literal>metricbeat5</literal>
-          and <literal>packetbeat5</literal>
-        </para>
-        <para>
-          The ELK-6.3 stack now comes with
-          <link xlink:href="https://www.elastic.co/products/x-pack/open">X-Pack
-          by default</link>. Since X-Pack is licensed under the
-          <link xlink:href="https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt">Elastic
-          License</link> the ELK packages now have an unfree license. To
-          use them you need to specify
-          <literal>allowUnfree = true;</literal> in your nixpkgs
-          configuration.
-        </para>
-        <para>
-          Fortunately there is also a free variant of the ELK stack
-          without X-Pack. The packages are available under the names:
-          <literal>elasticsearch-oss</literal>,
-          <literal>logstash-oss</literal> and
-          <literal>kibana-oss</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Options
-          <literal>boot.initrd.luks.devices.name.yubikey.ramfsMountPoint</literal>
-          <literal>boot.initrd.luks.devices.name.yubikey.storage.mountPoint</literal>
-          were removed. <literal>luksroot.nix</literal> module never
-          supported more than one YubiKey at a time anyway, hence those
-          options never had any effect. You should be able to remove
-          them from your config without any issues.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>stdenv.system</literal> and <literal>system</literal>
-          in nixpkgs now refer to the host platform instead of the build
-          platform. For native builds this is not change, let alone a
-          breaking one. For cross builds, it is a breaking change, and
-          <literal>stdenv.buildPlatform.system</literal> can be used
-          instead for the old behavior. They should be using that
-          anyways for clarity.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Groups <literal>kvm</literal> and <literal>render</literal>
-          are introduced now, as systemd requires them.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-18.09-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>dockerTools.pullImage</literal> relies on image
-          digest instead of image tag to download the image. The
-          <literal>sha256</literal> of a pulled image has to be updated.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.attrNamesToStr</literal> has been deprecated. Use
-          more specific concatenation
-          (<literal>lib.concat(Map)StringsSep</literal>) instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.addErrorContextToAttrs</literal> has been
-          deprecated. Use <literal>builtins.addErrorContext</literal>
-          directly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.showVal</literal> has been deprecated. Use
-          <literal>lib.traceSeqN</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.traceXMLVal</literal> has been deprecated. Use
-          <literal>lib.traceValFn builtins.toXml</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.traceXMLValMarked</literal> has been deprecated.
-          Use
-          <literal>lib.traceValFn (x: str + builtins.toXML x)</literal>
-          instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pkgs</literal> argument to NixOS modules can now
-          be set directly using <literal>nixpkgs.pkgs</literal>.
-          Previously, only the <literal>system</literal>,
-          <literal>config</literal> and <literal>overlays</literal>
-          arguments could be used to influence <literal>pkgs</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A NixOS system can now be constructed more easily based on a
-          preexisting invocation of Nixpkgs. For example:
-        </para>
-        <programlisting language="bash">
-{
-  inherit (pkgs.nixos {
-    boot.loader.grub.enable = false;
-    fileSystems.&quot;/&quot;.device = &quot;/dev/xvda1&quot;;
-  }) toplevel kernel initialRamdisk manual;
-}
-</programlisting>
-        <para>
-          This benefits evaluation performance, lets you write Nixpkgs
-          packages that depend on NixOS images and is consistent with a
-          deployment architecture that would be centered around Nixpkgs
-          overlays.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.traceValIfNot</literal> has been deprecated. Use
-          <literal>if/then/else</literal> and
-          <literal>lib.traceValSeq</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.traceCallXml</literal> has been deprecated.
-          Please complain if you use the function regularly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The attribute <literal>lib.nixpkgsVersion</literal> has been
-          deprecated in favor of <literal>lib.version</literal>. Please
-          refer to the discussion in
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/39416#discussion_r183845745">NixOS/nixpkgs#39416</link>
-          for further reference.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.recursiveUpdateUntil</literal> was not acting
-          according to its specification. It has been fixed to act
-          according to the docstring, and a test has been added.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The module for <literal>security.dhparams</literal> has two
-          new options now:
-        </para>
-        <variablelist>
-          <varlistentry>
-            <term>
-              <literal>security.dhparams.stateless</literal>
-            </term>
-            <listitem>
-              <para>
-                Puts the generated Diffie-Hellman parameters into the
-                Nix store instead of managing them in a stateful manner
-                in <literal>/var/lib/dhparams</literal>.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term>
-              <literal>security.dhparams.defaultBitSize</literal>
-            </term>
-            <listitem>
-              <para>
-                The default bit size to use for the generated
-                Diffie-Hellman parameters.
-              </para>
-            </listitem>
-          </varlistentry>
-        </variablelist>
-        <note>
-          <para>
-            The path to the actual generated parameter files should now
-            be queried using
-            <literal>config.security.dhparams.params.name.path</literal>
-            because it might be either in the Nix store or in a
-            directory configured by
-            <literal>security.dhparams.path</literal>.
-          </para>
-        </note>
-        <note>
-          <para>
-            <emphasis role="strong">For developers:</emphasis>
-          </para>
-          <para>
-            Module implementers should not set a specific bit size in
-            order to let users configure it by themselves if they want
-            to have a different bit size than the default (2048).
-          </para>
-          <para>
-            An example usage of this would be:
-          </para>
-          <programlisting language="bash">
-{ config, ... }:
-
-{
-  security.dhparams.params.myservice = {};
-  environment.etc.&quot;myservice.conf&quot;.text = ''
-    dhparams = ${config.security.dhparams.params.myservice.path}
-  '';
-}
-</programlisting>
-        </note>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>networking.networkmanager.useDnsmasq</literal> has
-          been deprecated. Use
-          <literal>networking.networkmanager.dns</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Kubernetes package has been bumped to major version 1.11.
-          Please consult the
-          <link xlink:href="https://github.com/kubernetes/kubernetes/blob/release-1.11/CHANGELOG-1.11.md">release
-          notes</link> for details on new features and api changes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.kubernetes.apiserver.admissionControl</literal>
-          was renamed to
-          <literal>services.kubernetes.apiserver.enableAdmissionPlugins</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Recommended way to access the Kubernetes Dashboard is via
-          HTTPS (TLS) Therefore; public service port for the dashboard
-          has changed to 443 (container port 8443) and scheme to https.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.kubernetes.apiserver.address</literal> was
-          renamed to
-          <literal>services.kubernetes.apiserver.bindAddress</literal>.
-          Note that the default value has changed from 127.0.0.1 to
-          0.0.0.0.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.kubernetes.apiserver.publicAddress</literal>
-          was not used and thus has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.kubernetes.addons.dashboard.enableRBAC</literal>
-          was renamed to
-          <literal>services.kubernetes.addons.dashboard.rbac.enable</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Kubernetes Dashboard now has only minimal RBAC permissions
-          by default. If dashboard cluster-admin rights are desired, set
-          <literal>services.kubernetes.addons.dashboard.rbac.clusterAdmin</literal>
-          to true. On existing clusters, in order for the revocation of
-          privileges to take effect, the current ClusterRoleBinding for
-          kubernetes-dashboard must be manually removed:
-          <literal>kubectl delete clusterrolebinding kubernetes-dashboard</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>programs.screen</literal> module provides allows
-          to configure <literal>/etc/screenrc</literal>, however the
-          module behaved fairly counterintuitive as the config exists,
-          but the package wasn't available. Since 18.09
-          <literal>pkgs.screen</literal> will be added to
-          <literal>environment.systemPackages</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The module <literal>services.networking.hostapd</literal> now
-          uses WPA2 by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>s6Dns</literal>, <literal>s6Networking</literal>,
-          <literal>s6LinuxUtils</literal> and
-          <literal>s6PortableUtils</literal> renamed to
-          <literal>s6-dns</literal>, <literal>s6-networking</literal>,
-          <literal>s6-linux-utils</literal> and
-          <literal>s6-portable-utils</literal> respectively.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The module option <literal>nix.useSandbox</literal> is now
-          defaulted to <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The config activation script of
-          <literal>nixos-rebuild</literal> now
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemctl.html#Manager%20Lifecycle%20Commands">reloads</link>
-          all user units for each authenticated user.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default display manager is now LightDM. To use SLiM set
-          <literal>services.xserver.displayManager.slim.enable</literal>
-          to <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS option descriptions are now automatically broken up into
-          individual paragraphs if the text contains two consecutive
-          newlines, so it's no longer necessary to use
-          <literal>&lt;/para&gt;&lt;para&gt;</literal> to start a new
-          paragraph.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Top-level <literal>buildPlatform</literal>,
-          <literal>hostPlatform</literal>, and
-          <literal>targetPlatform</literal> in Nixpkgs are deprecated.
-          Please use their equivalents in <literal>stdenv</literal>
-          instead: <literal>stdenv.buildPlatform</literal>,
-          <literal>stdenv.hostPlatform</literal>, and
-          <literal>stdenv.targetPlatform</literal>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml
deleted file mode 100644
index f26e68e13200..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml
+++ /dev/null
@@ -1,790 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-19.03">
-  <title>Release 19.03 (<quote>Koi</quote>, 2019/04/11)</title>
-  <section xml:id="sec-release-19.03-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          End of support is planned for end of October 2019, handing
-          over to 19.09.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default Python 3 interpreter is now CPython 3.7 instead of
-          CPython 3.6.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Added the Pantheon desktop environment. It can be enabled
-          through
-          <literal>services.xserver.desktopManager.pantheon.enable</literal>.
-        </para>
-        <note>
-          <para>
-            By default,
-            <literal>services.xserver.desktopManager.pantheon</literal>
-            enables LightDM as a display manager, as pantheon's screen
-            locking implementation relies on it. Because of that it is
-            recommended to leave LightDM enabled. If you'd like to
-            disable it anyway, set
-            <literal>services.xserver.displayManager.lightdm.enable</literal>
-            to <literal>false</literal> and enable your preferred
-            display manager.
-          </para>
-        </note>
-        <para>
-          Also note that Pantheon's LightDM greeter is not enabled by
-          default, because it has numerous issues in NixOS and isn't
-          optimal for use here yet.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A major refactoring of the Kubernetes module has been
-          completed. Refactorings primarily focus on decoupling
-          components and enhancing security. Two-way TLS and RBAC has
-          been enabled by default for all components, which slightly
-          changes the way the module is configured. See:
-          <xref linkend="sec-kubernetes" /> for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is now a set of <literal>confinement</literal> options
-          for <literal>systemd.services</literal>, which allows to
-          restrict services into a chroot 2 ed environment that only
-          contains the store paths from the runtime closure of the
-          service.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-19.03-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>./programs/nm-applet.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new <literal>security.googleOsLogin</literal>
-          module for using
-          <link xlink:href="https://cloud.google.com/compute/docs/instances/managing-instance-access">OS
-          Login</link> to manage SSH access to Google Compute Engine
-          instances, which supersedes the imperative and broken
-          <literal>google-accounts-daemon</literal> used in
-          <literal>nixos/modules/virtualisation/google-compute-config.nix</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/beanstalkd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new <literal>services.cockroachdb</literal> module
-          for running CockroachDB databases. NixOS now ships with
-          CockroachDB 2.1.x as well, available on
-          <literal>x86_64-linux</literal> and
-          <literal>aarch64-linux</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./security/duosec.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <link xlink:href="https://duo.com/docs/duounix">PAM module
-          for Duo Security</link> has been enabled for use. One can
-          configure it using the <literal>security.duosec</literal>
-          options along with the corresponding PAM option in
-          <literal>security.pam.services.&lt;name?&gt;.duoSecurity.enable</literal>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-19.03-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The minimum version of Nix required to evaluate Nixpkgs is now
-          2.0.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              For users of NixOS 18.03 and 19.03, NixOS defaults to Nix
-              2.0, but supports using Nix 1.11 by setting
-              <literal>nix.package = pkgs.nix1;</literal>. If this
-              option is set to a Nix 1.11 package, you will need to
-              either unset the option or upgrade it to Nix 2.0.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              For users of NixOS 17.09, you will first need to upgrade
-              Nix by setting
-              <literal>nix.package = pkgs.nixStable2;</literal> and run
-              <literal>nixos-rebuild switch</literal> as the
-              <literal>root</literal> user.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              For users of a daemon-less Nix installation on Linux or
-              macOS, you can upgrade Nix by running
-              <literal>curl -L https://nixos.org/nix/install | sh</literal>,
-              or prior to doing a channel update, running
-              <literal>nix-env -iA nix</literal>. If you have already
-              run a channel update and Nix is no longer able to evaluate
-              Nixpkgs, the error message printed should provide adequate
-              directions for upgrading Nix.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              For users of the Nix daemon on macOS, you can upgrade Nix
-              by running
-              <literal>sudo -i sh -c 'nix-channel --update &amp;&amp; nix-env -iA nixpkgs.nix'; sudo launchctl stop org.nixos.nix-daemon; sudo launchctl start org.nixos.nix-daemon</literal>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>buildPythonPackage</literal> function now sets
-          <literal>strictDeps = true</literal> to help distinguish
-          between native and non-native dependencies in order to improve
-          cross-compilation compatibility. Note however that this may
-          break user expressions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>buildPythonPackage</literal> function now sets
-          <literal>LANG = C.UTF-8</literal> to enable Unicode support.
-          The <literal>glibcLocales</literal> package is no longer
-          needed as a build input.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Syncthing state and configuration data has been moved from
-          <literal>services.syncthing.dataDir</literal> to the newly
-          defined <literal>services.syncthing.configDir</literal>, which
-          default to
-          <literal>/var/lib/syncthing/.config/syncthing</literal>. This
-          change makes possible to share synced directories using ACLs
-          without Syncthing resetting the permission on every start.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>ntp</literal> module now has sane default
-          restrictions. If you're relying on the previous defaults,
-          which permitted all queries and commands from all
-          firewall-permitted sources, you can set
-          <literal>services.ntp.restrictDefault</literal> and
-          <literal>services.ntp.restrictSource</literal> to
-          <literal>[]</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Package <literal>rabbitmq_server</literal> is renamed to
-          <literal>rabbitmq-server</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>light</literal> module no longer uses setuid
-          binaries, but udev rules. As a consequence users of that
-          module have to belong to the <literal>video</literal> group in
-          order to use the executable (i.e.
-          <literal>users.users.yourusername.extraGroups = [&quot;video&quot;];</literal>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Buildbot now supports Python 3 and its packages have been
-          moved to <literal>pythonPackages</literal>. The options
-          <literal>services.buildbot-master.package</literal> and
-          <literal>services.buildbot-worker.package</literal> can be
-          used to select the Python 2 or 3 version of the package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Options
-          <literal>services.znc.confOptions.networks.name.userName</literal>
-          and
-          <literal>services.znc.confOptions.networks.name.modulePackages</literal>
-          were removed. They were never used for anything and can
-          therefore safely be removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Package <literal>wasm</literal> has been renamed
-          <literal>proglodyte-wasm</literal>. The package
-          <literal>wasm</literal> will be pointed to
-          <literal>ocamlPackages.wasm</literal> in 19.09, so make sure
-          to update your configuration if you want to keep
-          <literal>proglodyte-wasm</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When the <literal>nixpkgs.pkgs</literal> option is set, NixOS
-          will no longer ignore the <literal>nixpkgs.overlays</literal>
-          option. The old behavior can be recovered by setting
-          <literal>nixpkgs.overlays = lib.mkForce [];</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          OpenSMTPD has been upgraded to version 6.4.0p1. This release
-          makes backwards-incompatible changes to the configuration file
-          format. See <literal>man smtpd.conf</literal> for more
-          information on the new file format.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The versioned <literal>postgresql</literal> have been renamed
-          to use underscore number seperators. For example,
-          <literal>postgresql96</literal> has been renamed to
-          <literal>postgresql_9_6</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Package <literal>consul-ui</literal> and passthrough
-          <literal>consul.ui</literal> have been removed. The package
-          <literal>consul</literal> now uses upstream releases that
-          vendor the UI into the binary. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/48714#issuecomment-433454834">#48714</link>
-          for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Slurm introduces the new option
-          <literal>services.slurm.stateSaveLocation</literal>, which is
-          now set to <literal>/var/spool/slurm</literal> by default
-          (instead of <literal>/var/spool</literal>). Make sure to move
-          all files to the new directory or to set the option
-          accordingly.
-        </para>
-        <para>
-          The slurmctld now runs as user <literal>slurm</literal>
-          instead of <literal>root</literal>. If you want to keep
-          slurmctld running as <literal>root</literal>, set
-          <literal>services.slurm.user = root</literal>.
-        </para>
-        <para>
-          The options <literal>services.slurm.nodeName</literal> and
-          <literal>services.slurm.partitionName</literal> are now sets
-          of strings to correctly reflect that fact that each of these
-          options can occour more than once in the configuration.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>solr</literal> package has been upgraded from
-          4.10.3 to 7.5.0 and has undergone some major changes. The
-          <literal>services.solr</literal> module has been updated to
-          reflect these changes. Please review
-          http://lucene.apache.org/solr/ carefully before upgrading.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Package <literal>ckb</literal> is renamed to
-          <literal>ckb-next</literal>, and options
-          <literal>hardware.ckb.*</literal> are renamed to
-          <literal>hardware.ckb-next.*</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.xserver.displayManager.job.logToFile</literal>
-          which was previously set to <literal>true</literal> when using
-          the display managers <literal>lightdm</literal>,
-          <literal>sddm</literal> or <literal>xpra</literal> has been
-          reset to the default value (<literal>false</literal>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Network interface indiscriminate NixOS firewall options
-          (<literal>networking.firewall.allow*</literal>) are now
-          preserved when also setting interface specific rules such as
-          <literal>networking.firewall.interfaces.en0.allow*</literal>.
-          These rules continue to use the pseudo device
-          &quot;default&quot;
-          (<literal>networking.firewall.interfaces.default.*</literal>),
-          and assigning to this pseudo device will override the
-          (<literal>networking.firewall.allow*</literal>) options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nscd</literal> service now disables all caching
-          of <literal>passwd</literal> and <literal>group</literal>
-          databases by default. This was interferring with the correct
-          functioning of the <literal>libnss_systemd.so</literal> module
-          which is used by <literal>systemd</literal> to manage uids and
-          usernames in the presence of <literal>DynamicUser=</literal>
-          in systemd services. This was already the default behaviour in
-          presence of <literal>services.sssd.enable = true</literal>
-          because nscd caching would interfere with
-          <literal>sssd</literal> in unpredictable ways as well. Because
-          we're using nscd not for caching, but for convincing glibc to
-          find NSS modules in the nix store instead of an absolute path,
-          we have decided to disable caching globally now, as it's
-          usually not the behaviour the user wants and can lead to
-          surprising behaviour. Furthermore, negative caching of host
-          lookups is also disabled now by default. This should fix the
-          issue of dns lookups failing in the presence of an unreliable
-          network.
-        </para>
-        <para>
-          If the old behaviour is desired, this can be restored by
-          setting the <literal>services.nscd.config</literal> option
-          with the desired caching parameters.
-        </para>
-        <programlisting language="bash">
-{
-  services.nscd.config =
-  ''
-  server-user             nscd
-  threads                 1
-  paranoia                no
-  debug-level             0
-
-  enable-cache            passwd          yes
-  positive-time-to-live   passwd          600
-  negative-time-to-live   passwd          20
-  suggested-size          passwd          211
-  check-files             passwd          yes
-  persistent              passwd          no
-  shared                  passwd          yes
-
-  enable-cache            group           yes
-  positive-time-to-live   group           3600
-  negative-time-to-live   group           60
-  suggested-size          group           211
-  check-files             group           yes
-  persistent              group           no
-  shared                  group           yes
-
-  enable-cache            hosts           yes
-  positive-time-to-live   hosts           600
-  negative-time-to-live   hosts           5
-  suggested-size          hosts           211
-  check-files             hosts           yes
-  persistent              hosts           no
-  shared                  hosts           yes
-  '';
-}
-</programlisting>
-        <para>
-          See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/50316">#50316</link>
-          for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GitLab Shell previously used the nix store paths for the
-          <literal>gitlab-shell</literal> command in its
-          <literal>authorized_keys</literal> file, which might stop
-          working after garbage collection. To circumvent that, we
-          regenerated that file on each startup. As
-          <literal>gitlab-shell</literal> has now been changed to use
-          <literal>/var/run/current-system/sw/bin/gitlab-shell</literal>,
-          this is not necessary anymore, but there might be leftover
-          lines with a nix store path. Regenerate the
-          <literal>authorized_keys</literal> file via
-          <literal>sudo -u git -H gitlab-rake gitlab:shell:setup</literal>
-          in that case.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pam_unix</literal> account module is now loaded
-          with its control field set to <literal>required</literal>
-          instead of <literal>sufficient</literal>, so that later PAM
-          account modules that might do more extensive checks are being
-          executed. Previously, the whole account module verification
-          was exited prematurely in case a nss module provided the
-          account name to <literal>pam_unix</literal>. The LDAP and SSSD
-          NixOS modules already add their NSS modules when enabled. In
-          case your setup breaks due to some later PAM account module
-          previosuly shadowed, or failing NSS lookups, please file a
-          bug. You can get back the old behaviour by manually setting
-          <literal>security.pam.services.&lt;name?&gt;.text</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pam_unix</literal> password module is now loaded
-          with its control field set to <literal>sufficient</literal>
-          instead of <literal>required</literal>, so that password
-          managed only by later PAM password modules are being executed.
-          Previously, for example, changing an LDAP account's password
-          through PAM was not possible: the whole password module
-          verification was exited prematurely by
-          <literal>pam_unix</literal>, preventing
-          <literal>pam_ldap</literal> to manage the password as it
-          should.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>fish</literal> has been upgraded to 3.0. It comes
-          with a number of improvements and backwards incompatible
-          changes. See the <literal>fish</literal>
-          <link xlink:href="https://github.com/fish-shell/fish-shell/releases/tag/3.0.0">release
-          notes</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The ibus-table input method has had a change in config format,
-          which causes all previous settings to be lost. See
-          <link xlink:href="https://github.com/mike-fabian/ibus-table/commit/f9195f877c5212fef0dfa446acb328c45ba5852b">this
-          commit message</link> for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS module system type <literal>types.optionSet</literal>
-          and <literal>lib.mkOption</literal> argument
-          <literal>options</literal> are deprecated. Use
-          <literal>types.submodule</literal> instead.
-          (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/54637">#54637</link>)
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>matrix-synapse</literal> has been updated to version
-          0.99. It will
-          <link xlink:href="https://github.com/matrix-org/synapse/pull/4509">no
-          longer generate a self-signed certificate on first
-          launch</link> and will be
-          <link xlink:href="https://matrix.org/blog/2019/02/05/synapse-0-99-0/">the
-          last version to accept self-signed certificates</link>. As
-          such, it is now recommended to use a proper certificate
-          verified by a root CA (for example Let's Encrypt). The new
-          <link linkend="module-services-matrix">manual chapter on
-          Matrix</link> contains a working example of using nginx as a
-          reverse proxy in front of <literal>matrix-synapse</literal>,
-          using Let's Encrypt certificates.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>mailutils</literal> now works by default when
-          <literal>sendmail</literal> is not in a setuid wrapper. As a
-          consequence, the <literal>sendmailPath</literal> argument,
-          having lost its main use, has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>graylog</literal> has been upgraded from version 2.*
-          to 3.*. Some setups making use of extraConfig (especially
-          those exposing Graylog via reverse proxies) need to be updated
-          as upstream removed/replaced some settings. See
-          <link xlink:href="http://docs.graylog.org/en/3.0/pages/upgrade/graylog-3.0.html#simplified-http-interface-configuration">Upgrading
-          Graylog</link> for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>users.ldap.bind.password</literal> was
-          renamed to <literal>users.ldap.bind.passwordFile</literal>,
-          and needs to be readable by the <literal>nslcd</literal> user.
-          Same applies to the new
-          <literal>users.ldap.daemon.rootpwmodpwFile</literal> option.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nodejs-6_x</literal> is end-of-life.
-          <literal>nodejs-6_x</literal>,
-          <literal>nodejs-slim-6_x</literal> and
-          <literal>nodePackages_6_x</literal> are removed.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-19.03-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The <literal>services.matomo</literal> module gained the
-          option <literal>services.matomo.package</literal> which
-          determines the used Matomo version.
-        </para>
-        <para>
-          The Matomo module now also comes with the systemd service
-          <literal>matomo-archive-processing.service</literal> and a
-          timer that automatically triggers archive processing every
-          hour. This means that you can safely
-          <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">
-          disable browser triggers for Matomo archiving </link> at
-          <literal>Administration &gt; System &gt; General Settings</literal>.
-        </para>
-        <para>
-          Additionally, you can enable to
-          <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">
-          delete old visitor logs </link> at
-          <literal>Administration &gt; System &gt; Privacy</literal>,
-          but make sure that you run
-          <literal>systemctl start matomo-archive-processing.service</literal>
-          at least once without errors if you have already collected
-          data before, so that the reports get archived before the
-          source data gets deleted.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>composableDerivation</literal> along with supporting
-          library functions has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The deprecated <literal>truecrypt</literal> package has been
-          removed and <literal>truecrypt</literal> attribute is now an
-          alias for <literal>veracrypt</literal>. VeraCrypt is
-          backward-compatible with TrueCrypt volumes. Note that
-          <literal>cryptsetup</literal> also supports loading TrueCrypt
-          volumes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Kubernetes DNS addons, kube-dns, has been replaced with
-          CoreDNS. This change is made in accordance with Kubernetes
-          making CoreDNS the official default starting from
-          <link xlink:href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.11.md#sig-cluster-lifecycle">Kubernetes
-          v1.11</link>. Please beware that upgrading DNS-addon on
-          existing clusters might induce minor downtime while the
-          DNS-addon terminates and re-initializes. Also note that the
-          DNS-service now runs with 2 pod replicas by default. The
-          desired number of replicas can be configured using:
-          <literal>services.kubernetes.addons.dns.replicas</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The quassel-webserver package and module was removed from
-          nixpkgs due to the lack of maintainers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The manual gained a <link linkend="module-services-matrix">
-          new chapter on self-hosting <literal>matrix-synapse</literal>
-          and <literal>riot-web</literal> </link>, the most prevalent
-          server and client implementations for the
-          <link xlink:href="https://matrix.org/">Matrix</link> federated
-          communication network.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The astah-community package was removed from nixpkgs due to it
-          being discontinued and the downloads not being available
-          anymore.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The httpd service now saves log files with a .log file
-          extension by default for easier integration with the logrotate
-          service.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The owncloud server packages and httpd subservice module were
-          removed from nixpkgs due to the lack of maintainers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          It is possible now to uze ZRAM devices as general purpose
-          ephemeral block devices, not only as swap. Using more than 1
-          device as ZRAM swap is no longer recommended, but is still
-          possible by setting <literal>zramSwap.swapDevices</literal>
-          explicitly.
-        </para>
-        <para>
-          ZRAM algorithm can be changed now.
-        </para>
-        <para>
-          Changes to ZRAM algorithm are applied during
-          <literal>nixos-rebuild switch</literal>, so make sure you have
-          enough swap space on disk to survive ZRAM device rebuild.
-          Alternatively, use
-          <literal>nixos-rebuild boot; reboot</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Flat volumes are now disabled by default in
-          <literal>hardware.pulseaudio</literal>. This has been done to
-          prevent applications, which are unaware of this feature,
-          setting their volumes to 100% on startup causing harm to your
-          audio hardware and potentially your ears.
-        </para>
-        <note>
-          <para>
-            With this change application specific volumes are relative
-            to the master volume which can be adjusted independently,
-            whereas before they were absolute; meaning that in effect,
-            it scaled the device-volume with the volume of the loudest
-            application.
-          </para>
-        </note>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="https://github.com/DanielAdolfsson/ndppd"><literal>ndppd</literal></link>
-          module now supports
-          <link xlink:href="options.html#opt-services.ndppd.enable">all
-          config options</link> provided by the current upstream version
-          as service options. Additionally the <literal>ndppd</literal>
-          package doesn't contain the systemd unit configuration from
-          upstream anymore, the unit is completely configured by the
-          NixOS module now.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          New installs of NixOS will default to the Redmine 4.x series
-          unless otherwise specified in
-          <literal>services.redmine.package</literal> while existing
-          installs of NixOS will default to the Redmine 3.x series.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.grafana.enable">Grafana
-          module</link> now supports declarative
-          <link xlink:href="http://docs.grafana.org/administration/provisioning/">datasource
-          and dashboard</link> provisioning.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The use of insecure ports on kubernetes has been deprecated.
-          Thus options:
-          <literal>services.kubernetes.apiserver.port</literal> and
-          <literal>services.kubernetes.controllerManager.port</literal>
-          has been renamed to <literal>.insecurePort</literal>, and
-          default of both options has changed to 0 (disabled).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Note that the default value of
-          <literal>services.kubernetes.apiserver.bindAddress</literal>
-          has changed from 127.0.0.1 to 0.0.0.0, allowing the apiserver
-          to be accessible from outside the master node itself. If the
-          apiserver insecurePort is enabled, it is strongly recommended
-          to only bind on the loopback interface. See:
-          <literal>services.kubernetes.apiserver.insecurebindAddress</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.kubernetes.apiserver.allowPrivileged</literal>
-          and
-          <literal>services.kubernetes.kubelet.allowPrivileged</literal>
-          now defaults to false. Disallowing privileged containers on
-          the cluster.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The kubernetes module does no longer add the kubernetes
-          package to <literal>environment.systemPackages</literal>
-          implicitly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>intel</literal> driver has been removed from the
-          default list of
-          <link xlink:href="options.html#opt-services.xserver.videoDrivers">X.org
-          video drivers</link>. The <literal>modesetting</literal>
-          driver should take over automatically, it is better maintained
-          upstream and has less problems with advanced X11 features.
-          This can lead to a change in the output names used by
-          <literal>xrandr</literal>. Some performance regressions on
-          some GPU models might happen. Some OpenCL and VA-API
-          applications might also break (Beignet seems to provide OpenCL
-          support with <literal>modesetting</literal> driver, too).
-          Kernel mode setting API does not support backlight control, so
-          <literal>xbacklight</literal> tool will not work; backlight
-          level can be controlled directly via <literal>/sys/</literal>
-          or with <literal>brightnessctl</literal>. Users who need this
-          functionality more than multi-output XRandR are advised to add
-          `intel` to `videoDrivers` and report an issue (or provide
-          additional details in an existing one)
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Openmpi has been updated to version 4.0.0, which removes some
-          deprecated MPI-1 symbols. This may break some older
-          applications that still rely on those symbols. An upgrade
-          guide can be found
-          <link xlink:href="https://www.open-mpi.org/faq/?category=mpi-removed">here</link>.
-        </para>
-        <para>
-          The nginx package now relies on OpenSSL 1.1 and supports TLS
-          1.3 by default. You can set the protocols used by the nginx
-          service using
-          <link xlink:href="options.html#opt-services.nginx.sslProtocols">services.nginx.sslProtocols</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A new subcommand <literal>nixos-rebuild edit</literal> was
-          added.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml
deleted file mode 100644
index 83cd649f4ea0..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml
+++ /dev/null
@@ -1,1197 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-19.09">
-  <title>Release 19.09 (<quote>Loris</quote>, 2019/10/09)</title>
-  <section xml:id="sec-release-19.09-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          End of support is planned for end of April 2020, handing over
-          to 20.03.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nix has been updated to 2.3; see its
-          <link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-2.3">release
-          notes</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Core version changes:
-        </para>
-        <para>
-          systemd: 239 -&gt; 243
-        </para>
-        <para>
-          gcc: 7 -&gt; 8
-        </para>
-        <para>
-          glibc: 2.27 (unchanged)
-        </para>
-        <para>
-          linux: 4.19 LTS (unchanged)
-        </para>
-        <para>
-          openssl: 1.0 -&gt; 1.1
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Desktop version changes:
-        </para>
-        <para>
-          plasma5: 5.14 -&gt; 5.16
-        </para>
-        <para>
-          gnome3: 3.30 -&gt; 3.32
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP now defaults to PHP 7.3, updated from 7.2.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP 7.1 is no longer supported due to upstream not supporting
-          this version for the entire lifecycle of the 19.09 release.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The binfmt module is now easier to use. Additional systems can
-          be added through
-          <literal>boot.binfmt.emulatedSystems</literal>. For instance,
-          <literal>boot.binfmt.emulatedSystems = [ &quot;wasm32-wasi&quot; &quot;x86_64-windows&quot; &quot;aarch64-linux&quot; ];</literal>
-          will set up binfmt interpreters for each of those listed
-          systems.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The installer now uses a less privileged
-          <literal>nixos</literal> user whereas before we logged in as
-          root. To gain root privileges use <literal>sudo -i</literal>
-          without a password.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          We've updated to Xfce 4.14, which brings a new module
-          <literal>services.xserver.desktopManager.xfce4-14</literal>.
-          If you'd like to upgrade, please switch from the
-          <literal>services.xserver.desktopManager.xfce</literal> module
-          as it will be deprecated in a future release. They're
-          incompatibilities with the current Xfce module; it doesn't
-          support <literal>thunarPlugins</literal> and it isn't
-          recommended to use
-          <literal>services.xserver.desktopManager.xfce</literal> and
-          <literal>services.xserver.desktopManager.xfce4-14</literal>
-          simultaneously or to downgrade from Xfce 4.14 after upgrading.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The GNOME 3 desktop manager module sports an interface to
-          enable/disable core services, applications, and optional GNOME
-          packages like games.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>services.gnome3.core-os-services.enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.gnome3.core-shell.enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.gnome3.core-utilities.enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.gnome3.games.enable</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          With these options we hope to give users finer grained control
-          over their systems. Prior to this change you'd either have to
-          manually disable options or use
-          <literal>environment.gnome3.excludePackages</literal> which
-          only excluded the optional applications.
-          <literal>environment.gnome3.excludePackages</literal> is now
-          unguarded, it can exclude any package installed with
-          <literal>environment.systemPackages</literal> in the GNOME 3
-          module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Orthogonal to the previous changes to the GNOME 3 desktop
-          manager module, we've updated all default services and
-          applications to match as close as possible to a default
-          reference GNOME 3 experience.
-        </para>
-        <para>
-          <emphasis role="strong">The following changes were enacted in
-          <literal>services.gnome3.core-utilities.enable</literal></emphasis>
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>accerciser</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>dconf-editor</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>evolution</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gnome-documents</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gnome-nettool</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gnome-power-manager</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gnome-todo</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gnome-tweaks</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gnome-usage</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>gucharmap</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>nautilus-sendto</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>vinagre</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>cheese</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>geary</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          <emphasis role="strong">The following changes were enacted in
-          <literal>services.gnome3.core-shell.enable</literal></emphasis>
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>gnome-color-manager</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>orca</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.avahi.enable</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-19.09-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>./programs/dwm-status.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The new <literal>hardware.printers</literal> module allows to
-          declaratively configure CUPS printers via the
-          <literal>ensurePrinters</literal> and
-          <literal>ensureDefaultPrinter</literal> options.
-          <literal>ensurePrinters</literal> will never delete existing
-          printers, but will make sure that the given printers are
-          configured as declared.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new
-          <link xlink:href="options.html#opt-services.system-config-printer.enable">services.system-config-printer.enable</link>
-          and
-          <link xlink:href="options.html#opt-programs.system-config-printer.enable">programs.system-config-printer.enable</link>
-          module for the program of the same name. If you previously had
-          <literal>system-config-printer</literal> enabled through some
-          other means you should migrate to using one of these modules.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>services.xserver.desktopManager.plasma5</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.xserver.desktopManager.gnome3</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.xserver.desktopManager.pantheon</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.xserver.desktopManager.mate</literal>
-              Note Mate uses
-              <literal>programs.system-config-printer</literal> as it
-              doesn't use it as a service, but its graphical interface
-              directly.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-services.blueman.enable">services.blueman.enable</link>
-          has been added. If you previously had blueman installed via
-          <literal>environment.systemPackages</literal> please migrate
-          to using the NixOS module, as this would result in an
-          insufficiently configured blueman.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-19.09-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Buildbot no longer supports Python 2, as support was dropped
-          upstream in version 2.0.0. Configurations may need to be
-          modified to make them compatible with Python 3.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PostgreSQL now uses <literal>/run/postgresql</literal> as its
-          socket directory instead of <literal>/tmp</literal>. So if you
-          run an application like eg. Nextcloud, where you need to use
-          the Unix socket path as the database host name, you need to
-          change it accordingly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PostgreSQL 9.4 is scheduled EOL during the 19.09 life cycle
-          and has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The options
-          <literal>services.prometheus.alertmanager.user</literal> and
-          <literal>services.prometheus.alertmanager.group</literal> have
-          been removed because the alertmanager service is now using
-          systemd's
-          <link xlink:href="http://0pointer.net/blog/dynamic-users-with-systemd.html">
-          DynamicUser mechanism</link> which obviates these options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The NetworkManager systemd unit was renamed back from
-          network-manager.service to NetworkManager.service for better
-          compatibility with other applications expecting this name. The
-          same applies to ModemManager where modem-manager.service is
-          now called ModemManager.service again.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.nzbget.configFile</literal> and
-          <literal>services.nzbget.openFirewall</literal> options were
-          removed as they are managed internally by the nzbget. The
-          <literal>services.nzbget.dataDir</literal> option hadn't
-          actually been used by the module for some time and so was
-          removed as cleanup.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.mysql.pidDir</literal> option was
-          removed, as it was only used by the wordpress apache-httpd
-          service to wait for mysql to have started up. This can be
-          accomplished by either describing a dependency on
-          mysql.service (preferred) or waiting for the (hardcoded)
-          <literal>/run/mysqld/mysql.sock</literal> file to appear.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.emby.enable</literal> module has been
-          removed, see <literal>services.jellyfin.enable</literal>
-          instead for a free software fork of Emby. See the Jellyfin
-          documentation:
-          <link xlink:href="https://jellyfin.readthedocs.io/en/latest/administrator-docs/migrate-from-emby/">
-          Migrating from Emby to Jellyfin </link>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          IPv6 Privacy Extensions are now enabled by default for
-          undeclared interfaces. The previous behaviour was quite
-          misleading — even though the default value for
-          <literal>networking.interfaces.*.preferTempAddress</literal>
-          was <literal>true</literal>, undeclared interfaces would not
-          prefer temporary addresses. Now, interfaces not mentioned in
-          the config will prefer temporary addresses. EUI64 addresses
-          can still be set as preferred by explicitly setting the option
-          to <literal>false</literal> for the interface in question.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Since Bittorrent Sync was superseded by Resilio Sync in 2016,
-          the <literal>bittorrentSync</literal>,
-          <literal>bittorrentSync14</literal>, and
-          <literal>bittorrentSync16</literal> packages have been removed
-          in favor of <literal>resilio-sync</literal>.
-        </para>
-        <para>
-          The corresponding module, <literal>services.btsync</literal>
-          has been replaced by the <literal>services.resilio</literal>
-          module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The httpd service no longer attempts to start the postgresql
-          service. If you have come to depend on this behaviour then you
-          can preserve the behavior with the following configuration:
-          <literal>systemd.services.httpd.after = [ &quot;postgresql.service&quot; ];</literal>
-        </para>
-        <para>
-          The option <literal>services.httpd.extraSubservices</literal>
-          has been marked as deprecated. You may still use this feature,
-          but it will be removed in a future release of NixOS. You are
-          encouraged to convert any httpd subservices you may have
-          written to a full NixOS module.
-        </para>
-        <para>
-          Most of the httpd subservices packaged with NixOS have been
-          replaced with full NixOS modules including LimeSurvey,
-          WordPress, and Zabbix. These modules can be enabled using the
-          <literal>services.limesurvey.enable</literal>,
-          <literal>services.mediawiki.enable</literal>,
-          <literal>services.wordpress.enable</literal>, and
-          <literal>services.zabbixWeb.enable</literal> options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>systemd.network.networks.&lt;name&gt;.routes.*.routeConfig.GatewayOnlink</literal>
-          was renamed to
-          <literal>systemd.network.networks.&lt;name&gt;.routes.*.routeConfig.GatewayOnLink</literal>
-          (capital <literal>L</literal>). This follows
-          <link xlink:href="https://github.com/systemd/systemd/commit/9cb8c5593443d24c19e40bfd4fc06d672f8c554c">
-          upstreams renaming </link> of the setting.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          As of this release the NixOps feature
-          <literal>autoLuks</literal> is deprecated. It no longer works
-          with our systemd version without manual intervention.
-        </para>
-        <para>
-          Whenever the usage of the module is detected the evaluation
-          will fail with a message explaining why and how to deal with
-          the situation.
-        </para>
-        <para>
-          A new knob named
-          <literal>nixops.enableDeprecatedAutoLuks</literal> has been
-          introduced to disable the eval failure and to acknowledge the
-          notice was received and read. If you plan on using the feature
-          please note that it might break with subsequent updates.
-        </para>
-        <para>
-          Make sure you set the <literal>_netdev</literal> option for
-          each of the file systems referring to block devices provided
-          by the autoLuks module. Not doing this might render the system
-          in a state where it doesn't boot anymore.
-        </para>
-        <para>
-          If you are actively using the <literal>autoLuks</literal>
-          module please let us know in
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/62211">issue
-          #62211</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The setopt declarations will be evaluated at the end of
-          <literal>/etc/zshrc</literal>, so any code in
-          <link xlink:href="options.html#opt-programs.zsh.interactiveShellInit">programs.zsh.interactiveShellInit</link>,
-          <link xlink:href="options.html#opt-programs.zsh.loginShellInit">programs.zsh.loginShellInit</link>
-          and
-          <link xlink:href="options.html#opt-programs.zsh.promptInit">programs.zsh.promptInit</link>
-          may break if it relies on those options being set.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>prometheus-nginx-exporter</literal> package now
-          uses the offical exporter provided by NGINX Inc. Its metrics
-          are differently structured and are incompatible to the old
-          ones. For information about the metrics, have a look at the
-          <link xlink:href="https://github.com/nginxinc/nginx-prometheus-exporter">official
-          repo</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>shibboleth-sp</literal> package has been updated
-          to version 3. It is largely backward compatible, for further
-          information refer to the
-          <link xlink:href="https://wiki.shibboleth.net/confluence/display/SP3/ReleaseNotes">release
-          notes</link> and
-          <link xlink:href="https://wiki.shibboleth.net/confluence/display/SP3/UpgradingFromV2">upgrade
-          guide</link>.
-        </para>
-        <para>
-          Nodejs 8 is scheduled EOL under the lifetime of 19.09 and has
-          been dropped.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          By default, prometheus exporters are now run with
-          <literal>DynamicUser</literal> enabled. Exporters that need a
-          real user, now run under a seperate user and group which
-          follow the pattern
-          <literal>&lt;exporter-name&gt;-exporter</literal>, instead of
-          the previous default <literal>nobody</literal> and
-          <literal>nogroup</literal>. Only some exporters are affected
-          by the latter, namely the exporters
-          <literal>dovecot</literal>, <literal>node</literal>,
-          <literal>postfix</literal> and <literal>varnish</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>ibus-qt</literal> package is not installed by
-          default anymore when
-          <link xlink:href="options.html#opt-i18n.inputMethod.enabled">i18n.inputMethod.enabled</link>
-          is set to <literal>ibus</literal>. If IBus support in Qt 4.x
-          applications is required, add the <literal>ibus-qt</literal>
-          package to your
-          <link xlink:href="options.html#opt-environment.systemPackages">environment.systemPackages</link>
-          manually.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The CUPS Printing service now uses socket-based activation by
-          default, only starting when needed. The previous behavior can
-          be restored by setting
-          <literal>services.cups.startWhenNeeded</literal> to
-          <literal>false</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.systemhealth</literal> module has been
-          removed from nixpkgs due to lack of maintainer.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.mantisbt</literal> module has been
-          removed from nixpkgs due to lack of maintainer.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Squid 3 has been removed and the <literal>squid</literal>
-          derivation now refers to Squid 4.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.pdns-recursor.extraConfig</literal>
-          option has been replaced by
-          <literal>services.pdns-recursor.settings</literal>. The new
-          option allows setting extra configuration while being better
-          type-checked and mergeable.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          No service depends on <literal>keys.target</literal> anymore
-          which is a systemd target that indicates if all
-          <link xlink:href="https://nixos.org/nixops/manual/#idm140737322342384">NixOps
-          keys</link> were successfully uploaded. Instead,
-          <literal>&lt;key-name&gt;-key.service</literal> should be used
-          to define a dependency of a key in a service. The full issue
-          behind the <literal>keys.target</literal> dependency is
-          described at
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/67265">NixOS/nixpkgs#67265</link>.
-        </para>
-        <para>
-          The following services are affected by this:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.dovecot2.enable"><literal>services.dovecot2</literal></link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.nsd.enable"><literal>services.nsd</literal></link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.softether.enable"><literal>services.softether</literal></link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.strongswan.enable"><literal>services.strongswan</literal></link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.strongswan-swanctl.enable"><literal>services.strongswan-swanctl</literal></link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.httpd.enable"><literal>services.httpd</literal></link>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.acme.directory</literal> option has been
-          replaced by a read-only
-          <literal>security.acme.certs.&lt;cert&gt;.directory</literal>
-          option for each certificate you define. This will be a
-          subdirectory of <literal>/var/lib/acme</literal>. You can use
-          this read-only option to figure out where the certificates are
-          stored for a specific certificate. For example, the
-          <literal>services.nginx.virtualhosts.&lt;name&gt;.enableACME</literal>
-          option will use this directory option to find the certs for
-          the virtual host.
-        </para>
-        <para>
-          <literal>security.acme.preDelay</literal> and
-          <literal>security.acme.activationDelay</literal> options have
-          been removed. To execute a service before certificates are
-          provisioned or renewed add a
-          <literal>RequiredBy=acme-${cert}.service</literal> to any
-          service.
-        </para>
-        <para>
-          Furthermore, the acme module will not automatically add a
-          dependency on <literal>lighttpd.service</literal> anymore. If
-          you are using certficates provided by letsencrypt for
-          lighttpd, then you should depend on the certificate service
-          <literal>acme-${cert}.service&gt;</literal> manually.
-        </para>
-        <para>
-          For nginx, the dependencies are still automatically managed
-          when
-          <literal>services.nginx.virtualhosts.&lt;name&gt;.enableACME</literal>
-          is enabled just like before. What changed is that nginx now
-          directly depends on the specific certificates that it needs,
-          instead of depending on the catch-all
-          <literal>acme-certificates.target</literal>. This target unit
-          was also removed from the codebase. This will mean nginx will
-          no longer depend on certificates it isn't explicitly managing
-          and fixes a bug with certificate renewal ordering racing with
-          nginx restarting which could lead to nginx getting in a broken
-          state as described at
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/60180">NixOS/nixpkgs#60180</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The old deprecated <literal>emacs</literal> package sets have
-          been dropped. What used to be called
-          <literal>emacsPackagesNg</literal> is now simply called
-          <literal>emacsPackages</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.xserver.desktopManager.xterm</literal> is
-          now disabled by default if <literal>stateVersion</literal> is
-          19.09 or higher. Previously the xterm desktopManager was
-          enabled when xserver was enabled, but it isn't useful for all
-          people so it didn't make sense to have any desktopManager
-          enabled default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The WeeChat plugin
-          <literal>pkgs.weechatScripts.weechat-xmpp</literal> has been
-          removed as it doesn't receive any updates from upstream and
-          depends on outdated Python2-based modules.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Old unsupported versions (<literal>logstash5</literal>,
-          <literal>kibana5</literal>, <literal>filebeat5</literal>,
-          <literal>heartbeat5</literal>, <literal>metricbeat5</literal>,
-          <literal>packetbeat5</literal>) of the ELK-stack and Elastic
-          beats have been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          For NixOS 19.03, both Prometheus 1 and 2 were available to
-          allow for a seamless transition from version 1 to 2 with
-          existing setups. Because Prometheus 1 is no longer developed,
-          it was removed. Prometheus 2 is now configured with
-          <literal>services.prometheus</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Citrix Receiver (<literal>citrix_receiver</literal>) has been
-          dropped in favor of Citrix Workspace
-          (<literal>citrix_workspace</literal>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.gitlab</literal> module has had its
-          literal secret options
-          (<literal>services.gitlab.smtp.password</literal>,
-          <literal>services.gitlab.databasePassword</literal>,
-          <literal>services.gitlab.initialRootPassword</literal>,
-          <literal>services.gitlab.secrets.secret</literal>,
-          <literal>services.gitlab.secrets.db</literal>,
-          <literal>services.gitlab.secrets.otp</literal> and
-          <literal>services.gitlab.secrets.jws</literal>) replaced by
-          file-based versions
-          (<literal>services.gitlab.smtp.passwordFile</literal>,
-          <literal>services.gitlab.databasePasswordFile</literal>,
-          <literal>services.gitlab.initialRootPasswordFile</literal>,
-          <literal>services.gitlab.secrets.secretFile</literal>,
-          <literal>services.gitlab.secrets.dbFile</literal>,
-          <literal>services.gitlab.secrets.otpFile</literal> and
-          <literal>services.gitlab.secrets.jwsFile</literal>). This was
-          done so that secrets aren't stored in the world-readable nix
-          store, but means that for each option you'll have to create a
-          file with the same exact string, add &quot;File&quot; to the
-          end of the option name, and change the definition to a string
-          pointing to the corresponding file; e.g.
-          <literal>services.gitlab.databasePassword = &quot;supersecurepassword&quot;</literal>
-          becomes
-          <literal>services.gitlab.databasePasswordFile = &quot;/path/to/secret_file&quot;</literal>
-          where the file <literal>secret_file</literal> contains the
-          string <literal>supersecurepassword</literal>.
-        </para>
-        <para>
-          The state path (<literal>services.gitlab.statePath</literal>)
-          now has the following restriction: no parent directory can be
-          owned by any other user than <literal>root</literal> or the
-          user specified in <literal>services.gitlab.user</literal>;
-          i.e. if <literal>services.gitlab.statePath</literal> is set to
-          <literal>/var/lib/gitlab/state</literal>,
-          <literal>gitlab</literal> and all parent directories must be
-          owned by either <literal>root</literal> or the user specified
-          in <literal>services.gitlab.user</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>networking.useDHCP</literal> option is
-          unsupported in combination with
-          <literal>networking.useNetworkd</literal> in anticipation of
-          defaulting to it. It has to be set to <literal>false</literal>
-          and enabled per interface with
-          <literal>networking.interfaces.&lt;name&gt;.useDHCP = true;</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Twitter client <literal>corebird</literal> has been
-          dropped as
-          <link xlink:href="https://www.patreon.com/posts/corebirds-future-18921328">it
-          is discontinued and does not work against the new Twitter
-          API</link>. Please use the fork <literal>cawbird</literal>
-          instead which has been adapted to the API changes and is still
-          maintained.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nodejs-11_x</literal> package has been removed as
-          it's EOLed by upstream.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Because of the systemd upgrade, systemd-timesyncd will no
-          longer work if <literal>system.stateVersion</literal> is not
-          set correctly. When upgrading from NixOS 19.03, please make
-          sure that <literal>system.stateVersion</literal> is set to
-          <literal>&quot;19.03&quot;</literal>, or lower if the
-          installation dates back to an earlier version of NixOS.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Due to the short lifetime of non-LTS kernel releases package
-          attributes like <literal>linux_5_1</literal>,
-          <literal>linux_5_2</literal> and <literal>linux_5_3</literal>
-          have been removed to discourage dependence on specific non-LTS
-          kernel versions in stable NixOS releases. Going forward,
-          versioned attributes like <literal>linux_4_9</literal> will
-          exist for LTS versions only. Please use
-          <literal>linux_latest</literal> or
-          <literal>linux_testing</literal> if you depend on non-LTS
-          releases. Keep in mind that <literal>linux_latest</literal>
-          and <literal>linux_testing</literal> will change versions
-          under the hood during the lifetime of a stable release and
-          might include breaking changes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Because of the systemd upgrade, some network interfaces might
-          change their name. For details see
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.net-naming-scheme.html#History">
-          upstream docs</link> or
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/71086">
-          our ticket</link>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-19.09-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The <literal>documentation</literal> module gained an option
-          named <literal>documentation.nixos.includeAllModules</literal>
-          which makes the generated configuration.nix 5 manual page
-          include all options from all NixOS modules included in a given
-          <literal>configuration.nix</literal> configuration file.
-          Currently, it is set to <literal>false</literal> by default as
-          enabling it frequently prevents evaluation. But the plan is to
-          eventually have it set to <literal>true</literal> by default.
-          Please set it to <literal>true</literal> now in your
-          <literal>configuration.nix</literal> and fix all the bugs it
-          uncovers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>vlc</literal> package gained support for
-          Chromecast streaming, enabled by default. TCP port 8010 must
-          be open for it to work, so something like
-          <literal>networking.firewall.allowedTCPPorts = [ 8010 ];</literal>
-          may be required in your configuration. Also consider enabling
-          <link xlink:href="https://nixos.wiki/wiki/Accelerated_Video_Playback">
-          Accelerated Video Playback</link> for better transcoding
-          performance.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The following changes apply if the
-          <literal>stateVersion</literal> is changed to 19.09 or higher.
-          For <literal>stateVersion = &quot;19.03&quot;</literal> or
-          lower the old behavior is preserved.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>solr.package</literal> defaults to
-              <literal>pkgs.solr_8</literal>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>hunspellDicts.fr-any</literal> dictionary now
-          ships with <literal>fr_FR.{aff,dic}</literal> which is linked
-          to <literal>fr-toutesvariantes.{aff,dic}</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>mysql</literal> service now runs as
-          <literal>mysql</literal> user. Previously, systemd did execute
-          it as root, and mysql dropped privileges itself. This includes
-          <literal>ExecStartPre=</literal> and
-          <literal>ExecStartPost=</literal> phases. To accomplish that,
-          runtime and data directory setup was delegated to
-          RuntimeDirectory and tmpfiles.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          With the upgrade to systemd version 242 the
-          <literal>systemd-timesyncd</literal> service is no longer
-          using <literal>DynamicUser=yes</literal>. In order for the
-          upgrade to work we rely on an activation script to move the
-          state from the old to the new directory. The older directory
-          (prior <literal>19.09</literal>) was
-          <literal>/var/lib/private/systemd/timesync</literal>.
-        </para>
-        <para>
-          As long as the <literal>system.config.stateVersion</literal>
-          is below <literal>19.09</literal> the state folder will
-          migrated to its proper location
-          (<literal>/var/lib/systemd/timesync</literal>), if required.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The package <literal>avahi</literal> is now built to look up
-          service definitions from
-          <literal>/etc/avahi/services</literal> instead of its output
-          directory in the nix store. Accordingly the module
-          <literal>avahi</literal> now supports custom service
-          definitions via
-          <literal>services.avahi.extraServiceFiles</literal>, which are
-          then placed in the aforementioned directory. See
-          avahi.service5 for more information on custom service
-          definitions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Since version 0.1.19, <literal>cargo-vendor</literal> honors
-          package includes that are specified in the
-          <literal>Cargo.toml</literal> file of Rust crates.
-          <literal>rustPlatform.buildRustPackage</literal> uses
-          <literal>cargo-vendor</literal> to collect and build dependent
-          crates. Since this change in <literal>cargo-vendor</literal>
-          changes the set of vendored files for most Rust packages, the
-          hash that use used to verify the dependencies,
-          <literal>cargoSha256</literal>, also changes.
-        </para>
-        <para>
-          The <literal>cargoSha256</literal> hashes of all in-tree
-          derivations that use <literal>buildRustPackage</literal> have
-          been updated to reflect this change. However, third-party
-          derivations that use <literal>buildRustPackage</literal> may
-          have to be updated as well.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>consul</literal> package was upgraded past
-          version <literal>1.5</literal>, so its deprecated legacy UI is
-          no longer available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default resample-method for PulseAudio has been changed
-          from the upstream default <literal>speex-float-1</literal> to
-          <literal>speex-float-5</literal>. Be aware that low-powered
-          ARM-based and MIPS-based boards will struggle with this so
-          you'll need to set
-          <literal>hardware.pulseaudio.daemon.config.resample-method</literal>
-          back to <literal>speex-float-1</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>phabricator</literal> package and associated
-          <literal>httpd.extraSubservice</literal>, as well as the
-          <literal>phd</literal> service have been removed from nixpkgs
-          due to lack of maintainer.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>mercurial</literal>
-          <literal>httpd.extraSubservice</literal> has been removed from
-          nixpkgs due to lack of maintainer.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>trac</literal>
-          <literal>httpd.extraSubservice</literal> has been removed from
-          nixpkgs because it was unmaintained.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>foswiki</literal> package and associated
-          <literal>httpd.extraSubservice</literal> have been removed
-          from nixpkgs due to lack of maintainer.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>tomcat-connector</literal>
-          <literal>httpd.extraSubservice</literal> has been removed from
-          nixpkgs.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          It's now possible to change configuration in
-          <link xlink:href="options.html#opt-services.nextcloud.enable">services.nextcloud</link>
-          after the initial deploy since all config parameters are
-          persisted in an additional config file generated by the
-          module. Previously core configuration like database parameters
-          were set using their imperative installer after creating
-          <literal>/var/lib/nextcloud</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There exists now <literal>lib.forEach</literal>, which is like
-          <literal>map</literal>, but with arguments flipped. When
-          mapping function body spans many lines (or has nested
-          <literal>map</literal>s), it is often hard to follow which
-          list is modified.
-        </para>
-        <para>
-          Previous solution to this problem was either to use
-          <literal>lib.flip map</literal> idiom or extract that
-          anonymous mapping function to a named one. Both can still be
-          used but <literal>lib.forEach</literal> is preferred over
-          <literal>lib.flip map</literal>.
-        </para>
-        <para>
-          The <literal>/etc/sysctl.d/nixos.conf</literal> file
-          containing all the options set via
-          <link xlink:href="options.html#opt-boot.kernel.sysctl">boot.kernel.sysctl</link>
-          was moved to <literal>/etc/sysctl.d/60-nixos.conf</literal>,
-          as sysctl.d5 recommends prefixing all filenames in
-          <literal>/etc/sysctl.d</literal> with a two-digit number and a
-          dash to simplify the ordering of the files.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          We now install the sysctl snippets shipped with systemd.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              Loose reverse path filtering
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Source route filtering
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>fq_codel</literal> as a packet scheduler (this
-              helps to fight bufferbloat)
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          This also configures the kernel to pass core dumps to
-          <literal>systemd-coredump</literal>, and restricts the SysRq
-          key combinations to the sync command only. These sysctl
-          snippets can be found in
-          <literal>/etc/sysctl.d/50-*.conf</literal>, and overridden via
-          <link xlink:href="options.html#opt-boot.kernel.sysctl">boot.kernel.sysctl</link>
-          (which will place the parameters in
-          <literal>/etc/sysctl.d/60-nixos.conf</literal>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Core dumps are now processed by
-          <literal>systemd-coredump</literal> by default.
-          <literal>systemd-coredump</literal> behaviour can still be
-          modified via <literal>systemd.coredump.extraConfig</literal>.
-          To stick to the old behaviour (having the kernel dump to a
-          file called <literal>core</literal> in the working directory),
-          without piping it through <literal>systemd-coredump</literal>,
-          set <literal>systemd.coredump.enable</literal> to
-          <literal>false</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>systemd.packages</literal> option now also supports
-          generators and shutdown scripts. Old
-          <literal>systemd.generator-packages</literal> option has been
-          removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>rmilter</literal> package was removed with
-          associated module and options due deprecation by upstream
-          developer. Use <literal>rspamd</literal> in proxy mode
-          instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          systemd cgroup accounting via the
-          <link xlink:href="options.html#opt-systemd.enableCgroupAccounting">systemd.enableCgroupAccounting</link>
-          option is now enabled by default. It now also enables the more
-          recent Block IO and IP accounting features.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          We no longer enable custom font rendering settings with
-          <literal>fonts.fontconfig.penultimate.enable</literal> by
-          default. The defaults from fontconfig are sufficient.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>crashplan</literal> package and the
-          <literal>crashplan</literal> service have been removed from
-          nixpkgs due to crashplan shutting down the service, while the
-          <literal>crashplansb</literal> package and
-          <literal>crashplan-small-business</literal> service have been
-          removed from nixpkgs due to lack of maintainer.
-        </para>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.redis.enable">redis
-          module</link> was hardcoded to use the
-          <literal>redis</literal> user, <literal>/run/redis</literal>
-          as runtime directory and <literal>/var/lib/redis</literal> as
-          state directory. Note that the NixOS module for Redis now
-          disables kernel support for Transparent Huge Pages (THP),
-          because this features causes major performance problems for
-          Redis, e.g. (https://redis.io/topics/latency).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Using <literal>fonts.enableDefaultFonts</literal> adds a
-          default emoji font <literal>noto-fonts-emoji</literal>.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>services.xserver.enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>programs.sway.enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>programs.way-cooler.enable</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.xrdp.enable</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>altcoins</literal> categorization of packages has
-          been removed. You now access these packages at the top level,
-          ie. <literal>nix-shell -p dogecoin</literal> instead of
-          <literal>nix-shell -p altcoins.dogecoin</literal>, etc.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Ceph has been upgraded to v14.2.1. See the
-          <link xlink:href="https://ceph.com/releases/v14-2-0-nautilus-released/">release
-          notes</link> for details. The mgr dashboard as well as osds
-          backed by loop-devices is no longer explicitly supported by
-          the package and module. Note: There's been some issues with
-          python-cherrypy, which is used by the dashboard and prometheus
-          mgr modules (and possibly others), hence
-          0000-dont-check-cherrypy-version.patch.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.weechat</literal> is now compiled against
-          <literal>pkgs.python3</literal>. Weechat also recommends
-          <link xlink:href="https://weechat.org/scripts/python3/">to use
-          Python3 in their docs.</link>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2003.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2003.section.xml
deleted file mode 100644
index 53e6e1329a94..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2003.section.xml
+++ /dev/null
@@ -1,1497 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-20.03">
-  <title>Release 20.03 (<quote>Markhor</quote>, 2020.04/20)</title>
-  <section xml:id="sec-release-20.03-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Support is planned until the end of October 2020, handing over
-          to 20.09.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Core version changes:
-        </para>
-        <para>
-          gcc: 8.3.0 -&gt; 9.2.0
-        </para>
-        <para>
-          glibc: 2.27 -&gt; 2.30
-        </para>
-        <para>
-          linux: 4.19 -&gt; 5.4
-        </para>
-        <para>
-          mesa: 19.1.5 -&gt; 19.3.3
-        </para>
-        <para>
-          openssl: 1.0.2u -&gt; 1.1.1d
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Desktop version changes:
-        </para>
-        <para>
-          plasma5: 5.16.5 -&gt; 5.17.5
-        </para>
-        <para>
-          kdeApplications: 19.08.2 -&gt; 19.12.3
-        </para>
-        <para>
-          gnome3: 3.32 -&gt; 3.34
-        </para>
-        <para>
-          pantheon: 5.0 -&gt; 5.1.3
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Linux kernel is updated to branch 5.4 by default (from 4.19).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Grub is updated to 2.04, adding support for booting from F2FS
-          filesystems and Btrfs volumes using zstd compression. Note
-          that some users have been unable to boot after upgrading to
-          2.04 - for more information, please see
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/61718#issuecomment-617618503">this
-          discussion</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Postgresql for NixOS service now defaults to v11.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The graphical installer image starts the graphical session
-          automatically. Before you'd be greeted by a tty and asked to
-          enter <literal>systemctl start display-manager</literal>. It
-          is now possible to disable the display-manager from running by
-          selecting the <literal>Disable display-manager</literal> quirk
-          in the boot menu.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GNOME 3 has been upgraded to 3.34. Please take a look at their
-          <link xlink:href="https://help.gnome.org/misc/release-notes/3.34">Release
-          Notes</link> for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If you enable the Pantheon Desktop Manager via
-          <link xlink:href="options.html#opt-services.xserver.desktopManager.pantheon.enable">services.xserver.desktopManager.pantheon.enable</link>,
-          we now default to also use
-          <link xlink:href="https://blog.elementary.io/say-hello-to-the-new-greeter/">
-          Pantheon's newly designed greeter </link>. Contrary to NixOS's
-          usual update policy, Pantheon will receive updates during the
-          cycle of NixOS 20.03 when backwards compatible.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          By default zfs pools will now be trimmed on a weekly basis.
-          Trimming is only done on supported devices (i.e. NVME or SSDs)
-          and should improve throughput and lifetime of these devices.
-          It is controlled by the
-          <literal>services.zfs.trim.enable</literal> varname. The zfs
-          scrub service
-          (<literal>services.zfs.autoScrub.enable</literal>) and the zfs
-          autosnapshot service
-          (<literal>services.zfs.autoSnapshot.enable</literal>) are now
-          only enabled if zfs is set in
-          <literal>config.boot.initrd.supportedFilesystems</literal> or
-          <literal>config.boot.supportedFilesystems</literal>. These
-          lists will automatically contain zfs as soon as any zfs
-          mountpoint is configured in <literal>fileSystems</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nixos-option</literal> has been rewritten in C++,
-          speeding it up, improving correctness, and adding a
-          <literal>-r</literal> option which prints all options and
-          their values recursively.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.xserver.desktopManager.default</literal> and
-          <literal>services.xserver.windowManager.default</literal>
-          options were replaced by a single
-          <link xlink:href="options.html#opt-services.xserver.displayManager.defaultSession">services.xserver.displayManager.defaultSession</link>
-          option to improve support for upstream session files. If you
-          used something like:
-        </para>
-        <programlisting language="bash">
-{
-  services.xserver.desktopManager.default = &quot;xfce&quot;;
-  services.xserver.windowManager.default = &quot;icewm&quot;;
-}
-</programlisting>
-        <para>
-          you should change it to:
-        </para>
-        <programlisting language="bash">
-{
-  services.xserver.displayManager.defaultSession = &quot;xfce+icewm&quot;;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The testing driver implementation in NixOS is now in Python
-          <literal>make-test-python.nix</literal>. This was done by
-          Jacek Galowicz
-          (<link xlink:href="https://github.com/tfc">@tfc</link>), and
-          with the collaboration of Julian Stecklina
-          (<link xlink:href="https://github.com/blitz">@blitz</link>)
-          and Jana Traue
-          (<link xlink:href="https://github.com/jtraue">@jtraue</link>).
-          All documentation has been updated to use this testing driver,
-          and a vast majority of the 286 tests in NixOS were ported to
-          python driver. In 20.09 the Perl driver implementation,
-          <literal>make-test.nix</literal>, is slated for removal. This
-          should give users of the NixOS integration framework a
-          transitory period to rewrite their tests to use the Python
-          implementation. Users of the Perl driver will see this warning
-          everytime they use it:
-        </para>
-        <programlisting>
-$ warning: Perl VM tests are deprecated and will be removed for 20.09.
-Please update your tests to use the python test driver.
-See https://github.com/NixOS/nixpkgs/pull/71684 for details.
-</programlisting>
-        <para>
-          API compatibility is planned to be kept for at least the next
-          release with the perl driver.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.03-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The kubernetes kube-proxy now supports a new hostname
-          configuration
-          <literal>services.kubernetes.proxy.hostname</literal> which
-          has to be set if the hostname of the node should be non
-          default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          UPower's configuration is now managed by NixOS and can be
-          customized via <literal>services.upower</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          To use Geary you should enable
-          <link xlink:href="options.html#opt-programs.geary.enable">programs.geary.enable</link>
-          instead of just adding it to
-          <link xlink:href="options.html#opt-environment.systemPackages">environment.systemPackages</link>.
-          It was created so Geary could function properly outside of
-          GNOME.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./config/console.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./hardware/brillo.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./hardware/tuxedo-keyboard.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/bandwhich.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/bash-my-aws.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/liboping.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./programs/traceroute.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/sanoid.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/syncoid.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/backup/zfs-replication.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/continuous-integration/buildkite-agents.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/databases/victoriametrics.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/gnome3/gnome-initial-setup.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/desktops/neard.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/games/openarena.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/hardware/fancontrol.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/mail/sympa.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/freeswitch.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/misc/mame.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/monitoring/do-agent.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/monitoring/prometheus/xmpp-alerts.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/orangefs/server.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/network-filesystems/orangefs/client.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/3proxy.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/corerad.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/go-shadowsocks2.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/ntp/openntpd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/shorewall.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/shorewall6.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/spacecookie.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/trickster.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/v2ray.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/xandikos.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/networking/yggdrasil.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/dokuwiki.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/gotify-server.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/grocy.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/ihatemoney</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/moinmoin.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/trac.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/trilium.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-apps/shiori.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/web-servers/ttyd.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/x11/picom.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/x11/hardware/digimend.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./services/x11/imwheel.nix</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>./virtualisation/cri-o.nix</literal>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.03-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The dhcpcd package
-          <link xlink:href="https://roy.marples.name/archives/dhcpcd-discuss/0002621.html">
-          does not request IPv4 addresses for tap and bridge interfaces
-          anymore by default</link>. In order to still get an address on
-          a bridge interface, one has to disable
-          <literal>networking.useDHCP</literal> and explicitly enable
-          <literal>networking.interfaces.&lt;name&gt;.useDHCP</literal>
-          on every interface, that should get an address via DHCP. This
-          way, dhcpcd is configured in an explicit way about which
-          interface to run on.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GnuPG is now built without support for a graphical passphrase
-          entry by default. Please enable the
-          <literal>gpg-agent</literal> user service via the NixOS option
-          <literal>programs.gnupg.agent.enable</literal>. Note that
-          upstream recommends using <literal>gpg-agent</literal> and
-          will spawn a <literal>gpg-agent</literal> on the first
-          invocation of GnuPG anyway.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>dynamicHosts</literal> option has been removed
-          from the
-          <link xlink:href="options.html#opt-networking.networkmanager.enable">NetworkManager</link>
-          module. Allowing (multiple) regular users to override host
-          entries affecting the whole system opens up a huge attack
-          vector. There seem to be very rare cases where this might be
-          useful. Consider setting system-wide host entries using
-          <link xlink:href="options.html#opt-networking.hosts">networking.hosts</link>,
-          provide them via the DNS server in your network, or use
-          <link xlink:href="options.html#opt-environment.etc">environment.etc</link>
-          to add a file into
-          <literal>/etc/NetworkManager/dnsmasq.d</literal> reconfiguring
-          <literal>hostsdir</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>99-main.network</literal> file was removed.
-          Matching all network interfaces caused many breakages, see
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/18962">#18962</link>
-          and
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/71106">#71106</link>.
-        </para>
-        <para>
-          We already don't support the global
-          <link xlink:href="options.html#opt-networking.useDHCP">networking.useDHCP</link>,
-          <link xlink:href="options.html#opt-networking.defaultGateway">networking.defaultGateway</link>
-          and
-          <link xlink:href="options.html#opt-networking.defaultGateway6">networking.defaultGateway6</link>
-          options if
-          <link xlink:href="options.html#opt-networking.useNetworkd">networking.useNetworkd</link>
-          is enabled, but direct users to configure the per-device
-          <link xlink:href="options.html#opt-networking.interfaces">networking.interfaces.&lt;name&gt;….</link>
-          options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The stdenv now runs all bash with <literal>set -u</literal>,
-          to catch the use of undefined variables. Before, it itself
-          used <literal>set -u</literal> but was careful to unset it so
-          other packages' code ran as before. Now, all bash code is held
-          to the same high standard, and the rather complex stateful
-          manipulation of the options can be discarded.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The SLIM Display Manager has been removed, as it has been
-          unmaintained since 2013. Consider migrating to a different
-          display manager such as LightDM (current default in NixOS),
-          SDDM, GDM, or using the startx module which uses Xinitrc.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Way Cooler wayland compositor has been removed, as the
-          project has been officially canceled. There are no more
-          <literal>way-cooler</literal> attribute and
-          <literal>programs.way-cooler</literal> options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The BEAM package set has been deleted. You will only find
-          there the different interpreters. You should now use the
-          different build tools coming with the languages with sandbox
-          mode disabled.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is now only one Xfce package-set and module. This means
-          that attributes <literal>xfce4-14</literal> and
-          <literal>xfceUnstable</literal> all now point to the latest
-          Xfce 4.14 packages. And in the future NixOS releases will be
-          the latest released version of Xfce available at the time of
-          the release's development (if viable).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.phpfpm.pools">phpfpm</link>
-          module now sets <literal>PrivateTmp=true</literal> in its
-          systemd units for better process isolation. If you rely on
-          <literal>/tmp</literal> being shared with other services,
-          explicitly override this by setting
-          <literal>serviceConfig.PrivateTmp</literal> to
-          <literal>false</literal> for each phpfpm unit.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          KDE’s old multimedia framework Phonon no longer supports Qt 4.
-          For that reason, Plasma desktop also does not have
-          <literal>enableQt4Support</literal> option any more.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The BeeGFS module has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The osquery module has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Going forward, <literal>~/bin</literal> in the users home
-          directory will no longer be in <literal>PATH</literal> by
-          default. If you depend on this you should set the option
-          <literal>environment.homeBinInPath</literal> to
-          <literal>true</literal>. The aforementioned option was added
-          this release.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>buildRustCrate</literal> infrastructure now
-          produces <literal>lib</literal> outputs in addition to the
-          <literal>out</literal> output. This has led to drastically
-          reduced closure sizes for some rust crates since development
-          dependencies are now in the <literal>lib</literal> output.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Pango was upgraded to 1.44, which no longer uses freetype for
-          font loading. This means that type1 and bitmap fonts are no
-          longer supported in applications relying on Pango for font
-          rendering (notably, GTK application). See
-          <link xlink:href="https://gitlab.gnome.org/GNOME/pango/issues/386">
-          upstream issue</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>roundcube</literal> module has been hardened.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The password of the database is not written world readable
-              in the store any more. If <literal>database.host</literal>
-              is set to <literal>localhost</literal>, then a unix user
-              of the same name as the database will be created and
-              PostreSQL peer authentication will be used, removing the
-              need for a password. Otherwise, a password is still needed
-              and can be provided with the new option
-              <literal>database.passwordFile</literal>, which should be
-              set to the path of a file containing the password and
-              readable by the user <literal>nginx</literal> only. The
-              <literal>database.password</literal> option is insecure
-              and deprecated. Usage of this option will print a warning.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              A random <literal>des_key</literal> is set by default in
-              the configuration of roundcube, instead of using the
-              hardcoded and insecure default. To ensure a clean
-              migration, all users will be logged out when you upgrade
-              to this release.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The packages <literal>openobex</literal> and
-          <literal>obexftp</literal> are no longer installed when
-          enabling Bluetooth via
-          <literal>hardware.bluetooth.enable</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>dump1090</literal> derivation has been changed to
-          use FlightAware's dump1090 as its upstream. However, this
-          version does not have an internal webserver anymore. The
-          assets in the <literal>share/dump1090</literal> directory of
-          the derivation can be used in conjunction with an external
-          webserver to replace this functionality.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The fourStore and fourStoreEndpoint modules have been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Polkit no longer has the user of uid 0 (root) as an admin
-          identity. We now follow the upstream default of only having
-          every member of the wheel group admin privileged. Before it
-          was root and members of wheel. The positive outcome of this is
-          pkexec GUI popups or terminal prompts will no longer require
-          the user to choose between two essentially equivalent choices
-          (whether to perform the action as themselves with wheel
-          permissions, or as the root user).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS containers no longer build NixOS manual by default. This
-          saves evaluation time, especially if there are many
-          declarative containers defined. Note that this is already done
-          when
-          <literal>&lt;nixos/modules/profiles/minimal.nix&gt;</literal>
-          module is included in container config.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>kresd</literal> services deprecates the
-          <literal>interfaces</literal> option in favor of the
-          <literal>listenPlain</literal> option which requires full
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream=">systemd.socket
-          compatible</link> declaration which always include a port.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Virtual console options have been reorganized and can be found
-          under a single top-level attribute:
-          <literal>console</literal>. The full set of changes is as
-          follows:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>i18n.consoleFont</literal> renamed to
-              <link xlink:href="options.html#opt-console.font">console.font</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>i18n.consoleKeyMap</literal> renamed to
-              <link xlink:href="options.html#opt-console.keyMap">console.keyMap</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>i18n.consoleColors</literal> renamed to
-              <link xlink:href="options.html#opt-console.colors">console.colors</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>i18n.consolePackages</literal> renamed to
-              <link xlink:href="options.html#opt-console.packages">console.packages</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>i18n.consoleUseXkbConfig</literal> renamed to
-              <link xlink:href="options.html#opt-console.useXkbConfig">console.useXkbConfig</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>boot.earlyVconsoleSetup</literal> renamed to
-              <link xlink:href="options.html#opt-console.earlySetup">console.earlySetup</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>boot.extraTTYs</literal> renamed to
-              <literal>console.extraTTYs</literal>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.awstats.enable">awstats</link>
-          module has been rewritten to serve stats via static html
-          pages, updated on a timer, over
-          <link xlink:href="options.html#opt-services.nginx.virtualHosts">nginx</link>,
-          instead of dynamic cgi pages over
-          <link xlink:href="options.html#opt-services.httpd.enable">apache</link>.
-        </para>
-        <para>
-          Minor changes will be required to migrate existing
-          configurations. Details of the required changes can seen by
-          looking through the
-          <link xlink:href="options.html#opt-services.awstats.enable">awstats</link>
-          module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The httpd module no longer provides options to support serving
-          web content without defining a virtual host. As a result of
-          this the
-          <link xlink:href="options.html#opt-services.httpd.logPerVirtualHost">services.httpd.logPerVirtualHost</link>
-          option now defaults to <literal>true</literal> instead of
-          <literal>false</literal>. Please update your configuration to
-          make use of
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts</link>.
-        </para>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;</link>
-          option has changed type from a list of submodules to an
-          attribute set of submodules, better matching
-          <link xlink:href="options.html#opt-services.nginx.virtualHosts">services.nginx.virtualHosts.&lt;name&gt;</link>.
-        </para>
-        <para>
-          This change comes with the addition of the following options
-          which mimic the functionality of their
-          <literal>nginx</literal> counterparts:
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.addSSL</link>,
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.forceSSL</link>,
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.onlySSL</link>,
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.enableACME</link>,
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.acmeRoot</link>,
-          and
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.useACMEHost</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          For NixOS configuration options, the <literal>loaOf</literal>
-          type has been deprecated and will be removed in a future
-          release. In nixpkgs, options of this type will be changed to
-          <literal>attrsOf</literal> instead. If you were using one of
-          these in your configuration, you will see a warning suggesting
-          what changes will be required.
-        </para>
-        <para>
-          For example,
-          <link xlink:href="options.html#opt-users.users">users.users</link>
-          is a <literal>loaOf</literal> option that is commonly used as
-          follows:
-        </para>
-        <programlisting language="bash">
-{
-  users.users =
-    [ { name = &quot;me&quot;;
-        description = &quot;My personal user.&quot;;
-        isNormalUser = true;
-      }
-    ];
-}
-</programlisting>
-        <para>
-          This should be rewritten by removing the list and using the
-          value of <literal>name</literal> as the name of the attribute
-          set:
-        </para>
-        <programlisting language="bash">
-{
-  users.users.me =
-    { description = &quot;My personal user.&quot;;
-      isNormalUser = true;
-    };
-}
-</programlisting>
-        <para>
-          For more information on this change have look at these links:
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/1800">issue
-          #1800</link>,
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/63103">PR
-          #63103</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          For NixOS modules, the types
-          <literal>types.submodule</literal> and
-          <literal>types.submoduleWith</literal> now support paths as
-          allowed values, similar to how <literal>imports</literal>
-          supports paths. Because of this, if you have a module that
-          defines an option of type
-          <literal>either (submodule ...) path</literal>, it will break
-          since a path is now treated as the first type instead of the
-          second. To fix this, change the type to
-          <literal>either path (submodule ...)</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.buildkite-agents">Buildkite
-          Agent</link> module and corresponding packages have been
-          updated to 3.x, and to support multiple instances of the agent
-          running at the same time. This means you will have to rename
-          <literal>services.buildkite-agent</literal> to
-          <literal>services.buildkite-agents.&lt;name&gt;</literal>.
-          Furthermore, the following options have been changed:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>services.buildkite-agent.meta-data</literal> has
-              been renamed to
-              <link xlink:href="options.html#opt-services.buildkite-agents">services.buildkite-agents.&lt;name&gt;.tags</link>,
-              to match upstreams naming for 3.x. Its type has also
-              changed - it now accepts an attrset of strings.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The<literal>services.buildkite-agent.openssh.publicKeyPath</literal>
-              option has been removed, as it's not necessary to deploy
-              public keys to clone private repositories.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.buildkite-agent.openssh.privateKeyPath</literal>
-              has been renamed to
-              <link xlink:href="options.html#opt-services.buildkite-agents">buildkite-agents.&lt;name&gt;.privateSshKeyPath</link>,
-              as the whole <literal>openssh</literal> now only contained
-              that single option.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.buildkite-agents">services.buildkite-agents.&lt;name&gt;.shell</link>
-              has been introduced, allowing to specify a custom shell to
-              be used.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>citrix_workspace_19_3_0</literal> package has
-          been removed as it will be EOLed within the lifespan of 20.03.
-          For further information, please refer to the
-          <link xlink:href="https://www.citrix.com/de-de/support/product-lifecycle/milestones/receiver.html">support
-          and maintenance information</link> from upstream.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>gcc5</literal> and <literal>gfortran5</literal>
-          packages have been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.xserver.displayManager.auto</literal>
-          module has been removed. It was only intended for use in
-          internal NixOS tests, and gave the false impression of it
-          being a special display manager when it's actually LightDM.
-          Please use the
-          <literal>services.xserver.displayManager.lightdm.autoLogin</literal>
-          options instead, or any other display manager in NixOS as they
-          all support auto-login. If you used this module specifically
-          because it permitted root auto-login you can override the
-          lightdm-autologin pam module like:
-        </para>
-        <programlisting language="bash">
-{
-  security.pam.services.lightdm-autologin.text = lib.mkForce ''
-      auth     requisite pam_nologin.so
-      auth     required  pam_succeed_if.so quiet
-      auth     required  pam_permit.so
-
-      account  include   lightdm
-
-      password include   lightdm
-
-      session  include   lightdm
-  '';
-}
-</programlisting>
-        <para>
-          The difference is the:
-        </para>
-        <programlisting>
-auth required pam_succeed_if.so quiet
-</programlisting>
-        <para>
-          line, where default it's:
-        </para>
-        <programlisting>
- auth required pam_succeed_if.so uid &gt;= 1000 quiet
-</programlisting>
-        <para>
-          not permitting users with uid's below 1000 (like root). All
-          other display managers in NixOS are configured like this.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There have been lots of improvements to the Mailman module. As
-          a result,
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The <literal>services.mailman.hyperkittyBaseUrl</literal>
-              option has been renamed to
-              <link xlink:href="options.html#opt-services.mailman.hyperkitty.baseUrl">services.mailman.hyperkitty.baseUrl</link>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>services.mailman.hyperkittyApiKey</literal>
-              option has been removed. This is because having an option
-              for the Hyperkitty API key meant that the API key would be
-              stored in the world-readable Nix store, which was a
-              security vulnerability. A new Hyperkitty API key will be
-              generated the first time the new Hyperkitty service is
-              run, and it will then be persisted outside of the Nix
-              store. To continue using Hyperkitty, you must set
-              <link xlink:href="options.html#opt-services.mailman.hyperkitty.enable">services.mailman.hyperkitty.enable</link>
-              to <literal>true</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Additionally, some Postfix configuration must now be set
-              manually instead of automatically by the Mailman module:
-            </para>
-            <programlisting language="bash">
-{
-  services.postfix.relayDomains = [ &quot;hash:/var/lib/mailman/data/postfix_domains&quot; ];
-  services.postfix.config.transport_maps = [ &quot;hash:/var/lib/mailman/data/postfix_lmtp&quot; ];
-  services.postfix.config.local_recipient_maps = [ &quot;hash:/var/lib/mailman/data/postfix_lmtp&quot; ];
-}
-</programlisting>
-            <para>
-              This is because some users may want to include other
-              values in these lists as well, and this was not possible
-              if they were set automatically by the Mailman module. It
-              would not have been possible to just concatenate values
-              from multiple modules each setting the values they needed,
-              because the order of elements in the list is significant.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The LLVM versions 3.5, 3.9 and 4 (including the corresponding
-          CLang versions) have been dropped.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <literal>networking.interfaces.*.preferTempAddress</literal>
-          option has been replaced by
-          <literal>networking.interfaces.*.tempAddress</literal>. The
-          new option allows better control of the IPv6 temporary
-          addresses, including completely disabling them for interfaces
-          where they are not needed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Rspamd was updated to version 2.2. Read
-          <link xlink:href="https://rspamd.com/doc/migration.html#migration-to-rspamd-20">
-          the upstream migration notes</link> carefully. Please be
-          especially aware that some modules were removed and the
-          default Bayes backend is now Redis.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>*psu</literal> versions of oraclejdk8 have been
-          removed as they aren't provided by upstream anymore.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.dnscrypt-proxy</literal> module has been
-          removed as it used the deprecated version of dnscrypt-proxy.
-          We've added
-          <link xlink:href="options.html#opt-services.dnscrypt-proxy2.enable">services.dnscrypt-proxy2.enable</link>
-          to use the supported version. This module supports
-          configuration via the Nix attribute set
-          <link xlink:href="options.html#opt-services.dnscrypt-proxy2.settings">services.dnscrypt-proxy2.settings</link>,
-          or by passing a TOML configuration file via
-          <link xlink:href="options.html#opt-services.dnscrypt-proxy2.configFile">services.dnscrypt-proxy2.configFile</link>.
-        </para>
-        <programlisting language="bash">
-{
-  # Example configuration:
-  services.dnscrypt-proxy2.enable = true;
-  services.dnscrypt-proxy2.settings = {
-    listen_addresses = [ &quot;127.0.0.1:43&quot; ];
-    sources.public-resolvers = {
-      urls = [ &quot;https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md&quot; ];
-      cache_file = &quot;public-resolvers.md&quot;;
-      minisign_key = &quot;RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3&quot;;
-      refresh_delay = 72;
-    };
-  };
-
-  services.dnsmasq.enable = true;
-  services.dnsmasq.servers = [ &quot;127.0.0.1#43&quot; ];
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>qesteidutil</literal> has been deprecated in favor of
-          <literal>qdigidoc</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          sqldeveloper_18 has been removed as it's not maintained
-          anymore, sqldeveloper has been updated to version
-          <literal>19.4</literal>. Please note that this means that this
-          means that the oraclejdk is now required. For further
-          information please read the
-          <link xlink:href="https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/sqldev-relnotes-194-5908846.html">release
-          notes</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Haskell <literal>env</literal> and <literal>shellFor</literal>
-          dev shell environments now organize dependencies the same way
-          as regular builds. In particular, rather than receiving all
-          the different lists of dependencies mashed together as one big
-          list, and then partitioning into Haskell and non-Hakell
-          dependencies, they work from the original many different
-          dependency parameters and don't need to algorithmically
-          partition anything.
-        </para>
-        <para>
-          This means that if you incorrectly categorize a dependency,
-          e.g. non-Haskell library dependency as a
-          <literal>buildDepends</literal> or run-time Haskell dependency
-          as a <literal>setupDepends</literal>, whereas things would
-          have worked before they may not work now.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The gcc-snapshot-package has been removed. It's marked as
-          broken for &gt;2 years and used to point to a fairly old
-          snapshot from the gcc7-branch.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The nixos-build-vms8 -script now uses the python test-driver.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The riot-web package now accepts configuration overrides as an
-          attribute set instead of a string. A formerly used JSON
-          configuration can be converted to an attribute set with
-          <literal>builtins.fromJSON</literal>.
-        </para>
-        <para>
-          The new default configuration also disables automatic guest
-          account registration and analytics to improve privacy. The
-          previous behavior can be restored by setting
-          <literal>config.riot-web.conf = { disable_guests = false; piwik = true; }</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Stand-alone usage of <literal>Upower</literal> now requires
-          <literal>services.upower.enable</literal> instead of just
-          installing into
-          <link xlink:href="options.html#opt-environment.systemPackages">environment.systemPackages</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          nextcloud has been updated to <literal>v18.0.2</literal>. This
-          means that users from NixOS 19.09 can't upgrade directly since
-          you can only move one version forward and 19.09 uses
-          <literal>v16.0.8</literal>.
-        </para>
-        <para>
-          To provide a safe upgrade-path and to circumvent similar
-          issues in the future, the following measures were taken:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The pkgs.nextcloud-attribute has been removed and replaced
-              with versioned attributes (currently pkgs.nextcloud17 and
-              pkgs.nextcloud18). With this change major-releases can be
-              backported without breaking stuff and to make
-              upgrade-paths easier.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Existing setups will be detected using
-              <link xlink:href="options.html#opt-system.stateVersion">system.stateVersion</link>:
-              by default, nextcloud17 will be used, but will raise a
-              warning which notes that after that deploy it's
-              recommended to update to the latest stable version
-              (nextcloud18) by declaring the newly introduced setting
-              <link xlink:href="options.html#opt-services.nextcloud.package">services.nextcloud.package</link>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Users with an overlay (e.g. to use nextcloud at version
-              <literal>v18</literal> on <literal>19.09</literal>) will
-              get an evaluation error by default. This is done to ensure
-              that our
-              <link xlink:href="options.html#opt-services.nextcloud.package">package</link>-option
-              doesn't select an older version by accident. It's
-              recommended to use pkgs.nextcloud18 or to set
-              <link xlink:href="options.html#opt-services.nextcloud.package">package</link>
-              to pkgs.nextcloud explicitly.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <warning>
-          <para>
-            Please note that if you're coming from
-            <literal>19.03</literal> or older, you have to manually
-            upgrade to <literal>19.09</literal> first to upgrade your
-            server to Nextcloud v16.
-          </para>
-        </warning>
-      </listitem>
-      <listitem>
-        <para>
-          Hydra has gained a massive performance improvement due to
-          <link xlink:href="https://github.com/NixOS/hydra/pull/710">some
-          database schema changes</link> by adding several IDs and
-          better indexing. However, it's necessary to upgrade Hydra in
-          multiple steps:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              At first, an older version of Hydra needs to be deployed
-              which adds those (nullable) columns. When having set
-              <link xlink:href="options.html#opt-system.stateVersion">stateVersion
-              </link> to a value older than <literal>20.03</literal>,
-              this package will be selected by default from the module
-              when upgrading. Otherwise, the package can be deployed
-              using the following config:
-            </para>
-            <programlisting language="bash">
-{ pkgs, ... }: {
-  services.hydra.package = pkgs.hydra-migration;
-}
-</programlisting>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Automatically fill the newly added ID columns on the server by
-          running the following command:
-        </para>
-        <programlisting>
-$ hydra-backfill-ids
-</programlisting>
-        <warning>
-          <para>
-            Please note that this process can take a while depending on
-            your database-size!
-          </para>
-        </warning>
-      </listitem>
-      <listitem>
-        <para>
-          Deploy a newer version of Hydra to activate the DB
-          optimizations. This can be done by using hydra-unstable. This
-          package already includes
-          <link xlink:href="https://github.com/nixos/rfcs/pull/49">flake-support</link>
-          and is therefore compiled against pkgs.nixFlakes.
-        </para>
-        <warning>
-          <para>
-            If your
-            <link xlink:href="options.html#opt-system.stateVersion">stateVersion</link>
-            is set to <literal>20.03</literal> or greater,
-            hydra-unstable will be used automatically! This will break
-            your setup if you didn't run the migration.
-          </para>
-        </warning>
-        <para>
-          Please note that Hydra is currently not available with
-          nixStable as this doesn't compile anymore.
-        </para>
-        <warning>
-          <para>
-            pkgs.hydra has been removed to ensure a graceful
-            database-migration using the dedicated package-attributes.
-            If you still have pkgs.hydra defined in e.g. an overlay, an
-            assertion error will be thrown. To circumvent this, you need
-            to set
-            <link xlink:href="options.html#opt-services.hydra.package">services.hydra.package</link>
-            to pkgs.hydra explicitly and make sure you know what you're
-            doing!
-          </para>
-        </warning>
-      </listitem>
-      <listitem>
-        <para>
-          The TokuDB storage engine will be disabled in mariadb 10.5. It
-          is recommended to switch to RocksDB. See also
-          <link xlink:href="https://mariadb.com/kb/en/tokudb/">TokuDB</link>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.03-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          SD images are now compressed by default using
-          <literal>bzip2</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The nginx web server previously started its master process as
-          root privileged, then ran worker processes as a less
-          privileged identity user (the <literal>nginx</literal> user).
-          This was changed to start all of nginx as a less privileged
-          user (defined by <literal>services.nginx.user</literal> and
-          <literal>services.nginx.group</literal>). As a consequence,
-          all files that are needed for nginx to run (included
-          configuration fragments, SSL certificates and keys, etc.) must
-          now be readable by this less privileged user/group.
-        </para>
-        <para>
-          To continue to use the old approach, you can configure:
-        </para>
-        <programlisting language="bash">
-{
-  services.nginx.appendConfig = let cfg = config.services.nginx; in ''user ${cfg.user} ${cfg.group};'';
-  systemd.services.nginx.serviceConfig.User = lib.mkForce &quot;root&quot;;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          OpenSSH has been upgraded from 7.9 to 8.1, improving security
-          and adding features but with potential incompatibilities.
-          Consult the
-          <link xlink:href="https://www.openssh.com/txt/release-8.1">
-          release announcement</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>PRETTY_NAME</literal> in
-          <literal>/etc/os-release</literal> now uses the short rather
-          than full version string.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The ACME module has switched from simp-le to
-          <link xlink:href="https://github.com/go-acme/lego">lego</link>
-          which allows us to support DNS-01 challenges and wildcard
-          certificates. The following options have been added:
-          <link xlink:href="options.html#opt-security.acme.acceptTerms">security.acme.acceptTerms</link>,
-          <link xlink:href="options.html#opt-security.acme.certs">security.acme.certs.&lt;name&gt;.dnsProvider</link>,
-          <link xlink:href="options.html#opt-security.acme.certs">security.acme.certs.&lt;name&gt;.credentialsFile</link>,
-          <link xlink:href="options.html#opt-security.acme.certs">security.acme.certs.&lt;name&gt;.dnsPropagationCheck</link>.
-          As well as this, the options
-          <literal>security.acme.acceptTerms</literal> and either
-          <literal>security.acme.email</literal> or
-          <literal>security.acme.certs.&lt;name&gt;.email</literal> must
-          be set in order to use the ACME module. Certificates will be
-          regenerated on activation, no account or certificate will be
-          migrated from simp-le. In particular private keys will not be
-          preserved. However, the credentials for simp-le are preserved
-          and thus it is possible to roll back to previous versions
-          without breaking certificate generation. Note also that in
-          contrary to simp-le a new private key is recreated at each
-          renewal by default, which can have consequences if you embed
-          your public key in apps.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          It is now possible to unlock LUKS-Encrypted file systems using
-          a FIDO2 token via
-          <literal>boot.initrd.luks.fido2Support</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Predictably named network interfaces get renamed in stage-1.
-          This means that it is possible to use the proper interface
-          name for e.g. Dropbear setups.
-        </para>
-        <para>
-          For further reference, please read
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/68953">#68953</link>
-          or the corresponding
-          <link xlink:href="https://discourse.nixos.org/t/predictable-network-interface-names-in-initrd/4055">discourse
-          thread</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The matrix-synapse-package has been updated to
-          <link xlink:href="https://github.com/matrix-org/synapse/releases/tag/v1.11.1">v1.11.1</link>.
-          Due to
-          <link xlink:href="https://github.com/matrix-org/synapse/releases/tag/v1.10.0rc1">stricter
-          requirements</link> for database configuration when using
-          postgresql, the automated database setup of the module has
-          been removed to avoid any further edge-cases.
-        </para>
-        <para>
-          matrix-synapse expects <literal>postgresql</literal>-databases
-          to have the options <literal>LC_COLLATE</literal> and
-          <literal>LC_CTYPE</literal> set to
-          <link xlink:href="https://www.postgresql.org/docs/12/locale.html"><literal>'C'</literal></link>
-          which basically instructs <literal>postgresql</literal> to
-          ignore any locale-based preferences.
-        </para>
-        <para>
-          Depending on your setup, you need to incorporate one of the
-          following changes in your setup to upgrade to 20.03:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              If you use <literal>sqlite3</literal> you don't need to do
-              anything.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              If you use <literal>postgresql</literal> on a different
-              server, you don't need to change anything as well since
-              this module was never designed to configure remote
-              databases.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              If you use <literal>postgresql</literal> and configured
-              your synapse initially on <literal>19.09</literal> or
-              older, you simply need to enable postgresql-support
-              explicitly:
-            </para>
-            <programlisting language="bash">
-{ ... }: {
-  services.matrix-synapse = {
-    enable = true;
-    /* and all the other config you've defined here */
-  };
-  services.postgresql.enable = true;
-}
-</programlisting>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          If you deploy a fresh matrix-synapse, you need to configure
-          the database yourself (e.g. by using the
-          <link xlink:href="options.html#opt-services.postgresql.initialScript">services.postgresql.initialScript</link>
-          option). An example for this can be found in the
-          <link linkend="module-services-matrix">documentation of the
-          Matrix module</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If you initially deployed your matrix-synapse on
-          <literal>nixos-unstable</literal> <emphasis>after</emphasis>
-          the <literal>19.09</literal>-release, your database is
-          misconfigured due to a regression in NixOS. For now,
-          matrix-synapse will startup with a warning, but it's
-          recommended to reconfigure the database to set the values
-          <literal>LC_COLLATE</literal> and <literal>LC_CTYPE</literal>
-          to
-          <link xlink:href="https://www.postgresql.org/docs/12/locale.html"><literal>'C'</literal></link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-systemd.network.links">systemd.network.links</link>
-          option is now respected even when
-          <link xlink:href="options.html#opt-systemd.network.enable">systemd-networkd</link>
-          is disabled. This mirrors the behaviour of systemd - It's udev
-          that parses <literal>.link</literal> files, not
-          <literal>systemd-networkd</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          mongodb has been updated to version <literal>3.4.24</literal>.
-        </para>
-        <warning>
-          <para>
-            Please note that mongodb has been relicensed under their own
-            <link xlink:href="https://www.mongodb.com/licensing/server-side-public-license/faq"><literal> sspl</literal></link>-license.
-            Since it's not entirely free and not OSI-approved, it's
-            listed as non-free. This means that Hydra doesn't provide
-            prebuilt mongodb-packages and needs to be built locally.
-          </para>
-        </warning>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
deleted file mode 100644
index edebd92b327a..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
+++ /dev/null
@@ -1,2210 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-20.09">
-  <title>Release 20.09 (<quote>Nightingale</quote>, 2020.10/27)</title>
-  <para>
-    Support is planned until the end of June 2021, handing over to
-    21.05. (Plans
-    <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md#core-changes">
-    have shifted</link> by two months since release of 20.09.)
-  </para>
-  <section xml:id="sec-release-20.09-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to 7349 new, 14442 updated, and 8181 removed packages,
-      this release has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Core version changes:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              gcc: 9.2.0 -&gt; 9.3.0
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              glibc: 2.30 -&gt; 2.31
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              linux: still defaults to 5.4.x, all supported kernels
-              available
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              mesa: 19.3.5 -&gt; 20.1.7
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Desktop Environments:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              plasma5: 5.17.5 -&gt; 5.18.5
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              kdeApplications: 19.12.3 -&gt; 20.08.1
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              gnome3: 3.34 -&gt; 3.36, see its
-              <link xlink:href="https://help.gnome.org/misc/release-notes/3.36/">release
-              notes</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              cinnamon: added at 4.6
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              NixOS now distributes an official
-              <link xlink:href="https://nixos.org/download.html#nixos-iso">GNOME
-              ISO</link>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Programming Languages and Frameworks:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              Agda ecosystem was heavily reworked (see more details
-              below)
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              PHP now defaults to PHP 7.4, updated from 7.3
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              PHP 7.2 is no longer supported due to upstream not
-              supporting this version for the entire lifecycle of the
-              20.09 release
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Python 3 now defaults to Python 3.8 instead of 3.7
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Python 3.5 reached its upstream EOL at the end of
-              September 2020: it has been removed from the list of
-              available packages
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Databases and Service Monitoring:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              MariaDB has been updated to 10.4, MariaDB Galera to 26.4.
-              Please read the related upgrade instructions under
-              <link linkend="sec-release-20.09-incompatibilities">backwards
-              incompatibilities</link> before upgrading.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Zabbix now defaults to 5.0, updated from 4.4. Please read
-              related sections under
-              <link linkend="sec-release-20.09-incompatibilities">backwards
-              compatibilities</link> before upgrading.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Major module changes:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              Quickly configure a complete, private, self-hosted video
-              conferencing solution with the new Jitsi Meet module.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Two new options,
-              <link xlink:href="options.html#opt-services.openssh.authorizedKeysCommand">authorizedKeysCommand</link>
-              and
-              <link xlink:href="options.html#opt-services.openssh.authorizedKeysCommandUser">authorizedKeysCommandUser</link>,
-              have been added to the <literal>openssh</literal> module.
-              If you have <literal>AuthorizedKeysCommand</literal> in
-              your
-              <link xlink:href="options.html#opt-services.openssh.extraConfig">services.openssh.extraConfig</link>
-              you should make use of these new options instead.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              There is a new module for Podman
-              (<literal>virtualisation.podman</literal>), a drop-in
-              replacement for the Docker command line.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The new <literal>virtualisation.containers</literal>
-              module manages configuration shared by the CRI-O and
-              Podman modules.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Declarative Docker containers are renamed from
-              <literal>docker-containers</literal> to
-              <literal>virtualisation.oci-containers.containers</literal>.
-              This is to make it possible to use
-              <literal>podman</literal> instead of
-              <literal>docker</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The new option
-              <link xlink:href="options.html#opt-documentation.man.generateCaches">documentation.man.generateCaches</link>
-              has been added to automatically generate the
-              <literal>man-db</literal> caches, which are needed by
-              utilities like <literal>whatis</literal> and
-              <literal>apropos</literal>. The caches are generated
-              during the build of the NixOS configuration: since this
-              can be expensive when a large number of packages are
-              installed, the feature is disabled by default.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.postfix.sslCACert</literal> was replaced
-              by
-              <literal>services.postfix.tlsTrustedAuthorities</literal>
-              which now defaults to system certificate authorities.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The various documented workarounds to use steam have been
-              converted to a module.
-              <literal>programs.steam.enable</literal> enables steam,
-              controller support and the workarounds.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Support for built-in LCDs in various pieces of Logitech
-              hardware (keyboards and USB speakers).
-              <literal>hardware.logitech.lcd.enable</literal> enables
-              support for all hardware supported by the
-              <link xlink:href="https://sourceforge.net/projects/g15daemon/">g15daemon
-              project</link>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The GRUB module gained support for basic password
-              protection, which allows to restrict non-default entries
-              in the boot menu to one or more users. The users and
-              passwords are defined via the option
-              <literal>boot.loader.grub.users</literal>. Note: Password
-              support is only available in GRUB version 2.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS module changes:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The NixOS module system now supports freeform modules as a
-              mix between <literal>types.attrsOf</literal> and
-              <literal>types.submodule</literal>. These allow you to
-              explicitly declare a subset of options while still
-              permitting definitions without an associated option. See
-              <xref linkend="sec-freeform-modules" /> for how to use
-              them.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Following its deprecation in 20.03, the Perl NixOS test
-              driver has been removed. All remaining tests have been
-              ported to the Python test framework. Code outside nixpkgs
-              using <literal>make-test.nix</literal> or
-              <literal>testing.nix</literal> needs to be ported to
-              <literal>make-test-python.nix</literal> and
-              <literal>testing-python.nix</literal> respectively.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Subordinate GID and UID mappings are now set up
-              automatically for all normal users. This will make
-              container tools like Podman work as non-root users out of
-              the box.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Starting with this release, the hydra-build-result
-          <literal>nixos-YY.MM</literal> branches no longer exist in the
-          <link xlink:href="https://github.com/nixos/nixpkgs-channels">deprecated
-          nixpkgs-channels repository</link>. These branches are now in
-          <link xlink:href="https://github.com/nixos/nixpkgs">the main
-          nixpkgs repository</link>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.09-new-services">
-    <title>New Services</title>
-    <para>
-      In addition to 1119 new, 118 updated, and 476 removed options; 61
-      new modules were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Hardware:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-hardware.system76.firmware-daemon.enable">hardware.system76.firmware-daemon.enable</link>
-              adds easy support of system76 firmware
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-hardware.uinput.enable">hardware.uinput.enable</link>
-              loads uinput kernel module
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-hardware.video.hidpi.enable">hardware.video.hidpi.enable</link>
-              enable good defaults for HiDPI displays
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-hardware.wooting.enable">hardware.wooting.enable</link>
-              support for Wooting keyboards
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-hardware.xpadneo.enable">hardware.xpadneo.enable</link>
-              xpadneo driver for Xbox One wireless controllers
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Programs:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-programs.hamster.enable">programs.hamster.enable</link>
-              enable hamster time tracking
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-programs.steam.enable">programs.steam.enable</link>
-              adds easy enablement of steam and related system
-              configuration
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Security:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-security.doas.enable">security.doas.enable</link>
-              alternative to sudo, allows non-root users to execute
-              commands as root
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-security.tpm2.enable">security.tpm2.enable</link>
-              add Trusted Platform Module 2 support
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          System:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-boot.initrd.network.openvpn.enable">boot.initrd.network.openvpn.enable</link>
-              start an OpenVPN client during initrd boot
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Virtualization:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-boot.enableContainers">boot.enableContainers</link>
-              use nixos-containers
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-virtualisation.oci-containers.containers">virtualisation.oci-containers.containers</link>
-              run OCI (Docker) containers
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-virtualisation.podman.enable">virtualisation.podman.enable</link>
-              daemonless container engine
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Services:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.ankisyncd.enable">services.ankisyncd.enable</link>
-              Anki sync server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.bazarr.enable">services.bazarr.enable</link>
-              Subtitle manager for Sonarr and Radarr
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.biboumi.enable">services.biboumi.enable</link>
-              Biboumi XMPP gateway to IRC
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.blockbook-frontend">services.blockbook-frontend</link>
-              Blockbook-frontend, a service for the Trezor wallet
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.cage.enable">services.cage.enable</link>
-              Wayland cage service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.convos.enable">services.convos.enable</link>
-              IRC daemon, which can be accessed throught the browser
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.engelsystem.enable">services.engelsystem.enable</link>
-              Tool for coordinating volunteers and shifts on large
-              events
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.espanso.enable">services.espanso.enable</link>
-              text-expander written in rust
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.foldingathome.enable">services.foldingathome.enable</link>
-              Folding@home client
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.gerrit.enable">services.gerrit.enable</link>
-              Web-based team code collaboration tool
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.go-neb.enable">services.go-neb.enable</link>
-              Matrix bot
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.hardware.xow.enable">services.hardware.xow.enable</link>
-              xow as a systemd service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.hercules-ci-agent.enable">services.hercules-ci-agent.enable</link>
-              Hercules CI build agent
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.jicofo.enable">services.jicofo.enable</link>
-              Jitsi Conference Focus, component of Jitsi Meet
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.jirafeau.enable">services.jirafeau.enable</link>
-              A web file repository
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.jitsi-meet.enable">services.jitsi-meet.enable</link>
-              Secure, simple and scalable video conferences
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.jitsi-videobridge.enable">services.jitsi-videobridge.enable</link>
-              Jitsi Videobridge, a WebRTC compatible router
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.jupyterhub.enable">services.jupyterhub.enable</link>
-              Jupyterhub development server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.k3s.enable">services.k3s.enable</link>
-              Lightweight Kubernetes distribution
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.magic-wormhole-mailbox-server.enable">services.magic-wormhole-mailbox-server.enable</link>
-              Magic Wormhole Mailbox Server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.malcontent.enable">services.malcontent.enable</link>
-              Parental Control support
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.matrix-appservice-discord.enable">services.matrix-appservice-discord.enable</link>
-              Matrix and Discord bridge
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.mautrix-telegram.enable">services.mautrix-telegram.enable</link>
-              Matrix-Telegram puppeting/relaybot bridge
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.mirakurun.enable">services.mirakurun.enable</link>
-              Japanese DTV Tuner Server Service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.molly-brown.enable">services.molly-brown.enable</link>
-              Molly-Brown Gemini server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.mullvad-vpn.enable">services.mullvad-vpn.enable</link>
-              Mullvad VPN daemon
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.ncdns.enable">services.ncdns.enable</link>
-              Namecoin to DNS bridge
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.nextdns.enable">services.nextdns.enable</link>
-              NextDNS to DoH Proxy service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.nix-store-gcs-proxy">services.nix-store-gcs-proxy</link>
-              Google storage bucket to be used as a nix store
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.onedrive.enable">services.onedrive.enable</link>
-              OneDrive sync service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.pinnwand.enable">services.pinnwand.enable</link>
-              Pastebin-like service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.pixiecore.enable">services.pixiecore.enable</link>
-              Manage network booting of machines
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.privacyidea.enable">services.privacyidea.enable</link>
-              Privacy authentication server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.quorum.enable">services.quorum.enable</link>
-              Quorum blockchain daemon
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.robustirc-bridge.enable">services.robustirc-bridge.enable</link>
-              RobustIRC bridge
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.rss-bridge.enable">services.rss-bridge.enable</link>
-              Generate RSS and Atom feeds
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.rtorrent.enable">services.rtorrent.enable</link>
-              rTorrent service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.smartdns.enable">services.smartdns.enable</link>
-              SmartDNS DNS server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.sogo.enable">services.sogo.enable</link>
-              SOGo groupware
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.teeworlds.enable">services.teeworlds.enable</link>
-              Teeworlds game server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.torque.mom.enable">services.torque.mom.enable</link>
-              torque computing node
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.torque.server.enable">services.torque.server.enable</link>
-              torque server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.tuptime.enable">services.tuptime.enable</link>
-              A total uptime service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.urserver.enable">services.urserver.enable</link>
-              X11 remote server
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.wasabibackend.enable">services.wasabibackend.enable</link>
-              Wasabi backend service
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.yubikey-agent.enable">services.yubikey-agent.enable</link>
-              Yubikey agent
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-services.zigbee2mqtt.enable">services.zigbee2mqtt.enable</link>
-              Zigbee to MQTT bridge
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.09-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          MariaDB has been updated to 10.4, MariaDB Galera to 26.4.
-          Before you upgrade, it would be best to take a backup of your
-          database. For MariaDB Galera Cluster, see
-          <link xlink:href="https://mariadb.com/kb/en/upgrading-from-mariadb-103-to-mariadb-104-with-galera-cluster/">Upgrading
-          from MariaDB 10.3 to MariaDB 10.4 with Galera Cluster</link>
-          instead. Before doing the upgrade read
-          <link xlink:href="https://mariadb.com/kb/en/upgrading-from-mariadb-103-to-mariadb-104/#incompatible-changes-between-103-and-104">Incompatible
-          Changes Between 10.3 and 10.4</link>. After the upgrade you
-          will need to run <literal>mysql_upgrade</literal>. MariaDB
-          10.4 introduces a number of changes to the authentication
-          process, intended to make things easier and more intuitive.
-          See
-          <link xlink:href="https://mariadb.com/kb/en/authentication-from-mariadb-104/">Authentication
-          from MariaDB 10.4</link>. unix_socket auth plugin does not use
-          a password, and uses the connecting user's UID instead. When a
-          new MariaDB data directory is initialized, two MariaDB users
-          are created and can be used with new unix_socket auth plugin,
-          as well as traditional mysql_native_password plugin:
-          root@localhost and mysql@localhost. To actually use the
-          traditional mysql_native_password plugin method, one must run
-          the following:
-        </para>
-        <programlisting language="bash">
-{
-services.mysql.initialScript = pkgs.writeText &quot;mariadb-init.sql&quot; ''
-  ALTER USER root@localhost IDENTIFIED VIA mysql_native_password USING PASSWORD(&quot;verysecret&quot;);
-'';
-}
-</programlisting>
-        <para>
-          When MariaDB data directory is just upgraded (not
-          initialized), the users are not created or modified.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          MySQL server is now started with additional systemd
-          sandbox/hardening options for better security. The PrivateTmp,
-          ProtectHome, and ProtectSystem options may be problematic when
-          MySQL is attempting to read from or write to your filesystem
-          anywhere outside of its own state directory, for example when
-          calling
-          <literal>LOAD DATA INFILE or SELECT * INTO OUTFILE</literal>.
-          In this scenario a variant of the following may be required: -
-          allow MySQL to read from /home and /tmp directories when using
-          <literal>LOAD DATA INFILE</literal>
-        </para>
-        <programlisting language="bash">
-{
-  systemd.services.mysql.serviceConfig.ProtectHome = lib.mkForce &quot;read-only&quot;;
-}
-</programlisting>
-        <para>
-          - allow MySQL to write to custom folder
-          <literal>/var/data</literal> when using
-          <literal>SELECT * INTO OUTFILE</literal>, assuming the mysql
-          user has write access to <literal>/var/data</literal>
-        </para>
-        <programlisting language="bash">
-{
-  systemd.services.mysql.serviceConfig.ReadWritePaths = [ &quot;/var/data&quot; ];
-}
-</programlisting>
-        <para>
-          The MySQL service no longer runs its
-          <literal>systemd</literal> service startup script as
-          <literal>root</literal> anymore. A dedicated non
-          <literal>root</literal> super user account is required for
-          operation. This means users with an existing MySQL or MariaDB
-          database server are required to run the following SQL
-          statements as a super admin user before upgrading:
-        </para>
-        <programlisting language="SQL">
-CREATE USER IF NOT EXISTS 'mysql'@'localhost' identified with unix_socket;
-GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'localhost' WITH GRANT OPTION;
-</programlisting>
-        <para>
-          If you use MySQL instead of MariaDB please replace
-          <literal>unix_socket</literal> with
-          <literal>auth_socket</literal>. If you have changed the value
-          of
-          <link xlink:href="options.html#opt-services.mysql.user">services.mysql.user</link>
-          from the default of <literal>mysql</literal> to a different
-          user please change <literal>'mysql'@'localhost'</literal> to
-          the corresponding user instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Zabbix now defaults to 5.0, updated from 4.4. Please carefully
-          read through
-          <link xlink:href="https://www.zabbix.com/documentation/current/manual/installation/upgrade/sources">the
-          upgrade guide</link> and apply any changes required. Be sure
-          to take special note of the section on
-          <link xlink:href="https://www.zabbix.com/documentation/current/manual/installation/upgrade_notes_500#enabling_extended_range_of_numeric_float_values">enabling
-          extended range of numeric (float) values</link> as you will
-          need to apply this database migration manually.
-        </para>
-        <para>
-          If you are using Zabbix Server with a MySQL or MariaDB
-          database you should note that using a character set of
-          <literal>utf8</literal> and a collate of
-          <literal>utf8_bin</literal> has become mandatory with this
-          release. See the upstream
-          <link xlink:href="https://support.zabbix.com/browse/ZBX-17357">issue</link>
-          for further discussion. Before upgrading you should check the
-          character set and collation used by your database and ensure
-          they are correct:
-        </para>
-        <programlisting language="SQL">
-SELECT
-  default_character_set_name,
-  default_collation_name
-FROM
-  information_schema.schemata
-WHERE
-  schema_name = 'zabbix';
-</programlisting>
-        <para>
-          If these values are not correct you should take a backup of
-          your database and convert the character set and collation as
-          required. Here is an
-          <link xlink:href="https://www.zabbix.com/forum/zabbix-help/396573-reinstall-after-upgrade?p=396891#post396891">example</link>
-          of how to do so, taken from the Zabbix forums:
-        </para>
-        <programlisting language="SQL">
-ALTER DATABASE `zabbix` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
-
--- the following will produce a list of SQL commands you should subsequently execute
-SELECT CONCAT(&quot;ALTER TABLE &quot;, TABLE_NAME,&quot; CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;&quot;) AS ExecuteTheString
-FROM information_schema.`COLUMNS`
-WHERE table_schema = &quot;zabbix&quot; AND COLLATION_NAME = &quot;utf8_general_ci&quot;;
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          maxx package removed along with
-          <literal>services.xserver.desktopManager.maxx</literal>
-          module. Please migrate to cdesktopenv and
-          <literal>services.xserver.desktopManager.cde</literal> module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.matrix-synapse.enable">matrix-synapse</link>
-          module no longer includes optional dependencies by default,
-          they have to be added through the
-          <link xlink:href="options.html#opt-services.matrix-synapse.plugins">plugins</link>
-          option.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>buildGoModule</literal> now internally creates a
-          vendor directory in the source tree for downloaded modules
-          instead of using go's
-          <link xlink:href="https://golang.org/cmd/go/#hdr-Module_proxy_protocol">module
-          proxy protocol</link>. This storage format is simpler and
-          therefore less likely to break with future versions of go. As
-          a result <literal>buildGoModule</literal> switched from
-          <literal>modSha256</literal> to the
-          <literal>vendorSha256</literal> attribute to pin fetched
-          version data.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Grafana is now built without support for phantomjs by default.
-          Phantomjs support has been
-          <link xlink:href="https://grafana.com/docs/grafana/latest/guides/whats-new-in-v6-4/">deprecated
-          in Grafana</link> and the phantomjs project is
-          <link xlink:href="https://github.com/ariya/phantomjs/issues/15344#issue-302015362">currently
-          unmaintained</link>. It can still be enabled by providing
-          <literal>phantomJsSupport = true</literal> to the package
-          instantiation:
-        </para>
-        <programlisting language="bash">
-{
-  services.grafana.package = pkgs.grafana.overrideAttrs (oldAttrs: rec {
-    phantomJsSupport = true;
-  });
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.supybot.enable">supybot</link>
-          module now uses <literal>/var/lib/supybot</literal> as its
-          default
-          <link xlink:href="options.html#opt-services.supybot.stateDir">stateDir</link>
-          path if <literal>stateVersion</literal> is 20.09 or higher. It
-          also enables a number of
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Sandboxing">systemd
-          sandboxing options</link> which may possibly interfere with
-          some plugins. If this is the case you can disable the options
-          through attributes in
-          <literal>systemd.services.supybot.serviceConfig</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.duosec.skey</literal> option, which
-          stored a secret in the nix store, has been replaced by a new
-          <link xlink:href="options.html#opt-security.duosec.secretKeyFile">security.duosec.secretKeyFile</link>
-          option for better security.
-        </para>
-        <para>
-          <literal>security.duosec.ikey</literal> has been renamed to
-          <link xlink:href="options.html#opt-security.duosec.integrationKey">security.duosec.integrationKey</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>vmware</literal> has been removed from the
-          <literal>services.x11.videoDrivers</literal> defaults. For
-          VMWare guests set
-          <literal>virtualisation.vmware.guest.enable</literal> to
-          <literal>true</literal> which will include the appropriate
-          drivers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The initrd SSH support now uses OpenSSH rather than Dropbear
-          to allow the use of Ed25519 keys and other OpenSSH-specific
-          functionality. Host keys must now be in the OpenSSH format,
-          and at least one pre-generated key must be specified.
-        </para>
-        <para>
-          If you used the
-          <literal>boot.initrd.network.ssh.host*Key</literal> options,
-          you'll get an error explaining how to convert your host keys
-          and migrate to the new
-          <literal>boot.initrd.network.ssh.hostKeys</literal> option.
-          Otherwise, if you don't have any host keys set, you'll need to
-          generate some; see the <literal>hostKeys</literal> option
-          documentation for instructions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Since this release there's an easy way to customize your PHP
-          install to get a much smaller base PHP with only wanted
-          extensions enabled. See the following snippet installing a
-          smaller PHP with the extensions <literal>imagick</literal>,
-          <literal>opcache</literal>, <literal>pdo</literal> and
-          <literal>pdo_mysql</literal> loaded:
-        </para>
-        <programlisting language="bash">
-{
-  environment.systemPackages = [
-    (pkgs.php.withExtensions
-      ({ all, ... }: with all; [
-        imagick
-        opcache
-        pdo
-        pdo_mysql
-      ])
-    )
-  ];
-}
-</programlisting>
-        <para>
-          The default <literal>php</literal> attribute hasn't lost any
-          extensions. The <literal>opcache</literal> extension has been
-          added. All upstream PHP extensions are available under
-          php.extensions.&lt;name?&gt;.
-        </para>
-        <para>
-          All PHP <literal>config</literal> flags have been removed for
-          the following reasons:
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The updated <literal>php</literal> attribute is now easily
-          customizable to your liking by using
-          <literal>php.withExtensions</literal> or
-          <literal>php.buildEnv</literal> instead of writing config
-          files or changing configure flags.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The remaining configuration flags can now be set directly on
-          the <literal>php</literal> attribute. For example, instead of
-        </para>
-        <programlisting language="bash">
-{
-  php.override {
-    config.php.embed = true;
-    config.php.apxs2 = false;
-  }
-}
-</programlisting>
-        <para>
-          you should now write
-        </para>
-        <programlisting language="bash">
-{
-  php.override {
-    embedSupport = true;
-    apxs2Support = false;
-  }
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The ACME module has been overhauled for simplicity and
-          maintainability. Cert generation now implicitly uses the
-          <literal>acme</literal> user, and the
-          <literal>security.acme.certs._name_.user</literal> option has
-          been removed. Instead, certificate access from other services
-          is now managed through group permissions. The module no longer
-          runs lego twice under certain conditions, and will correctly
-          renew certificates if their configuration is changed. Services
-          which reload nginx and httpd after certificate renewal are now
-          properly configured too so you no longer have to do this
-          manually if you are using HTTPS enabled virtual hosts. A
-          mechanism for regenerating certs on demand has also been added
-          and documented.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Gollum received a major update to version 5.x and you may have
-          to change some links in your wiki when migrating from gollum
-          4.x. More information can be found
-          <link xlink:href="https://github.com/gollum/gollum/wiki/5.0-release-notes#migrating-your-wiki">here</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Deluge 2.x was added and is used as default for new NixOS
-          installations where stateVersion is &gt;= 20.09. If you are
-          upgrading from a previous NixOS version, you can set
-          <literal>service.deluge.package = pkgs.deluge-2_x</literal> to
-          upgrade to Deluge 2.x and migrate the state to the new format.
-          Be aware that backwards state migrations are not supported by
-          Deluge.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nginx web server now starting with additional
-          sandbox/hardening options. By default, write access to
-          <literal>/var/log/nginx</literal> and
-          <literal>/var/cache/nginx</literal> is allowed. To allow
-          writing to other folders, use
-          <literal>systemd.services.nginx.serviceConfig.ReadWritePaths</literal>
-        </para>
-        <programlisting language="bash">
-{
-  systemd.services.nginx.serviceConfig.ReadWritePaths = [ &quot;/var/www&quot; ];
-}
-</programlisting>
-        <para>
-          Nginx is also started with the systemd option
-          <literal>ProtectHome = mkDefault true;</literal> which forbids
-          it to read anything from <literal>/home</literal>,
-          <literal>/root</literal> and <literal>/run/user</literal> (see
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=">ProtectHome
-          docs</link> for details). If you require serving files from
-          home directories, you may choose to set e.g.
-        </para>
-        <programlisting language="bash">
-{
-  systemd.services.nginx.serviceConfig.ProtectHome = &quot;read-only&quot;;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The NixOS options <literal>nesting.clone</literal> and
-          <literal>nesting.children</literal> have been deleted, and
-          replaced with named
-          <link xlink:href="options.html#opt-specialisation">specialisation</link>
-          configurations.
-        </para>
-        <para>
-          Replace a <literal>nesting.clone</literal> entry with:
-        </para>
-        <programlisting language="bash">
-{
-  specialisation.example-sub-configuration = {
-    configuration = {
-      ...
-    };
-};
-</programlisting>
-        <para>
-          Replace a <literal>nesting.children</literal> entry with:
-        </para>
-        <programlisting language="bash">
-{
-  specialisation.example-sub-configuration = {
-    inheritParentConfig = false;
-    configuration = {
-      ...
-    };
-};
-</programlisting>
-        <para>
-          To switch to a specialised configuration at runtime you need
-          to run:
-        </para>
-        <programlisting>
-$ sudo /run/current-system/specialisation/example-sub-configuration/bin/switch-to-configuration test
-</programlisting>
-        <para>
-          Before you would have used:
-        </para>
-        <programlisting>
-$ sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The Nginx log directory has been moved to
-          <literal>/var/log/nginx</literal>, the cache directory to
-          <literal>/var/cache/nginx</literal>. The option
-          <literal>services.nginx.stateDir</literal> has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The httpd web server previously started its main process as
-          root privileged, then ran worker processes as a less
-          privileged identity user. This was changed to start all of
-          httpd as a less privileged user (defined by
-          <link xlink:href="options.html#opt-services.httpd.user">services.httpd.user</link>
-          and
-          <link xlink:href="options.html#opt-services.httpd.group">services.httpd.group</link>).
-          As a consequence, all files that are needed for httpd to run
-          (included configuration fragments, SSL certificates and keys,
-          etc.) must now be readable by this less privileged user/group.
-        </para>
-        <para>
-          The default value for
-          <link xlink:href="options.html#opt-services.httpd.mpm">services.httpd.mpm</link>
-          has been changed from <literal>prefork</literal> to
-          <literal>event</literal>. Along with this change the default
-          value for
-          <link xlink:href="options.html#opt-services.httpd.virtualHosts">services.httpd.virtualHosts.&lt;name&gt;.http2</link>
-          has been set to <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>systemd-networkd</literal> option
-          <literal>systemd.network.networks.&lt;name&gt;.dhcp.CriticalConnection</literal>
-          has been removed following upstream systemd's deprecation of
-          the same. It is recommended to use
-          <literal>systemd.network.networks.&lt;name&gt;.networkConfig.KeepConfiguration</literal>
-          instead. See systemd.network 5 for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>systemd-networkd</literal> option
-          <literal>systemd.network.networks._name_.dhcpConfig</literal>
-          has been renamed to
-          <link xlink:href="options.html#opt-systemd.network.networks._name_.dhcpV4Config">systemd.network.networks.<emphasis>name</emphasis>.dhcpV4Config</link>
-          following upstream systemd's documentation change. See
-          systemd.network 5 for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In the <literal>picom</literal> module, several options that
-          accepted floating point numbers encoded as strings (for
-          example
-          <link xlink:href="options.html#opt-services.picom.activeOpacity">services.picom.activeOpacity</link>)
-          have been changed to the (relatively) new native
-          <literal>float</literal> type. To migrate your configuration
-          simply remove the quotes around the numbers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When using <literal>buildBazelPackage</literal> from Nixpkgs,
-          <literal>flat</literal> hash mode is now used for dependencies
-          instead of <literal>recursive</literal>. This is to better
-          allow using hashed mirrors where needed. As a result, these
-          hashes will have changed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The syntax of the PostgreSQL configuration file is now checked
-          at build time. If your configuration includes a file
-          inaccessible inside the build sandbox, set
-          <literal>services.postgresql.checkConfig</literal> to
-          <literal>false</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The rkt module has been removed, it was archived by upstream.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="https://bazaar.canonical.com">Bazaar</link>
-          VCS is unmaintained and, as consequence of the Python 2 EOL,
-          the packages <literal>bazaar</literal> and
-          <literal>bazaarTools</literal> were removed. Breezy, the
-          backward compatible fork of Bazaar (see the
-          <link xlink:href="https://www.jelmer.uk/breezy-intro.html">announcement</link>),
-          was packaged as <literal>breezy</literal> and can be used
-          instead.
-        </para>
-        <para>
-          Regarding Nixpkgs, <literal>fetchbzr</literal>,
-          <literal>nix-prefetch-bzr</literal> and Bazaar support in
-          Hydra will continue to work through Breezy.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In addition to the hostname, the fully qualified domain name
-          (FQDN), which consists of
-          <literal>${networking.hostName}</literal> and
-          <literal>${networking.domain}</literal> is now added to
-          <literal>/etc/hosts</literal>, to allow local FQDN resolution,
-          as used by the <literal>hostname --fqdn</literal> command and
-          other applications that try to determine the FQDN. These new
-          entries take precedence over entries from the DNS which could
-          cause regressions in some very specific setups. Additionally
-          the hostname is now resolved to <literal>127.0.0.2</literal>
-          instead of <literal>127.0.1.1</literal> to be consistent with
-          what <literal>nss-myhostname</literal> (from systemd) returns.
-          The old behaviour can e.g. be restored by using
-          <literal>networking.hosts = lib.mkForce { &quot;127.0.1.1&quot; = [ config.networking.hostName ]; };</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The hostname (<literal>networking.hostName</literal>) must now
-          be a valid DNS label (see RFC 1035, RFC 1123) and as such must
-          not contain the domain part. This means that the hostname must
-          start with a letter or digit, end with a letter or digit, and
-          have as interior characters only letters, digits, and hyphen.
-          The maximum length is 63 characters. Additionally it is
-          recommended to only use lower-case characters. If (e.g. for
-          legacy reasons) a FQDN is required as the Linux kernel network
-          node hostname (<literal>uname --nodename</literal>) the option
-          <literal>boot.kernel.sysctl.&quot;kernel.hostname&quot;</literal>
-          can be used as a workaround (but be aware of the 64 character
-          limit).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The GRUB specific option
-          <literal>boot.loader.grub.extraInitrd</literal> has been
-          replaced with the generic option
-          <literal>boot.initrd.secrets</literal>. This option creates a
-          secondary initrd from the specified files, rather than using a
-          manually created initrd file. Due to an existing bug with
-          <literal>boot.loader.grub.extraInitrd</literal>, it is not
-          possible to directly boot an older generation that used that
-          option. It is still possible to rollback to that generation if
-          the required initrd file has not been deleted.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="https://github.com/okTurtles/dnschain">DNSChain</link>
-          package and NixOS module have been removed from Nixpkgs as the
-          software is unmaintained and can't be built. For more
-          information see issue
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/89205">#89205</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In the <literal>resilio</literal> module,
-          <link xlink:href="options.html#opt-services.resilio.httpListenAddr">services.resilio.httpListenAddr</link>
-          has been changed to listen to <literal>[::1]</literal> instead
-          of <literal>0.0.0.0</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>sslh</literal> has been updated to version
-          <literal>1.21</literal>. The <literal>ssl</literal> probe must
-          be renamed to <literal>tls</literal> in
-          <link xlink:href="options.html#opt-services.sslh.appendConfig">services.sslh.appendConfig</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Users of <link xlink:href="http://openafs.org">OpenAFS
-          1.6</link> must upgrade their services to OpenAFS 1.8! In this
-          release, the OpenAFS package version 1.6.24 is marked broken
-          but can be used during transition to OpenAFS 1.8.x. Use the
-          options
-          <literal>services.openafsClient.packages.module</literal>,
-          <literal>services.openafsClient.packages.programs</literal>
-          and <literal>services.openafsServer.package</literal> to
-          select a different OpenAFS package. OpenAFS 1.6 will be
-          removed in the next release. The package
-          <literal>openafs</literal> and the service options will then
-          silently point to the OpenAFS 1.8 release.
-        </para>
-        <para>
-          See also the OpenAFS
-          <link xlink:href="http://docs.openafs.org/AdminGuide/index.html">Administrator
-          Guide</link> for instructions. Beware of the following when
-          updating servers:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The storage format of the server key has changed and the
-              key must be converted before running the new release.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              When updating multiple database servers, turn off the
-              database servers from the highest IP down to the lowest
-              with resting periods in between. Start up in reverse
-              order. Do not concurrently run database servers working
-              with different OpenAFS releases!
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Update servers first, then clients.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Radicale's default package has changed from 2.x to 3.x. An
-          upgrade checklist can be found
-          <link xlink:href="https://github.com/Kozea/Radicale/blob/3.0.x/NEWS.md#upgrade-checklist">here</link>.
-          You can use the newer version in the NixOS service by setting
-          the <literal>package</literal> to
-          <literal>radicale3</literal>, which is done automatically if
-          <literal>stateVersion</literal> is 20.09 or higher.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>udpt</literal> experienced a complete rewrite from
-          C++ to rust. The configuration format changed from ini to
-          toml. The new configuration documentation can be found at
-          <link xlink:href="https://naim94a.github.io/udpt/config.html">the
-          official website</link> and example configuration is packaged
-          in <literal>${udpt}/share/udpt/udpt.toml</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          We now have a unified
-          <link xlink:href="options.html#opt-services.xserver.displayManager.autoLogin">services.xserver.displayManager.autoLogin</link>
-          option interface to be used for every display-manager in
-          NixOS.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>bitcoind</literal> module has changed to
-          multi-instance, using submodules. Therefore, it is now
-          mandatory to name each instance. To use this new
-          multi-instance config with an existing bitcoind data directory
-          and user, you have to adjust the original config, e.g.:
-        </para>
-        <programlisting language="bash">
-{
-  services.bitcoind = {
-    enable = true;
-    extraConfig = &quot;...&quot;;
-    ...
-  };
-}
-</programlisting>
-        <para>
-          To something similar:
-        </para>
-        <programlisting language="bash">
-{
-  services.bitcoind.mainnet = {
-    enable = true;
-    dataDir = &quot;/var/lib/bitcoind&quot;;
-    user = &quot;bitcoin&quot;;
-    extraConfig = &quot;...&quot;;
-    ...
-  };
-}
-</programlisting>
-        <para>
-          The key settings are:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>dataDir</literal> - to continue using the same
-              data directory.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>user</literal> - to continue using the same user
-              so that bitcoind maintains access to its files.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Graylog introduced a change in the LDAP server certificate
-          validation behaviour for version 3.3.3 which might break
-          existing setups. When updating Graylog from a version before
-          3.3.3 make sure to check the Graylog
-          <link xlink:href="https://www.graylog.org/post/announcing-graylog-v3-3-3">release
-          info</link> for information on how to avoid the issue.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>dokuwiki</literal> module has changed to
-          multi-instance, using submodules. Therefore, it is now
-          mandatory to name each instance. Moreover, forcing SSL by
-          default has been dropped, so <literal>nginx.forceSSL</literal>
-          and <literal>nginx.enableACME</literal> are no longer set to
-          <literal>true</literal>. To continue using your service with
-          the original SSL settings, you have to adjust the original
-          config, e.g.:
-        </para>
-        <programlisting language="bash">
-{
-  services.dokuwiki = {
-    enable = true;
-    ...
-  };
-}
-</programlisting>
-        <para>
-          To something similar:
-        </para>
-        <programlisting language="bash">
-{
-  services.dokuwiki.&quot;mywiki&quot; = {
-    enable = true;
-    nginx = {
-      forceSSL = true;
-      enableACME = true;
-    };
-    ...
-  };
-}
-</programlisting>
-        <para>
-          The base package has also been upgraded to the 2020-07-29
-          &quot;Hogfather&quot; release. Plugins might be incompatible
-          or require upgrading.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.postgresql.dataDir">services.postgresql.dataDir</link>
-          option is now set to
-          <literal>&quot;/var/lib/postgresql/${cfg.package.psqlSchema}&quot;</literal>
-          regardless of your
-          <link xlink:href="options.html#opt-system.stateVersion">system.stateVersion</link>.
-          Users with an existing postgresql install that have a
-          <link xlink:href="options.html#opt-system.stateVersion">system.stateVersion</link>
-          of <literal>17.03</literal> or below should double check what
-          the value of their
-          <link xlink:href="options.html#opt-services.postgresql.dataDir">services.postgresql.dataDir</link>
-          option is (<literal>/var/db/postgresql</literal>) and then
-          explicitly set this value to maintain compatibility:
-        </para>
-        <programlisting language="bash">
-{
-  services.postgresql.dataDir = &quot;/var/db/postgresql&quot;;
-}
-</programlisting>
-        <para>
-          The postgresql module now expects there to be a database super
-          user account called <literal>postgres</literal> regardless of
-          your
-          <link xlink:href="options.html#opt-system.stateVersion">system.stateVersion</link>.
-          Users with an existing postgresql install that have a
-          <link xlink:href="options.html#opt-system.stateVersion">system.stateVersion</link>
-          of <literal>17.03</literal> or below should run the following
-          SQL statements as a database super admin user before
-          upgrading:
-        </para>
-        <programlisting language="SQL">
-CREATE ROLE postgres LOGIN SUPERUSER;
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The USBGuard module now removes options and instead hardcodes
-          values for <literal>IPCAccessControlFiles</literal>,
-          <literal>ruleFiles</literal>, and
-          <literal>auditFilePath</literal>. Audit logs can be found in
-          the journal.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The NixOS module system now evaluates option definitions more
-          strictly, allowing it to detect a larger set of problems. As a
-          result, what previously evaluated may not do so anymore. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/82743#issuecomment-674520472">the
-          PR that changed this</link> for more info.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          For NixOS configuration options, the type
-          <literal>loaOf</literal>, after its initial deprecation in
-          release 20.03, has been removed. In NixOS and Nixpkgs options
-          using this type have been converted to
-          <literal>attrsOf</literal>. For more information on this
-          change have look at these links:
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/1800">issue
-          #1800</link>,
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/63103">PR
-          #63103</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>config.systemd.services.${name}.path</literal> now
-          returns a list of paths instead of a colon-separated string.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Caddy module now uses Caddy v2 by default. Caddy v1 can still
-          be used by setting
-          <link xlink:href="options.html#opt-services.caddy.package">services.caddy.package</link>
-          to <literal>pkgs.caddy1</literal>.
-        </para>
-        <para>
-          New option
-          <link xlink:href="options.html#opt-services.caddy.adapter">services.caddy.adapter</link>
-          has been added.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.jellyfin.enable">jellyfin</link>
-          module will use and stay on the Jellyfin version
-          <literal>10.5.5</literal> if <literal>stateVersion</literal>
-          is lower than <literal>20.09</literal>. This is because
-          significant changes were made to the database schema, and it
-          is highly recommended to backup your instance before
-          upgrading. After making your backup, you can upgrade to the
-          latest version either by setting your
-          <literal>stateVersion</literal> to <literal>20.09</literal> or
-          higher, or set the
-          <literal>services.jellyfin.package</literal> to
-          <literal>pkgs.jellyfin</literal>. If you do not wish to
-          upgrade Jellyfin, but want to change your
-          <literal>stateVersion</literal>, you can set the value of
-          <literal>services.jellyfin.package</literal> to
-          <literal>pkgs.jellyfin_10_5</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.rngd</literal> service is now disabled
-          by default. This choice was made because there's krngd in the
-          linux kernel space making it (for most usecases) functionally
-          redundent.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>hardware.nvidia.optimus_prime.enable</literal>
-          service has been renamed to
-          <literal>hardware.nvidia.prime.sync.enable</literal> and has
-          many new enhancements. Related nvidia prime settings may have
-          also changed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The package nextcloud17 has been removed and nextcloud18 was
-          marked as insecure since both of them will
-          <link xlink:href="https://docs.nextcloud.com/server/19/admin_manual/release_schedule.html">
-          will be EOL (end of life) within the lifetime of 20.09</link>.
-        </para>
-        <para>
-          It's necessary to upgrade to nextcloud19:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              From nextcloud17, you have to upgrade to nextcloud18 first
-              as Nextcloud doesn't allow going multiple major revisions
-              forward in a single upgrade. This is possible by setting
-              <link xlink:href="options.html#opt-services.nextcloud.package">services.nextcloud.package</link>
-              to nextcloud18.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              From nextcloud18, it's possible to directly upgrade to
-              nextcloud19 by setting
-              <link xlink:href="options.html#opt-services.nextcloud.package">services.nextcloud.package</link>
-              to nextcloud19.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The GNOME desktop manager no longer default installs
-          gnome3.epiphany. It was chosen to do this as it has a
-          usability breaking issue (see issue
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/98819">#98819</link>)
-          that makes it unsuitable to be a default app.
-        </para>
-        <note>
-          <para>
-            Issue
-            <link xlink:href="https://github.com/NixOS/nixpkgs/issues/98819">#98819</link>
-            is now fixed and gnome3.epiphany is once again installed by
-            default.
-          </para>
-        </note>
-      </listitem>
-      <listitem>
-        <para>
-          If you want to manage the configuration of wpa_supplicant
-          outside of NixOS you must ensure that none of
-          <link xlink:href="options.html#opt-networking.wireless.networks">networking.wireless.networks</link>,
-          <link xlink:href="options.html#opt-networking.wireless.extraConfig">networking.wireless.extraConfig</link>
-          or
-          <link xlink:href="options.html#opt-networking.wireless.userControlled.enable">networking.wireless.userControlled.enable</link>
-          is being used or <literal>true</literal>. Using any of those
-          options will cause wpa_supplicant to be started with a NixOS
-          generated configuration file instead of your own.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.09-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          SD images are now compressed by default using
-          <literal>zstd</literal>. The compression for ISO images has
-          also been changed to <literal>zstd</literal>, but ISO images
-          are still not compressed by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.journald.rateLimitBurst</literal> was
-          updated from <literal>1000</literal> to
-          <literal>10000</literal> to follow the new upstream systemd
-          default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The notmuch package moves its emacs-related binaries and emacs
-          lisp files to a separate output. They're not part of the
-          default <literal>out</literal> output anymore - if you relied
-          on the <literal>notmuch-emacs-mua</literal> binary or the
-          emacs lisp files, access them via the
-          <literal>notmuch.emacs</literal> output.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Device tree overlay support was improved in
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/79370">#79370</link>
-          and now uses
-          <link xlink:href="options.html#opt-hardware.deviceTree.kernelPackage">hardware.deviceTree.kernelPackage</link>
-          instead of <literal>hardware.deviceTree.base</literal>.
-          <link xlink:href="options.html#opt-hardware.deviceTree.overlays">hardware.deviceTree.overlays</link>
-          configuration was extended to support <literal>.dts</literal>
-          files with symbols. Device trees can now be filtered by
-          setting
-          <link xlink:href="options.html#opt-hardware.deviceTree.filter">hardware.deviceTree.filter</link>
-          option.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default output of <literal>buildGoPackage</literal> is now
-          <literal>$out</literal> instead of <literal>$bin</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>buildGoModule</literal> <literal>doCheck</literal>
-          now defaults to <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Packages built using <literal>buildRustPackage</literal> now
-          use <literal>release</literal> mode for the
-          <literal>checkPhase</literal> by default.
-        </para>
-        <para>
-          Please note that Rust packages utilizing a custom
-          build/install procedure (e.g. by using a
-          <literal>Makefile</literal>) or test suites that rely on the
-          structure of the <literal>target/</literal> directory may
-          break due to those assumptions. For further information,
-          please read the Rust section in the Nixpkgs manual.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The cc- and binutils-wrapper's &quot;infix salt&quot; and
-          <literal>_BUILD_</literal> and <literal>_TARGET_</literal>
-          user infixes have been replaced with with a &quot;suffix
-          salt&quot; and suffixes and <literal>_FOR_BUILD</literal> and
-          <literal>_FOR_TARGET</literal>. This matches the autotools
-          convention for env vars which standard for these things,
-          making interfacing with other tools easier.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Additional Git documentation (HTML and text files) is now
-          available via the <literal>git-doc</literal> package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Default algorithm for ZRAM swap was changed to
-          <literal>zstd</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The installer now enables sshd by default. This improves
-          installation on headless machines especially ARM
-          single-board-computer. To login through ssh, either a password
-          or an ssh key must be set for the root user or the nixos user.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The scripted networking system now uses
-          <literal>.link</literal> files in
-          <literal>/etc/systemd/network</literal> to configure mac
-          address and link MTU, instead of the sometimes buggy
-          <literal>network-link-*</literal> units, which have been
-          removed. Bringing the interface up has been moved to the
-          beginning of the <literal>network-addresses-*</literal> unit.
-          Note this doesn't require <literal>systemd-networkd</literal>
-          - it's udev that parses <literal>.link</literal> files. Extra
-          care needs to be taken in the presence of
-          <link xlink:href="https://wiki.debian.org/NetworkInterfaceNames#THE_.22PERSISTENT_NAMES.22_SCHEME">legacy
-          udev rules</link> to rename interfaces, as MAC Address and MTU
-          defined in these options can only match on the original link
-          name. In such cases, you most likely want to create a
-          <literal>10-*.link</literal> file through
-          <link xlink:href="options.html#opt-systemd.network.links">systemd.network.links</link>
-          and set both name and MAC Address / MTU there.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Grafana received a major update to version 7.x. A plugin is
-          now needed for image rendering support, and plugins must now
-          be signed by default. More information can be found
-          <link xlink:href="https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v7-0">in
-          the Grafana documentation</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>hardware.u2f</literal> module, which was
-          installing udev rules was removed, as udev gained native
-          support to handle FIDO security tokens.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.transmission</literal> module was
-          enhanced with the new options:
-          <link xlink:href="options.html#opt-services.transmission.credentialsFile">services.transmission.credentialsFile</link>,
-          <link xlink:href="options.html#opt-services.transmission.openFirewall">services.transmission.openFirewall</link>,
-          and
-          <link xlink:href="options.html#opt-services.transmission.performanceNetParameters">services.transmission.performanceNetParameters</link>.
-        </para>
-        <para>
-          <literal>transmission-daemon</literal> is now started with
-          additional systemd sandbox/hardening options for better
-          security. Please
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues">report</link>
-          any use case where this is not working well. In particular,
-          the <literal>RootDirectory</literal> option newly set forbids
-          uploading or downloading a torrent outside of the default
-          directory configured at
-          <link xlink:href="options.html#opt-services.transmission.settings">settings.download-dir</link>.
-          If you really need Transmission to access other directories,
-          you must include those directories into the
-          <literal>BindPaths</literal> of the service:
-        </para>
-        <programlisting language="bash">
-{
-  systemd.services.transmission.serviceConfig.BindPaths = [ &quot;/path/to/alternative/download-dir&quot; ];
-}
-</programlisting>
-        <para>
-          Also, connection to the RPC (Remote Procedure Call) of
-          <literal>transmission-daemon</literal> is now only available
-          on the local network interface by default. Use:
-        </para>
-        <programlisting language="bash">
-{
-  services.transmission.settings.rpc-bind-address = &quot;0.0.0.0&quot;;
-}
-</programlisting>
-        <para>
-          to get the previous behavior of listening on all network
-          interfaces.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          With this release <literal>systemd-networkd</literal> (when
-          enabled through
-          <link xlink:href="options.html#opt-networking.useNetworkd">networking.useNetworkd</link>)
-          has it's netlink socket created through a
-          <literal>systemd.socket</literal> unit. This gives us control
-          over socket buffer sizes and other parameters. For larger
-          setups where networkd has to create a lot of (virtual) devices
-          the default buffer size (currently 128MB) is not enough.
-        </para>
-        <para>
-          On a machine with &gt;100 virtual interfaces (e.g., wireguard
-          tunnels, VLANs, …), that all have to be brought up during
-          system startup, the receive buffer size will spike for a brief
-          period. Eventually some of the message will be dropped since
-          there is not enough (permitted) buffer space available.
-        </para>
-        <para>
-          By having <literal>systemd-networkd</literal> start with a
-          netlink socket created by <literal>systemd</literal> we can
-          configure the <literal>ReceiveBufferSize=</literal> parameter
-          in the socket options (i.e.
-          <literal>systemd.sockets.systemd-networkd.socketOptions.ReceiveBufferSize</literal>)
-          without recompiling <literal>systemd-networkd</literal>.
-        </para>
-        <para>
-          Since the actual memory requirements depend on hardware,
-          timing, exact configurations etc. it isn't currently possible
-          to infer a good default from within the NixOS module system.
-          Administrators are advised to monitor the logs of
-          <literal>systemd-networkd</literal> for
-          <literal>rtnl: kernel receive buffer overrun</literal> spam
-          and increase the memory limit as they see fit.
-        </para>
-        <para>
-          Note: Increasing the <literal>ReceiveBufferSize=</literal>
-          doesn't allocate any memory. It just increases the upper bound
-          on the kernel side. The memory allocation depends on the
-          amount of messages that are queued on the kernel side of the
-          netlink socket.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Specifying
-          <link xlink:href="options.html#opt-services.dovecot2.mailboxes">mailboxes</link>
-          in the dovecot2 module as a list is deprecated and will break
-          eval in 21.05. Instead, an attribute-set should be specified
-          where the <literal>name</literal> should be the key of the
-          attribute.
-        </para>
-        <para>
-          This means that a configuration like this
-        </para>
-        <programlisting language="bash">
-{
-  services.dovecot2.mailboxes = [
-    { name = &quot;Junk&quot;;
-      auto = &quot;create&quot;;
-    }
-  ];
-}
-</programlisting>
-        <para>
-          should now look like this:
-        </para>
-        <programlisting language="bash">
-{
-  services.dovecot2.mailboxes = {
-    Junk.auto = &quot;create&quot;;
-  };
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          netbeans was upgraded to 12.0 and now defaults to OpenJDK 11.
-          This might cause problems if your projects depend on packages
-          that were removed in Java 11.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          nextcloud has been updated to
-          <link xlink:href="https://nextcloud.com/blog/nextcloud-hub-brings-productivity-to-home-office/">v19</link>.
-        </para>
-        <para>
-          If you have an existing installation, please make sure that
-          you're on nextcloud18 before upgrading to nextcloud19 since
-          Nextcloud doesn't support upgrades across multiple major
-          versions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nixos-run-vms</literal> script now deletes the
-          previous run machines states on test startup. You can use the
-          <literal>--keep-vm-state</literal> flag to match the previous
-          behaviour and keep the same VM state between different test
-          runs.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-nix.buildMachines">nix.buildMachines</link>
-          option is now type-checked. There are no functional changes,
-          however this may require updating some configurations to use
-          correct types for all attributes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>fontconfig</literal> module stopped generating
-          config and cache files for fontconfig 2.10.x, the
-          <literal>/etc/fonts/fonts.conf</literal> now belongs to the
-          latest fontconfig, just like on other Linux distributions, and
-          we will
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/95358">no
-          longer</link> be versioning the config directories.
-        </para>
-        <para>
-          Fontconfig 2.10.x was removed from Nixpkgs since it hasn’t
-          been used in any Nixpkgs package for years now.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nginx module
-          <literal>nginxModules.fastcgi-cache-purge</literal> renamed to
-          official name <literal>nginxModules.cache-purge</literal>.
-          Nginx module <literal>nginxModules.ngx_aws_auth</literal>
-          renamed to official name
-          <literal>nginxModules.aws-auth</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>defaultPackages</literal> was added. It
-          installs the packages perl, rsync and strace for now. They
-          were added unconditionally to
-          <literal>systemPackages</literal> before, but are not strictly
-          necessary for a minimal NixOS install. You can set it to an
-          empty list to have a more minimal system. Be aware that some
-          functionality might still have an impure dependency on those
-          packages, so things might break.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>undervolt</literal> option no longer needs to
-          apply its settings every 30s. If they still become undone,
-          open an issue and restore the previous behaviour using
-          <literal>undervolt.useTimer</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Agda has been heavily reworked.
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>agda.mkDerivation</literal> has been heavily
-              changed and is now located at agdaPackages.mkDerivation.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              New top-level packages agda and
-              <literal>agda.withPackages</literal> have been added, the
-              second of which sets up agda with access to chosen
-              libraries.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              All agda libraries now live under
-              <literal>agdaPackages</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Many broken libraries have been removed.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          See the
-          <link xlink:href="https://nixos.org/nixpkgs/manual/#agda">new
-          documentation</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>deepin</literal> package set has been removed
-          from nixpkgs. It was a work in progress to package the
-          <link xlink:href="https://www.deepin.org/en/dde/">Deepin
-          Desktop Environment (DDE)</link>, including libraries, tools
-          and applications, and it was still missing a service to launch
-          the desktop environment. It has shown to no longer be a
-          feasible goal due to reasons discussed in
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/94870">issue
-          #94870</link>. The package
-          <literal>netease-cloud-music</literal> has also been removed,
-          as it depends on libraries from deepin.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>opendkim</literal> module now uses systemd
-          sandboxing features to limit the exposure of the system
-          towards the opendkim service.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Kubernetes has been upgraded to 1.19.1, which also means that
-          the golang version to build it has been bumped to 1.15. This
-          may have consequences for your existing clusters and their
-          certificates. Please consider
-          <link xlink:href="https://relnotes.k8s.io/?markdown=93264">
-          the release notes for Kubernetes 1.19 carefully </link> before
-          upgrading.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          For AMD GPUs, Vulkan can now be used by adding
-          <literal>amdvlk</literal> to
-          <literal>hardware.opengl.extraPackages</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Similarly, still for AMD GPUs, the ROCm OpenCL stack can now
-          be used by adding <literal>rocm-opencl-icd</literal> to
-          <literal>hardware.opengl.extraPackages</literal>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-20.09-contributions">
-    <title>Contributions</title>
-    <para>
-      I, Jonathan Ringer, would like to thank the following individuals
-      for their work on nixpkgs. This release could not be done without
-      the hard work of the NixOS community. There were 31282
-      contributions across 1313 contributors.
-    </para>
-    <orderedlist numeration="arabic">
-      <listitem>
-        <para>
-          2288 Mario Rodas
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          1837 Frederik Rietdijk
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          946 Jörg Thalheim
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          925 Maximilian Bosch
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          687 Jonathan Ringer
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          651 Jan Tojnar
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          622 Daniël de Kok
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          605 WORLDofPEACE
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          597 Florian Klink
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          528 José Romildo Malaquias
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          281 volth
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          101 Robert Scott
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          86 Tim Steinbach
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          76 WORLDofPEACE
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          49 Maximilian Bosch
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          42 Thomas Tuegel
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          37 Doron Behar
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          36 Vladimír Čunát
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          27 Jonathan Ringer
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          27 Maciej Krüger
-        </para>
-      </listitem>
-    </orderedlist>
-    <para>
-      I, Jonathan Ringer, would also like to personally thank
-      @WORLDofPEACE for their help in mentoring me on the release
-      process. Special thanks also goes to Thomas Tuegel for helping
-      immensely with stabilizing Qt, KDE, and Plasma5; I would also like
-      to thank Robert Scott for his numerous fixes and pull request
-      reviews.
-    </para>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml
deleted file mode 100644
index fb11b19229e2..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml
+++ /dev/null
@@ -1,1567 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-21.05">
-  <title>Release 21.05 (<quote>Okapi</quote>, 2021.05/31)</title>
-  <para>
-    Support is planned until the end of December 2021, handing over to
-    21.11.
-  </para>
-  <section xml:id="sec-release-21.05-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Core version changes:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              gcc: 9.3.0 -&gt; 10.3.0
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              glibc: 2.30 -&gt; 2.32
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              default linux: 5.4 -&gt; 5.10, all supported kernels
-              available
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              mesa: 20.1.7 -&gt; 21.0.1
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Desktop Environments:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              GNOME: 3.36 -&gt; 40, see its
-              <link xlink:href="https://help.gnome.org/misc/release-notes/40.0/">release
-              notes</link>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Plasma5: 5.18.5 -&gt; 5.21.3
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              kdeApplications: 20.08.1 -&gt; 20.12.3
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              cinnamon: 4.6 -&gt; 4.8.1
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Programming Languages and Frameworks:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              Python optimizations were disabled again. Builds with
-              optimizations enabled are not reproducible. Optimizations
-              can now be enabled with an option.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The linux_latest kernel was updated to the 5.13 series. It
-          currently is not officially supported for use with the zfs
-          filesystem. If you use zfs, you should use a different kernel
-          version (either the LTS kernel, or track a specific one).
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-21.05-new-services">
-    <title>New Services</title>
-    <para>
-      The following new services were added since the last release:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.gnuradio.org/">GNURadio</link>
-          3.8 and 3.9 were
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/82263">finally</link>
-          packaged, along with a rewrite to the Nix expressions,
-          allowing users to override the features upstream supports
-          selecting to compile or not to. Additionally, the attribute
-          <literal>gnuradio</literal> (3.9),
-          <literal>gnuradio3_8</literal> and
-          <literal>gnuradio3_7</literal> now point to an externally
-          wrapped by default derivations, that allow you to also add
-          `extraPythonPackages` to the Python interpreter used by
-          GNURadio. Missing environmental variables needed for
-          operational GUI were also added
-          (<link xlink:href="https://github.com/NixOS/nixpkgs/issues/75478">#75478</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.keycloak.org/">Keycloak</link>,
-          an open source identity and access management server with
-          support for
-          <link xlink:href="https://openid.net/connect/">OpenID
-          Connect</link>, <link xlink:href="https://oauth.net/2/">OAUTH
-          2.0</link> and
-          <link xlink:href="https://en.wikipedia.org/wiki/SAML_2.0">SAML
-          2.0</link>.
-        </para>
-        <para>
-          See the <link linkend="module-services-keycloak">Keycloak
-          section of the NixOS manual</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-services.samba-wsdd.enable">services.samba-wsdd.enable</link>
-          Web Services Dynamic Discovery host daemon
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.discourse.org/">Discourse</link>,
-          a modern and open source discussion platform.
-        </para>
-        <para>
-          See the <link linkend="module-services-discourse">Discourse
-          section of the NixOS manual</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-services.nebula.networks">services.nebula.networks</link>
-          <link xlink:href="https://github.com/slackhq/nebula">Nebula
-          VPN</link>
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-21.05-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <para>
-      When upgrading from a previous release, please be aware of the
-      following incompatible changes:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          GNOME desktop environment was upgraded to 40, see the release
-          notes for
-          <link xlink:href="https://help.gnome.org/misc/release-notes/40.0/">40.0</link>
-          and
-          <link xlink:href="https://help.gnome.org/misc/release-notes/3.38/">3.38</link>.
-          The <literal>gnome3</literal> attribute set has been renamed
-          to <literal>gnome</literal> and so have been the NixOS
-          options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If you are using <literal>services.udev.extraRules</literal>
-          to assign custom names to network interfaces, this may stop
-          working due to a change in the initialisation of dhcpcd and
-          systemd networkd. To avoid this, either move them to
-          <literal>services.udev.initrdRules</literal> or see the new
-          <link linkend="sec-custom-ifnames">Assigning custom
-          names</link> section of the NixOS manual for an example using
-          networkd links.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.hideProcessInformation</literal> module
-          has been removed. It was broken since the switch to
-          cgroups-v2.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>linuxPackages.ati_drivers_x11</literal> kernel
-          modules have been removed. The drivers only supported kernels
-          prior to 4.2, and thus have become obsolete.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>systemConfig</literal> kernel parameter is no
-          longer added to boot loader entries. It has been unused since
-          September 2010, but if do have a system generation from that
-          era, you will now be unable to boot into them.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>systemd-journal2gelf</literal> no longer parses json
-          and expects the receiving system to handle it. How to achieve
-          this with Graylog is described in this
-          <link xlink:href="https://github.com/parse-nl/SystemdJournal2Gelf/issues/10">GitHub
-          issue</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If the <literal>services.dbus</literal> module is enabled,
-          then the user D-Bus session is now always socket activated.
-          The associated options
-          <literal>services.dbus.socketActivated</literal> and
-          <literal>services.xserver.startDbusSession</literal> have
-          therefore been removed and you will receive a warning if they
-          are present in your configuration. This change makes the user
-          D-Bus session available also for non-graphical logins.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>networking.wireless.iwd</literal> module now
-          installs the upstream-provided 80-iwd.link file, which sets
-          the NamePolicy= for all wlan devices to &quot;keep
-          kernel&quot;, to avoid race conditions between iwd and
-          networkd. If you don't want this, you can set
-          <literal>systemd.network.links.&quot;80-iwd&quot; = lib.mkForce {}</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>rubyMinimal</literal> was removed due to being unused
-          and unusable. The default ruby interpreter includes JIT
-          support, which makes it reference it's compiler. Since JIT
-          support is probably needed by some Gems, it was decided to
-          enable this feature with all cc references by default, and
-          allow to build a Ruby derivation without references to cc, by
-          setting <literal>jitSupport = false;</literal> in an overlay.
-          See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/90151">#90151</link>
-          for more info.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Setting
-          <literal>services.openssh.authorizedKeysFiles</literal> now
-          also affects which keys
-          <literal>security.pam.enableSSHAgentAuth</literal> will use.
-          WARNING: If you are using these options in combination do make
-          sure that any key paths you use are present in
-          <literal>services.openssh.authorizedKeysFiles</literal>!
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>fonts.enableFontDir</literal> has been
-          renamed to
-          <link xlink:href="options.html#opt-fonts.fontDir.enable">fonts.fontDir.enable</link>.
-          The path of font directory has also been changed to
-          <literal>/run/current-system/sw/share/X11/fonts</literal>, for
-          consistency with other X11 resources.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A number of options have been renamed in the kicad interface.
-          <literal>oceSupport</literal> has been renamed to
-          <literal>withOCE</literal>, <literal>withOCCT</literal> has
-          been renamed to <literal>withOCC</literal>,
-          <literal>ngspiceSupport</literal> has been renamed to
-          <literal>withNgspice</literal>, and
-          <literal>scriptingSupport</literal> has been renamed to
-          <literal>withScripting</literal>. Additionally,
-          <literal>kicad/base.nix</literal> no longer provides default
-          argument values since these are provided by
-          <literal>kicad/default.nix</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The socket for the <literal>pdns-recursor</literal> module was
-          moved from <literal>/var/lib/pdns-recursor</literal> to
-          <literal>/run/pdns-recursor</literal> to match upstream.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Paperwork was updated to version 2. The on-disk format
-          slightly changed, and it is not possible to downgrade from
-          Paperwork 2 back to Paperwork 1.3. Back your documents up
-          before upgrading. See
-          <link xlink:href="https://forum.openpaper.work/t/paperwork-2-0/112/5">this
-          thread</link> for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PowerDNS has been updated from <literal>4.2.x</literal> to
-          <literal>4.3.x</literal>. Please be sure to review the
-          <link xlink:href="https://doc.powerdns.com/authoritative/upgrading.html#x-to-4-3-0">Upgrade
-          Notes</link> provided by upstream before upgrading. Worth
-          specifically noting is that the service now runs entirely as a
-          dedicated <literal>pdns</literal> user, instead of starting as
-          <literal>root</literal> and dropping privileges, as well as
-          the default <literal>socket-dir</literal> location changing
-          from <literal>/var/lib/powerdns</literal> to
-          <literal>/run/pdns</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>mediatomb</literal> service is now using by
-          default the new and maintained fork <literal>gerbera</literal>
-          package instead of the unmaintained
-          <literal>mediatomb</literal> package. If you want to keep the
-          old behavior, you must declare it with:
-        </para>
-        <programlisting language="bash">
-{
-  services.mediatomb.package = pkgs.mediatomb;
-}
-</programlisting>
-        <para>
-          One new option <literal>openFirewall</literal> has been
-          introduced which defaults to false. If you relied on the
-          service declaration to add the firewall rules itself before,
-          you should now declare it with:
-        </para>
-        <programlisting language="bash">
-{
-  services.mediatomb.openFirewall = true;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          xfsprogs was update from 4.19 to 5.11. It now enables reflink
-          support by default on filesystem creation. Support for
-          reflinks was added with an experimental status to kernel 4.9
-          and deemed stable in kernel 4.16. If you want to be able to
-          mount XFS filesystems created with this release of xfsprogs on
-          kernel releases older than those, you need to format them with
-          <literal>mkfs.xfs -m reflink=0</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The uWSGI server is now built with POSIX capabilities. As a
-          consequence, root is no longer required in emperor mode and
-          the service defaults to running as the unprivileged
-          <literal>uwsgi</literal> user. Any additional capability can
-          be added via the new option
-          <link xlink:href="options.html#opt-services.uwsgi.capabilities">services.uwsgi.capabilities</link>.
-          The previous behaviour can be restored by setting:
-        </para>
-        <programlisting language="bash">
-{
-  services.uwsgi.user = &quot;root&quot;;
-  services.uwsgi.group = &quot;root&quot;;
-  services.uwsgi.instance =
-    {
-      uid = &quot;uwsgi&quot;;
-      gid = &quot;uwsgi&quot;;
-    };
-}
-</programlisting>
-        <para>
-          Another incompatibility from the previous release is that
-          vassals running under a different user or group need to use
-          <literal>immediate-{uid,gid}</literal> instead of the usual
-          <literal>uid,gid</literal> options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          btc1 has been abandoned upstream, and removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          cpp_ethereum (aleth) has been abandoned upstream, and removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          riak-cs package removed along with
-          <literal>services.riak-cs</literal> module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          stanchion package removed along with
-          <literal>services.stanchion</literal> module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          mutt has been updated to a new major version (2.x), which
-          comes with some backward incompatible changes that are
-          described in the
-          <link xlink:href="http://www.mutt.org/relnotes/2.0/">release
-          notes for Mutt 2.0</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>vim</literal> and <literal>neovim</literal> switched
-          to Python 3, dropping all Python 2 support.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-networking.wireguard.interfaces">networking.wireguard.interfaces.&lt;name&gt;.generatePrivateKeyFile</link>,
-          which is off by default, had a <literal>chmod</literal> race
-          condition fixed. As an aside, the parent directory's
-          permissions were widened, and the key files were made
-          owner-writable. This only affects newly created keys. However,
-          if the exact permissions are important for your setup, read
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/121294">#121294</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-boot.zfs.forceImportAll">boot.zfs.forceImportAll</link>
-          previously did nothing, but has been fixed. However its
-          default has been changed to <literal>false</literal> to
-          preserve the existing default behaviour. If you have this
-          explicitly set to <literal>true</literal>, please note that
-          your non-root pools will now be forcibly imported.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          openafs now points to openafs_1_8, which is the new stable
-          release. OpenAFS 1.6 was removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The WireGuard module gained a new option
-          <literal>networking.wireguard.interfaces.&lt;name&gt;.peers.*.dynamicEndpointRefreshSeconds</literal>
-          that implements refreshing the IP of DNS-based endpoints
-          periodically (which WireGuard itself
-          <link xlink:href="https://lists.zx2c4.com/pipermail/wireguard/2017-November/002028.html">cannot
-          do</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          MariaDB has been updated to 10.5. Before you upgrade, it would
-          be best to take a backup of your database and read
-          <link xlink:href="https://mariadb.com/kb/en/upgrading-from-mariadb-104-to-mariadb-105/#incompatible-changes-between-104-and-105">
-          Incompatible Changes Between 10.4 and 10.5</link>. After the
-          upgrade you will need to run <literal>mysql_upgrade</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The TokuDB storage engine dropped in mariadb 10.5 and removed
-          in mariadb 10.6. It is recommended to switch to RocksDB. See
-          also
-          <link xlink:href="https://mariadb.com/kb/en/tokudb/">TokuDB</link>
-          and
-          <link xlink:href="https://jira.mariadb.org/browse/MDEV-19780">MDEV-19780:
-          Remove the TokuDB storage engine</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>openldap</literal> module now has support for
-          OLC-style configuration, users of the
-          <literal>configDir</literal> option may wish to migrate. If
-          you continue to use <literal>configDir</literal>, ensure that
-          <literal>olcPidFile</literal> is set to
-          <literal>/run/slapd/slapd.pid</literal>.
-        </para>
-        <para>
-          As a result, <literal>extraConfig</literal> and
-          <literal>extraDatabaseConfig</literal> are removed. To help
-          with migration, you can convert your
-          <literal>slapd.conf</literal> file to OLC configuration with
-          the following script (find the location of this configuration
-          file by running <literal>systemctl status openldap</literal>,
-          it is the <literal>-f</literal> option.
-        </para>
-        <programlisting>
-$ TMPDIR=$(mktemp -d)
-$ slaptest -f /path/to/slapd.conf -F $TMPDIR
-$ slapcat -F $TMPDIR -n0 -H 'ldap:///???(!(objectClass=olcSchemaConfig))'
-</programlisting>
-        <para>
-          This will dump your current configuration in LDIF format,
-          which should be straightforward to convert into Nix settings.
-          This does not show your schema configuration, as this is
-          unnecessarily verbose for users of the default schemas and
-          <literal>slaptest</literal> is buggy with schemas directly in
-          the config file.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Amazon EC2 and OpenStack Compute (nova) images now re-fetch
-          instance meta data and user data from the instance metadata
-          service (IMDS) on each boot. For example: stopping an EC2
-          instance, changing its user data, and restarting the instance
-          will now cause it to fetch and apply the new user data.
-        </para>
-        <warning>
-          <para>
-            Specifically, <literal>/etc/ec2-metadata</literal> is
-            re-populated on each boot. Some NixOS scripts that read from
-            this directory are guarded to only run if the files they
-            want to manipulate do not already exist, and so will not
-            re-apply their changes if the IMDS response changes.
-            Examples: <literal>root</literal>'s SSH key is only added if
-            <literal>/root/.ssh/authorized_keys</literal> does not
-            exist, and SSH host keys are only set from user data if they
-            do not exist in <literal>/etc/ssh</literal>.
-          </para>
-        </warning>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>rspamd</literal> services is now sandboxed. It is
-          run as a dynamic user instead of root, so secrets and other
-          files may have to be moved or their permissions may have to be
-          fixed. The sockets are now located in
-          <literal>/run/rspamd</literal> instead of
-          <literal>/run</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Enabling the Tor client no longer silently also enables and
-          configures Privoxy, and the
-          <literal>services.tor.client.privoxy.enable</literal> option
-          has been removed. To enable Privoxy, and to configure it to
-          use Tor's faster port, use the following configuration:
-        </para>
-        <programlisting language="bash">
-{
-  opt-services.privoxy.enable = true;
-  opt-services.privoxy.enableTor = true;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.tor</literal> module has a new
-          exhaustively typed
-          <link xlink:href="options.html#opt-services.tor.settings">services.tor.settings</link>
-          option following RFC 0042; backward compatibility with old
-          options has been preserved when aliasing was possible. The
-          corresponding systemd service has been hardened, but there is
-          a chance that the service still requires more permissions, so
-          please report any related trouble on the bugtracker. Onion
-          services v3 are now supported in
-          <link xlink:href="options.html#opt-services.tor.relay.onionServices">services.tor.relay.onionServices</link>.
-          A new
-          <link xlink:href="options.html#opt-services.tor.openFirewall">services.tor.openFirewall</link>
-          option as been introduced for allowing connections on all the
-          TCP ports configured.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The options
-          <literal>services.slurm.dbdserver.storagePass</literal> and
-          <literal>services.slurm.dbdserver.configFile</literal> have
-          been removed. Use
-          <literal>services.slurm.dbdserver.storagePassFile</literal>
-          instead to provide the database password. Extra config options
-          can be given via the option
-          <literal>services.slurm.dbdserver.extraConfig</literal>. The
-          actual configuration file is created on the fly on startup of
-          the service. This avoids that the password gets exposed in the
-          nix store.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>wafHook</literal> hook does not wrap Python
-          anymore. Packages depending on <literal>wafHook</literal> need
-          to include any Python into their
-          <literal>nativeBuildInputs</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Starting with version 1.7.0, the project formerly named
-          <literal>CodiMD</literal> is now named
-          <literal>HedgeDoc</literal>. New installations will no longer
-          use the old name for users, state directories and such, this
-          needs to be considered when moving state to a more recent
-          NixOS installation. Based on
-          <link xlink:href="options.html#opt-system.stateVersion">system.stateVersion</link>,
-          existing installations will continue to work.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The fish-foreign-env package has been replaced with
-          fishPlugins.foreign-env, in which the fish functions have been
-          relocated to the <literal>vendor_functions.d</literal>
-          directory to be loaded automatically.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The prometheus json exporter is now managed by the prometheus
-          community. Together with additional features some backwards
-          incompatibilities were introduced. Most importantly the
-          exporter no longer accepts a fixed command-line parameter to
-          specify the URL of the endpoint serving JSON. It now expects
-          this URL to be passed as an URL parameter, when scraping the
-          exporter's <literal>/probe</literal> endpoint. In the
-          prometheus scrape configuration the scrape target might look
-          like this:
-        </para>
-        <programlisting>
-http://some.json-exporter.host:7979/probe?target=https://example.com/some/json/endpoint
-</programlisting>
-        <para>
-          Existing configuration for the exporter needs to be updated,
-          but can partially be re-used. Documentation is available in
-          the upstream repository and a small example for NixOS is
-          available in the corresponding NixOS test.
-        </para>
-        <para>
-          These changes also affect
-          <link xlink:href="options.html#opt-services.prometheus.exporters.rspamd.enable">services.prometheus.exporters.rspamd.enable</link>,
-          which is just a preconfigured instance of the json exporter.
-        </para>
-        <para>
-          For more information, take a look at the
-          <link xlink:href="https://github.com/prometheus-community/json_exporter">
-          official documentation</link> of the json_exporter.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Androidenv was updated, removing the
-          <literal>includeDocs</literal> and
-          <literal>lldbVersions</literal> arguments. Docs only covered a
-          single version of the Android SDK, LLDB is now bundled with
-          the NDK, and both are no longer available to download from the
-          Android package repositories. Additionally, since the package
-          lists have been updated, some older versions of Android
-          packages may not be bundled. If you depend on older versions
-          of Android packages, we recommend overriding the repo.
-        </para>
-        <para>
-          Android packages are now loaded from a repo.json file created
-          by parsing Android repo XML files. The arguments
-          <literal>repoJson</literal> and <literal>repoXmls</literal>
-          have been added to allow overriding the built-in androidenv
-          repo.json with your own. Additionally, license files are now
-          written to allow compatibility with Gradle-based tools, and
-          the <literal>extraLicenses</literal> argument has been added
-          to accept more SDK licenses if your project requires it. See
-          the androidenv documentation for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The attribute <literal>mpi</literal> is now consistently used
-          to provide a default, system-wide MPI implementation. The
-          default implementation is openmpi, which has been used before
-          by all derivations affects by this change. Note that all
-          packages that have used <literal>mpi ? null</literal> in the
-          input for optional MPI builds, have been changed to the
-          boolean input paramater <literal>useMpi</literal> to enable
-          building with MPI. Building all packages with
-          <literal>mpich</literal> instead of the default
-          <literal>openmpi</literal> can now be achived like this:
-        </para>
-        <programlisting language="bash">
-self: super:
-{
-  mpi = super.mpich;
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The Searx module has been updated with the ability to
-          configure the service declaratively and uWSGI integration. The
-          option <literal>services.searx.configFile</literal> has been
-          renamed to
-          <link xlink:href="options.html#opt-services.searx.settingsFile">services.searx.settingsFile</link>
-          for consistency with the new
-          <link xlink:href="options.html#opt-services.searx.settings">services.searx.settings</link>.
-          In addition, the <literal>searx</literal> uid and gid
-          reservations have been removed since they were not necessary:
-          the service is now running with a dynamically allocated uid.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The libinput module has been updated with the ability to
-          configure mouse and touchpad settings separately. The options
-          in <literal>services.xserver.libinput</literal> have been
-          renamed to
-          <literal>services.xserver.libinput.touchpad</literal>, while
-          there is a new
-          <literal>services.xserver.libinput.mouse</literal> for mouse
-          related configuration.
-        </para>
-        <para>
-          Since touchpad options no longer apply to all devices, you may
-          want to replicate your touchpad configuration in mouse
-          section.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          ALSA OSS emulation
-          (<literal>sound.enableOSSEmulation</literal>) is now disabled
-          by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Thinkfan as been updated to <literal>1.2.x</literal>, which
-          comes with a new YAML based configuration format. For this
-          reason, several NixOS options of the thinkfan module have been
-          changed to non-backward compatible types. In addition, a new
-          <link xlink:href="options.html#opt-services.thinkfan.settings">services.thinkfan.settings</link>
-          option has been added.
-        </para>
-        <para>
-          Please read the
-          <link xlink:href="https://github.com/vmatare/thinkfan#readme">
-          thinkfan documentation</link> before updating.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Adobe Flash Player support has been dropped from the tree. In
-          particular, the following packages no longer support it:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              chromium
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              firefox
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              qt48
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              qt5.qtwebkit
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          Additionally, packages flashplayer and hal-flash were removed
-          along with the <literal>services.flashpolicyd</literal>
-          module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.rngd</literal> module has been removed.
-          It was disabled by default in 20.09 as it was functionally
-          redundant with krngd in the linux kernel. It is not necessary
-          for any device that the kernel recognises as an hardware RNG,
-          as it will automatically run the krngd task to periodically
-          collect random data from the device and mix it into the
-          kernel's RNG.
-        </para>
-        <para>
-          The default SMTP port for GitLab has been changed to
-          <literal>25</literal> from its previous default of
-          <literal>465</literal>. If you depended on this default, you
-          should now set the
-          <link xlink:href="options.html#opt-services.gitlab.smtp.port">services.gitlab.smtp.port</link>
-          option.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default version of ImageMagick has been updated from 6 to
-          7. You can use imagemagick6, imagemagick6_light, and
-          imagemagick6Big if you need the older version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-services.xserver.videoDrivers">services.xserver.videoDrivers</link>
-          no longer uses the deprecated <literal>cirrus</literal> and
-          <literal>vesa</literal> device dependent X drivers by default.
-          It also enables both <literal>amdgpu</literal> and
-          <literal>nouveau</literal> drivers by default now.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>kindlegen</literal> package is gone, because it
-          is no longer supported or hosted by Amazon. Sadly, its
-          replacement, Kindle Previewer, has no Linux support. However,
-          there are other ways to generate MOBI files. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/96439">the
-          discussion</link> for more info.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The apacheKafka packages are now built with version-matched
-          JREs. Versions 2.6 and above, the ones that recommend it, use
-          jdk11, while versions below remain on jdk8. The NixOS service
-          has been adjusted to start the service using the same version
-          as the package, adjustable with the new
-          <link xlink:href="options.html#opt-services.apache-kafka.jre">services.apache-kafka.jre</link>
-          option. Furthermore, the default list of
-          <link xlink:href="options.html#opt-services.apache-kafka.jvmOptions">services.apache-kafka.jvmOptions</link>
-          have been removed. You should set your own according to the
-          <link xlink:href="https://kafka.apache.org/documentation/#java">upstream
-          documentation</link> for your Kafka version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The kodi package has been modified to allow concise addon
-          management. Consider the following configuration from previous
-          releases of NixOS to install kodi, including the
-          kodiPackages.inputstream-adaptive and kodiPackages.vfs-sftp
-          addons:
-        </para>
-        <programlisting language="bash">
-{
-  environment.systemPackages = [
-    pkgs.kodi
-  ];
-
-  nixpkgs.config.kodi = {
-    enableInputStreamAdaptive = true;
-    enableVFSSFTP = true;
-  };
-}
-</programlisting>
-        <para>
-          All Kodi <literal>config</literal> flags have been removed,
-          and as a result the above configuration should now be written
-          as:
-        </para>
-        <programlisting language="bash">
-{
-  environment.systemPackages = [
-    (pkgs.kodi.withPackages (p: with p; [
-      inputstream-adaptive
-      vfs-sftp
-    ]))
-  ];
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>environment.defaultPackages</literal> now includes
-          the nano package. If pkgs.nano is not added to the list, make
-          sure another editor is installed and the
-          <literal>EDITOR</literal> environment variable is set to it.
-          Environment variables can be set using
-          <literal>environment.variables</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.minio.dataDir</literal> changed type to a
-          list of paths, required for specifiyng multiple data
-          directories for using with erasure coding. Currently, the
-          service doesn't enforce nor checks the correct number of paths
-          to correspond to minio requirements.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          All CUDA toolkit versions prior to CUDA 10 have been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The kbdKeymaps package was removed since dvp and neo are now
-          included in kbd. If you want to use the Programmer Dvorak
-          Keyboard Layout, you have to use
-          <literal>dvorak-programmer</literal> in
-          <literal>console.keyMap</literal> now instead of
-          <literal>dvp</literal>. In
-          <literal>services.xserver.xkbVariant</literal> it's still
-          <literal>dvp</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The babeld service is now being run as an unprivileged user.
-          To achieve that the module configures
-          <literal>skip-kernel-setup true</literal> and takes care of
-          setting forwarding and rp_filter sysctls by itself as well as
-          for each interface in
-          <literal>services.babeld.interfaces</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.zigbee2mqtt.config</literal> option has
-          been renamed to
-          <literal>services.zigbee2mqtt.settings</literal> and now
-          follows
-          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
-          0042</link>.
-        </para>
-      </listitem>
-    </itemizedlist>
-    <para>
-      The yadm dotfile manager has been updated from 2.x to 3.x, which
-      has new (XDG) default locations for some data/state files. Most
-      yadm commands will fail and print a legacy path warning (which
-      describes how to upgrade/migrate your repository). If you have
-      scripts, daemons, scheduled jobs, shell profiles, etc. that invoke
-      yadm, expect them to fail or misbehave until you perform this
-      migration and prepare accordingly.
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Instead of determining
-          <literal>services.radicale.package</literal> automatically
-          based on <literal>system.stateVersion</literal>, the latest
-          version is always used because old versions are not officially
-          supported.
-        </para>
-        <para>
-          Furthermore, Radicale's systemd unit was hardened which might
-          break some deployments. In particular, a non-default
-          <literal>filesystem_folder</literal> has to be added to
-          <literal>systemd.services.radicale.serviceConfig.ReadWritePaths</literal>
-          if the deprecated <literal>services.radicale.config</literal>
-          is used.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In the <literal>security.acme</literal> module, use of
-          <literal>--reuse-key</literal> parameter for Lego has been
-          removed. It was introduced for HKPK, but this security feature
-          is now deprecated. It is a better security practice to rotate
-          key pairs instead of always keeping the same. If you need to
-          keep this parameter, you can add it back using
-          <literal>extraLegoRenewFlags</literal> as an option for the
-          appropriate certificate.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-21.05-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>stdenv.lib</literal> has been deprecated and will
-          break eval in 21.11. Please use <literal>pkgs.lib</literal>
-          instead. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/108938">#108938</link>
-          for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.gnuradio.org/">GNURadio</link>
-          has a <literal>pkgs</literal> attribute set, and there's a
-          <literal>gnuradio.callPackage</literal> function that extends
-          <literal>pkgs</literal> with a
-          <literal>mkDerivation</literal>, and a
-          <literal>mkDerivationWith</literal>, like Qt5. Now all
-          <literal>gnuradio.pkgs</literal> are defined with
-          <literal>gnuradio.callPackage</literal> and some packages that
-          depend on gnuradio are defined with this as well.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.privoxy.org/">Privoxy</link> has
-          been updated to version 3.0.32 (See
-          <link xlink:href="https://lists.privoxy.org/pipermail/privoxy-announce/2021-February/000007.html">announcement</link>).
-          Compared to the previous release, Privoxy has gained support
-          for HTTPS inspection (still experimental), Brotli
-          decompression, several new filters and lots of bug fixes,
-          including security ones. In addition, the package is now built
-          with compression and external filters support, which were
-          previously disabled.
-        </para>
-        <para>
-          Regarding the NixOS module, new options for HTTPS inspection
-          have been added and
-          <literal>services.privoxy.extraConfig</literal> has been
-          replaced by the new
-          <link xlink:href="options.html#opt-services.privoxy.settings">services.privoxy.settings</link>
-          (See
-          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
-          0042</link> for the motivation).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://kodi.tv/">Kodi</link> has been
-          updated to version 19.1 &quot;Matrix&quot;. See the
-          <link xlink:href="https://kodi.tv/article/kodi-19-0-matrix-release">announcement</link>
-          for further details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.packagekit.backend</literal> option has
-          been removed as it only supported a single setting which would
-          always be the default. Instead new
-          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
-          0042</link> compliant
-          <link xlink:href="options.html#opt-services.packagekit.settings">services.packagekit.settings</link>
-          and
-          <link xlink:href="options.html#opt-services.packagekit.vendorSettings">services.packagekit.vendorSettings</link>
-          options have been introduced.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://nginx.org">Nginx</link> has been
-          updated to stable version 1.20.0. Now nginx uses the zlib-ng
-          library by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          KDE Gear (formerly KDE Applications) is upgraded to 21.04, see
-          its
-          <link xlink:href="https://kde.org/announcements/gear/21.04/">release
-          notes</link> for details.
-        </para>
-        <para>
-          The <literal>kdeApplications</literal> package set is now
-          <literal>kdeGear</literal>, in keeping with the new name. The
-          old name remains for compatibility, but it is deprecated.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://libreswan.org/">Libreswan</link> has
-          been updated to version 4.4. The package now includes example
-          configurations and manual pages by default. The NixOS module
-          has been changed to use the upstream systemd units and write
-          the configuration in the <literal>/etc/ipsec.d/ </literal>
-          directory. In addition, two new options have been added to
-          specify connection policies
-          (<link xlink:href="options.html#opt-services.libreswan.policies">services.libreswan.policies</link>)
-          and disable send/receive redirects
-          (<link xlink:href="options.html#opt-services.libreswan.disableRedirects">services.libreswan.disableRedirects</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Mailman NixOS module (<literal>services.mailman</literal>)
-          has a new option
-          <link xlink:href="options.html#opt-services.mailman.enablePostfix">services.mailman.enablePostfix</link>,
-          defaulting to true, that controls integration with Postfix.
-        </para>
-        <para>
-          If this option is disabled, default MTA config becomes not set
-          and you should set the options in
-          <literal>services.mailman.settings.mta</literal> according to
-          the desired configuration as described in
-          <link xlink:href="https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html">Mailman
-          documentation</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default-version of <literal>nextcloud</literal> is
-          nextcloud21. Please note that it's <emphasis>not</emphasis>
-          possible to upgrade <literal>nextcloud</literal> across
-          multiple major versions! This means that it's e.g. not
-          possible to upgrade from nextcloud18 to nextcloud20 in a
-          single deploy and most <literal>20.09</literal> users will
-          have to upgrade to nextcloud20 first.
-        </para>
-        <para>
-          The package can be manually upgraded by setting
-          <link xlink:href="options.html#opt-services.nextcloud.package">services.nextcloud.package</link>
-          to nextcloud21.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The setting
-          <link xlink:href="options.html#opt-services.redis.bind">services.redis.bind</link>
-          defaults to <literal>127.0.0.1</literal> now, making Redis
-          listen on the loopback interface only, and not all public
-          network interfaces.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS now emits a deprecation warning if systemd's
-          <literal>StartLimitInterval</literal> setting is used in a
-          <literal>serviceConfig</literal> section instead of in a
-          <literal>unitConfig</literal>; that setting is deprecated and
-          now undocumented for the service section by systemd upstream,
-          but still effective and somewhat buggy there, which can be
-          confusing. See
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/45785">#45785</link>
-          for details.
-        </para>
-        <para>
-          All services should use
-          <link xlink:href="options.html#opt-systemd.services._name_.startLimitIntervalSec">systemd.services.<emphasis>name</emphasis>.startLimitIntervalSec</link>
-          or <literal>StartLimitIntervalSec</literal> in
-          <link xlink:href="options.html#opt-systemd.services._name_.unitConfig">systemd.services.<emphasis>name</emphasis>.unitConfig</link>
-          instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>mediatomb</literal> service declares new options.
-          It also adapts existing options so the configuration
-          generation is now lazy. The existing option
-          <literal>customCfg</literal> (defaults to false), when
-          enabled, stops the service configuration generation
-          completely. It then expects the users to provide their own
-          correct configuration at the right location (whereas the
-          configuration was generated and not used at all before). The
-          new option <literal>transcodingOption</literal> (defaults to
-          no) allows a generated configuration. It makes the mediatomb
-          service pulls the necessary runtime dependencies in the nix
-          store (whereas it was generated with hardcoded values before).
-          The new option <literal>mediaDirectories</literal> allows the
-          users to declare autoscan media directories from their nixos
-          configuration:
-        </para>
-        <programlisting language="bash">
-{
-  services.mediatomb.mediaDirectories = [
-    { path = &quot;/var/lib/mediatomb/pictures&quot;; recursive = false; hidden-files = false; }
-    { path = &quot;/var/lib/mediatomb/audio&quot;; recursive = true; hidden-files = false; }
-  ];
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The Unbound DNS resolver service
-          (<literal>services.unbound</literal>) has been refactored to
-          allow reloading, control sockets and to fix startup ordering
-          issues.
-        </para>
-        <para>
-          It is now possible to enable a local UNIX control socket for
-          unbound by setting the
-          <link xlink:href="options.html#opt-services.unbound.localControlSocketPath">services.unbound.localControlSocketPath</link>
-          option.
-        </para>
-        <para>
-          Previously we just applied a very minimal set of restrictions
-          and trusted unbound to properly drop root privs and
-          capabilities.
-        </para>
-        <para>
-          As of this we are (for the most part) just using the upstream
-          example unit file for unbound. The main difference is that we
-          start unbound as <literal>unbound</literal> user with the
-          required capabilities instead of letting unbound do the chroot
-          &amp; uid/gid changes.
-        </para>
-        <para>
-          The upstream unit configuration this is based on is a lot
-          stricter with all kinds of permissions then our previous
-          variant. It also came with the default of having the
-          <literal>Type</literal> set to <literal>notify</literal>,
-          therefore we are now also using the
-          <literal>unbound-with-systemd</literal> package here. Unbound
-          will start up, read the configuration files and start
-          listening on the configured ports before systemd will declare
-          the unit <literal>active (running)</literal>. This will likely
-          help with startup order and the occasional race condition
-          during system activation where the DNS service is started but
-          not yet ready to answer queries. Services depending on
-          <literal>nss-lookup.target</literal> or
-          <literal>unbound.service</literal> are now be able to use
-          unbound when those targets have been reached.
-        </para>
-        <para>
-          Additionally to the much stricter runtime environment the
-          <literal>/dev/urandom</literal> mount lines we previously had
-          in the code (that randomly failed during the stop-phase) have
-          been removed as systemd will take care of those for us.
-        </para>
-        <para>
-          The <literal>preStart</literal> script is now only required if
-          we enabled the trust anchor updates (which are still enabled
-          by default).
-        </para>
-        <para>
-          Another benefit of the refactoring is that we can now issue
-          reloads via either <literal>pkill -HUP unbound</literal> and
-          <literal>systemctl reload unbound</literal> to reload the
-          running configuration without taking the daemon offline. A
-          prerequisite of this was that unbound configuration is
-          available on a well known path on the file system. We are
-          using the path <literal>/etc/unbound/unbound.conf</literal> as
-          that is the default in the CLI tooling which in turn enables
-          us to use <literal>unbound-control</literal> without passing a
-          custom configuration location.
-        </para>
-        <para>
-          The module has also been reworked to be
-          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
-          0042</link> compliant. As such,
-          <literal>sevices.unbound.extraConfig</literal> has been
-          removed and replaced by
-          <link xlink:href="options.html#opt-services.unbound.settings">services.unbound.settings</link>.
-          <literal>services.unbound.interfaces</literal> has been
-          renamed to
-          <literal>services.unbound.settings.server.interface</literal>.
-        </para>
-        <para>
-          <literal>services.unbound.forwardAddresses</literal> and
-          <literal>services.unbound.allowedAccess</literal> have also
-          been changed to use the new settings interface. You can follow
-          the instructions when executing
-          <literal>nixos-rebuild</literal> to upgrade your configuration
-          to use the new interface.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.dnscrypt-proxy2</literal> module now
-          takes the upstream's example configuration and updates it with
-          the user's settings. An option has been added to restore the
-          old behaviour if you prefer to declare the configuration from
-          scratch.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS now defaults to the unified cgroup hierarchy
-          (cgroupsv2). See the
-          <link xlink:href="https://www.redhat.com/sysadmin/fedora-31-control-group-v2">Fedora
-          Article for 31</link> for details on why this is desirable,
-          and how it impacts containers.
-        </para>
-        <para>
-          If you want to run containers with a runtime that does not yet
-          support cgroupsv2, you can switch back to the old behaviour by
-          setting
-          <link xlink:href="options.html#opt-systemd.enableUnifiedCgroupHierarchy">systemd.enableUnifiedCgroupHierarchy</link>
-          = <literal>false</literal>; and rebooting.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PulseAudio was upgraded to 14.0, with changes to the handling
-          of default sinks. See its
-          <link xlink:href="https://www.freedesktop.org/wiki/Software/PulseAudio/Notes/14.0/">release
-          notes</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GNOME users may wish to delete their
-          <literal>~/.config/pulse</literal> due to the changes to
-          stream routing logic. See
-          <link xlink:href="https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832">PulseAudio
-          bug 832</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The zookeeper package does not provide
-          <literal>zooInspector.sh</literal> anymore, as that
-          &quot;contrib&quot; has been dropped from upstream releases.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In the ACME module, the data used to build the hash for the
-          account directory has changed to accomodate new features to
-          reduce account rate limit issues. This will trigger new
-          account creation on the first rebuild following this update.
-          No issues are expected to arise from this, thanks to the new
-          account creation handling.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-users.users._name_.createHome">users.users.<emphasis>name</emphasis>.createHome</link>
-          now always ensures home directory permissions to be
-          <literal>0700</literal>. Permissions had previously been
-          ignored for already existing home directories, possibly
-          leaving them readable by others. The option's description was
-          incorrect regarding ownership management and has been
-          simplified greatly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When defining a new user, one of
-          <link xlink:href="options.html#opt-users.users._name_.isNormalUser">users.users.<emphasis>name</emphasis>.isNormalUser</link>
-          and
-          <link xlink:href="options.html#opt-users.users._name_.isSystemUser">users.users.<emphasis>name</emphasis>.isSystemUser</link>
-          is now required. This is to prevent accidentally giving a UID
-          above 1000 to system users, which could have unexpected
-          consequences, like running user activation scripts for system
-          users. Note that users defined with an explicit UID below 500
-          are exempted from this check, as
-          <link xlink:href="options.html#opt-users.users._name_.isSystemUser">users.users.<emphasis>name</emphasis>.isSystemUser</link>
-          has no effect for those.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.apparmor</literal> module, for the
-          <link xlink:href="https://gitlab.com/apparmor/apparmor/-/wikis/Documentation">AppArmor</link>
-          Mandatory Access Control system, has been substantialy
-          improved along with related tools, so that module maintainers
-          can now more easily write AppArmor profiles for NixOS. The
-          most notable change on the user-side is the new option
-          <link xlink:href="options.html#opt-security.apparmor.policies">security.apparmor.policies</link>,
-          replacing the previous <literal>profiles</literal> option to
-          provide a way to disable a profile and to select whether to
-          confine in enforce mode (default) or in complain mode (see
-          <literal>journalctl -b --grep apparmor</literal>).
-          Security-minded users may also want to enable
-          <link xlink:href="options.html#opt-security.apparmor.killUnconfinedConfinables">security.apparmor.killUnconfinedConfinables</link>,
-          at the cost of having some of their processes killed when
-          updating to a NixOS version introducing new AppArmor profiles.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The GNOME desktop manager once again installs gnome.epiphany
-          by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          NixOS now generates empty <literal>/etc/netgroup</literal>.
-          <literal>/etc/netgroup</literal> defines network-wide groups
-          and may affect to setups using NIS.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Platforms, like <literal>stdenv.hostPlatform</literal>, no
-          longer have a <literal>platform</literal> attribute. It has
-          been (mostly) flattened away:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>platform.gcc</literal> is now
-              <literal>gcc</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>platform.kernel*</literal> is now
-              <literal>linux-kernel.*</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          Additionally, <literal>platform.kernelArch</literal> moved to
-          the top level as <literal>linuxArch</literal> to match the
-          other <literal>*Arch</literal> variables.
-        </para>
-        <para>
-          The <literal>platform</literal> grouping of these things never
-          meant anything, and was just a historial/implementation
-          artifact that was overdue removal.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.restic</literal> now uses a dedicated cache
-          directory for every backup defined in
-          <literal>services.restic.backups</literal>. The old global
-          cache directory, <literal>/root/.cache/restic</literal>, is
-          now unused and can be removed to free up disk space.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>isync</literal>: The <literal>isync</literal>
-          compatibility wrapper was removed and the Master/Slave
-          terminology has been deprecated and should be replaced with
-          Far/Near in the configuration file.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The nix-gc service now accepts randomizedDelaySec (default: 0)
-          and persistent (default: true) parameters. By default nix-gc
-          will now run immediately if it would have been triggered at
-          least once during the time when the timer was inactive.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>rustPlatform.buildRustPackage</literal> function
-          is split into several hooks: cargoSetupHook to set up
-          vendoring for Cargo-based projects, cargoBuildHook to build a
-          project using Cargo, cargoInstallHook to install a project
-          using Cargo, and cargoCheckHook to run tests in Cargo-based
-          projects. With this change, mixed-language projects can use
-          the relevant hooks within builders other than
-          <literal>buildRustPackage</literal>. However, these changes
-          also required several API changes to
-          <literal>buildRustPackage</literal> itself:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              The <literal>target</literal> argument was removed.
-              Instead, <literal>buildRustPackage</literal> will always
-              use the same target as the C/C++ compiler that is used.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>cargoParallelTestThreads</literal> argument
-              was removed. Parallel tests are now disabled through
-              <literal>dontUseCargoParallelTests</literal>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>rustPlatform.maturinBuildHook</literal> hook was
-          added. This hook can be used with
-          <literal>buildPythonPackage</literal> to build Python packages
-          that are written in Rust and use Maturin as their build tool.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Kubernetes has
-          <link xlink:href="https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/">deprecated
-          docker</link> as container runtime. As a consequence, the
-          Kubernetes module now has support for configuration of custom
-          remote container runtimes and enables containerd by default.
-          Note that containerd is more strict regarding container image
-          OCI-compliance. As an example, images with CMD or ENTRYPOINT
-          defined as strings (not lists) will fail on containerd, while
-          working fine on docker. Please test your setup and container
-          images with containerd prior to upgrading.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The GitLab module now has support for automatic backups. A
-          schedule can be set with the
-          <link xlink:href="options.html#opt-services.gitlab.backup.startAt">services.gitlab.backup.startAt</link>
-          option.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Prior to this release, systemd would also read system units
-          from an undocumented
-          <literal>/etc/systemd-mutable/system</literal> path. This path
-          has been dropped from the defaults. That path (or others) can
-          be re-enabled by adding it to the
-          <link xlink:href="options.html#opt-boot.extraSystemdUnitPaths">boot.extraSystemdUnitPaths</link>
-          list.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PostgreSQL 9.5 is scheduled EOL during the 21.05 life cycle
-          and has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.xfce.org/">Xfce4</link> relies
-          on GIO/GVfs for userspace virtual filesystem access in
-          applications like
-          <link xlink:href="https://docs.xfce.org/xfce/thunar/">thunar</link>
-          and
-          <link xlink:href="https://docs.xfce.org/apps/gigolo/">gigolo</link>.
-          For that to work, the gvfs nixos service is enabled by
-          default, and it can be configured with the specific package
-          that provides GVfs. Until now Xfce4 was setting it to use a
-          lighter version of GVfs (without support for samba). To avoid
-          conflicts with other desktop environments this setting has
-          been dropped. Users that still want it should add the
-          following to their system configuration:
-        </para>
-        <programlisting language="bash">
-{
-  services.gvfs.package = pkgs.gvfs.override { samba = null; };
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          The newly enabled <literal>systemd-pstore.service</literal>
-          now automatically evacuates crashdumps and panic logs from the
-          persistent storage to
-          <literal>/var/lib/systemd/pstore</literal>. This prevents
-          NVRAM from filling up, which ensures the latest diagnostic
-          data is always stored and alleviates problems with writing new
-          boot configurations.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nixpkgs now contains
-          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/118232">automatically
-          packaged GNOME Shell extensions</link> from the
-          <link xlink:href="https://extensions.gnome.org/">GNOME
-          Extensions</link> portal. You can find them, filed by their
-          UUID, under <literal>gnome38Extensions</literal> attribute for
-          GNOME 3.38 and under <literal>gnome40Extensions</literal> for
-          GNOME 40. Finally, the <literal>gnomeExtensions</literal>
-          attribute contains extensions for the latest GNOME Shell
-          version in Nixpkgs, listed under a more human-friendly name.
-          The unqualified attribute scope also contains manually
-          packaged extensions. Note that the automatically packaged
-          extensions are provided for convenience and are not checked or
-          guaranteed to work.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Erlang/OTP versions older than R21 got dropped. We also
-          dropped the cuter package, as it was purely an example of how
-          to build a package. We also dropped <literal>lfe_1_2</literal>
-          as it could not build with R21+. Moving forward, we expect to
-          only support 3 yearly releases of OTP.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
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
deleted file mode 100644
index d9ebbe74d54f..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ /dev/null
@@ -1,2092 +0,0 @@
-<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 (“Porcupine”, 2021/11/30)</title>
-  <itemizedlist spacing="compact">
-    <listitem>
-      <para>
-        Support is planned until the end of June 2022, handing over to
-        22.05.
-      </para>
-    </listitem>
-  </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> is now using
-          <literal>nf_tables</literal> under the hood, by using
-          <literal>iptables-nft</literal>, similar to
-          <link xlink:href="https://wiki.debian.org/nftables#Current_status">Debian</link>
-          and
-          <link xlink:href="https://fedoraproject.org/wiki/Changes/iptables-nft-default">Fedora</link>.
-          This means, <literal>ip[6]tables</literal>,
-          <literal>arptables</literal> and <literal>ebtables</literal>
-          commands will actually show rules from some specific tables in
-          the <literal>nf_tables</literal> kernel subsystem. In case
-          you’re migrating from an older release without rebooting,
-          there might be cases where you end up with iptable rules
-          configured both in the legacy <literal>iptables</literal>
-          kernel backend, as well as in the <literal>nf_tables</literal>
-          backend. This can lead to confusing firewall behaviour. An
-          <literal>iptables-save</literal> after switching will complain
-          about <quote>iptables-legacy tables present</quote>. It’s
-          probably best to reboot after the upgrade, or manually
-          removing all legacy iptables rules (via the
-          <literal>iptables-legacy</literal> package).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          systemd got an <literal>nftables</literal> backend, and
-          configures (networkd) rules in their own
-          <literal>io.systemd.*</literal> tables. Check
-          <literal>nft list ruleset</literal> to see these rules, not
-          <literal>iptables-save</literal> (which only shows
-          <literal>iptables</literal>-created rules.
-        </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
-          default runtime.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>python3</literal> now defaults to Python 3.9, updated
-          from Python 3.8.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PostgreSQL now defaults to major version 13.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          spark now defaults to spark 3, updated from 2. A
-          <link xlink:href="https://spark.apache.org/docs/latest/core-migration-guide.html#upgrading-from-core-24-to-30">migration
-          guide</link> is available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Improvements have been made to the Hadoop module and package:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              HDFS and YARN now support production-ready highly
-              available deployments with automatic failover.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Hadoop now defaults to Hadoop 3, updated from 2.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              JournalNode, ZKFS and HTTPFS services have been added.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Activation scripts can now, 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>
-        <para>
-          KDE Plasma now finally works on Wayland.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          bash now defaults to major version 5.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Systemd was updated to version 249 (from 247).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Pantheon desktop has been updated to version 6. Due to changes
-          of screen locker, if locking doesn’t work for you, please try
-          <literal>gsettings set org.gnome.desktop.lockdown disable-lock-screen false</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>kubernetes-helm</literal> now defaults to 3.7.0,
-          which introduced some breaking changes to the experimental OCI
-          manifest format. See
-          <link xlink:href="https://github.com/helm/community/blob/main/hips/hip-0006.md">HIP
-          6</link> for more details. <literal>helmfile</literal> also
-          defaults to 0.141.0, which is the minimum compatible version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GNOME has been upgraded to 41. Please take a look at their
-          <link xlink:href="https://help.gnome.org/misc/release-notes/41.0/">Release
-          Notes</link> for details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          LXD support was greatly improved:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              building LXD images from configurations is now directly
-              possible with just nixpkgs
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              hydra is now building nixOS LXD images that can be used
-              standalone with full nixos-rebuild support
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          OpenSSH was updated to version 8.8p1
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              This breaks connections to old SSH daemons as ssh-rsa host
-              keys and ssh-rsa public keys that were signed with SHA-1
-              are disabled by default now
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              These can be re-enabled, see the
-              <link xlink:href="https://www.openssh.com/txt/release-8.8">OpenSSH
-              changelog</link> for details
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <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">
-    <title>New Services</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <link xlink:href="https://digint.ch/btrbk/index.html">btrbk</link>,
-          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
-          <link xlink:href="options.html#opt-services.brtbk.instances">services.btrbk</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/xrelkd/clipcat/">clipcat</link>,
-          an X11 clipboard manager written in Rust. Available at
-          <link xlink:href="options.html#opt-services.clipcat.enable">services.clipcat</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/dexidp/dex">dex</link>,
-          an OpenID Connect (OIDC) identity and OAuth 2.0 provider.
-          Available at
-          <link xlink:href="options.html#opt-services.dex.enable">services.dex</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/maxmind/geoipupdate">geoipupdate</link>,
-          a GeoIP database updater from MaxMind. Available as
-          <link xlink:href="options.html#opt-services.geoipupdate.enable">services.geoipupdate</link>.
-        </para>
-      </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.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.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>
-        <para>
-          <link xlink:href="https://sr.ht">sourcehut</link>, a
-          collection of tools useful for software development. Available
-          as
-          <link xlink:href="options.html#opt-services.sourcehut.enable">services.sourcehut</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://download.pureftpd.org/pub/ucarp/README">ucarp</link>,
-          an userspace implementation of the Common Address Redundancy
-          Protocol (CARP). Available as
-          <link xlink:href="options.html#opt-networking.ucarp.enable">networking.ucarp</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Users of flashrom should migrate to
-          <link xlink:href="options.html#opt-programs.flashrom.enable">programs.flashrom.enable</link>
-          and add themselves to the <literal>flashrom</literal> group to
-          be able to access programmers supported by flashrom.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://vikunja.io">vikunja</link>, a to-do
-          list app. Available as
-          <link linkend="opt-services.vikunja.enable">services.vikunja</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/evilsocket/opensnitch">opensnitch</link>,
-          an application firewall. Available as
-          <link linkend="opt-services.opensnitch.enable">services.opensnitch</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.snapraid.it/">snapraid</link>, a
-          backup program for disk arrays. Available as
-          <link linkend="opt-snapraid.enable">snapraid</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/hockeypuck/hockeypuck">Hockeypuck</link>,
-          a OpenPGP Key Server. Available as
-          <link linkend="opt-services.hockeypuck.enable">services.hockeypuck</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/buildkite/buildkite-agent-metrics">buildkite-agent-metrics</link>,
-          a command-line tool for collecting Buildkite agent metrics,
-          now has a Prometheus exporter available as
-          <link linkend="opt-services.prometheus.exporters.buildkite-agent.enable">services.prometheus.exporters.buildkite-agent</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/prometheus/influxdb_exporter">influxdb-exporter</link>
-          a Prometheus exporter that exports metrics received on an
-          InfluxDB compatible endpoint is now available as
-          <link linkend="opt-services.prometheus.exporters.influxdb.enable">services.prometheus.exporters.influxdb</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/matrix-discord/mx-puppet-discord">mx-puppet-discord</link>,
-          a discord puppeting bridge for matrix. Available as
-          <link linkend="opt-services.mx-puppet-discord.enable">services.mx-puppet-discord</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.meshcommander.com/meshcentral2/overview">MeshCentral</link>,
-          a remote administration service (<quote>TeamViewer but
-          self-hosted and with more features</quote>) is now available
-          with a package and a module:
-          <link linkend="opt-services.meshcentral.enable">services.meshcentral.enable</link>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/Arksine/moonraker">moonraker</link>,
-          an API web server for Klipper. Available as
-          <link linkend="opt-services.moonraker.enable">moonraker</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/influxdata/influxdb">influxdb2</link>,
-          a Scalable datastore for metrics, events, and real-time
-          analytics. Available as
-          <link linkend="opt-services.influxdb2.enable">services.influxdb2</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://posativ.org/isso/">isso</link>, a
-          commenting server similar to Disqus. Available as
-          <link linkend="opt-services.isso.enable">isso</link>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.navidrome.org/">navidrome</link>,
-          a personal music streaming server with subsonic-compatible
-          api. Available as
-          <link linkend="opt-services.navidrome.enable">navidrome</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://docs.fluidd.xyz/">fluidd</link>, a
-          Klipper web interface for managing 3d printers using
-          moonraker. Available as
-          <link linkend="opt-services.fluidd.enable">fluidd</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/earnestly/sx">sx</link>,
-          a simple alternative to both xinit and startx for starting a
-          Xorg server. Available as
-          <link linkend="opt-services.xserver.displayManager.sx.enable">services.xserver.displayManager.sx</link>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://postfixadmin.sourceforge.io/">postfixadmin</link>,
-          a web based virtual user administration interface for Postfix
-          mail servers. Available as
-          <link linkend="opt-services.postfixadmin.enable">postfixadmin</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://wiki.servarr.com/prowlarr">prowlarr</link>,
-          an indexer manager/proxy built on the popular arr .net/reactjs
-          base stack
-          <link linkend="opt-services.prowlarr.enable">services.prowlarr</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://sr.ht/~emersion/soju">soju</link>, a
-          user-friendly IRC bouncer. Available as
-          <link xlink:href="options.html#opt-services.soju.enable">services.soju</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://nats.io/">nats</link>, a high
-          performance cloud and edge messaging system. Available as
-          <link linkend="opt-services.nats.enable">services.nats</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://git-scm.com">git</link>, a
-          distributed version control system. Available as
-          <link xlink:href="options.html#opt-programs.git.enable">programs.git</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>,
-          a service which parses incoming
-          <link xlink:href="https://dmarc.org/">DMARC</link> reports and
-          stores or sends them to a downstream service for further
-          analysis. Documented in
-          <link linkend="module-services-parsedmarc">its manual
-          entry</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://spark.apache.org/">spark</link>, a
-          unified analytics engine for large-scale data processing.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/JoseExposito/touchegg">touchegg</link>,
-          a multi-touch gesture recognizer. Available as
-          <link linkend="opt-services.touchegg.enable">services.touchegg</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/pantheon-tweaks/pantheon-tweaks">pantheon-tweaks</link>,
-          an unofficial system settings panel for Pantheon. Available as
-          <link linkend="opt-programs.pantheon-tweaks.enable">programs.pantheon-tweaks</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/DanielOgorchock/joycond">joycond</link>,
-          a service that uses <literal>hid-nintendo</literal> to provide
-          nintendo joycond pairing and better nintendo switch pro
-          controller support.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/opensvc/multipath-tools">multipath</link>,
-          the device mapper multipath (DM-MP) daemon. Available as
-          <link linkend="opt-services.multipath.enable">services.multipath</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.seafile.com/en/home/">seafile</link>,
-          an open source file syncing &amp; sharing software. Available
-          as
-          <link xlink:href="options.html#opt-services.seafile.enable">services.seafile</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/mchehab/rasdaemon">rasdaemon</link>,
-          a hardware error logging daemon. Available as
-          <link linkend="opt-hardware.rasdaemon.enable">hardware.rasdaemon</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>code-server</literal>-module now available
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/xmrig/xmrig">xmrig</link>,
-          a high performance, open source, cross platform RandomX,
-          KawPow, CryptoNight and AstroBWT unified CPU/GPU miner and
-          RandomX benchmark.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Auto nice daemons
-          <link xlink:href="https://github.com/Nefelim4ag/Ananicy">ananicy</link>
-          and
-          <link xlink:href="https://gitlab.com/ananicy-cpp/ananicy-cpp/">ananicy-cpp</link>.
-          Available as
-          <link linkend="opt-services.ananicy.enable">services.ananicy</link>.
-        </para>
-      </listitem>
-      <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">
-    <title>Backward Incompatibilities</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The NixOS VM test framework,
-          <literal>pkgs.nixosTest</literal>/<literal>make-test-python.nix</literal>
-          (<literal>pkgs.testers.nixosTest</literal> since 22.05), now
-          requires detaching commands such as
-          <literal>succeed(&quot;foo &amp;&quot;)</literal> and
-          <literal>succeed(&quot;foo | xclip -i&quot;)</literal> to
-          close stdout. This can be done with a redirect such as
-          <literal>succeed(&quot;foo &gt;&amp;2 &amp;&quot;)</literal>.
-          This breaking change was necessitated by a race condition
-          causing tests to fail or hang. It applies to all methods that
-          invoke commands on the nodes, including
-          <literal>execute</literal>, <literal>succeed</literal>,
-          <literal>fail</literal>,
-          <literal>wait_until_succeeds</literal>,
-          <literal>wait_until_fails</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.wakeonlan</literal> option was removed,
-          and replaced with
-          <literal>networking.interfaces.&lt;name&gt;.wakeOnLan</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>security.wrappers</literal> option now requires
-          to always specify an owner, group and whether the
-          setuid/setgid bit should be set. This is motivated by the fact
-          that before NixOS 21.11, specifying either setuid or setgid
-          but not owner/group resulted in wrappers owned by
-          nobody/nogroup, which is unsafe.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Since <literal>iptables</literal> now uses
-          <literal>nf_tables</literal> backend and
-          <literal>ipset</literal> doesn’t support it, some applications
-          (ferm, shorewall, firehol) may have limited functionality.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>paperless</literal> module and package have been
-          removed. All users should migrate to the successor
-          <literal>paperless-ng</literal> instead. The Paperless project
-          <link xlink:href="https://github.com/the-paperless-project/paperless/commit/9b0063c9731f7c5f65b1852cb8caff97f5e40ba4">has
-          been archived</link> and advises all users to use
-          <literal>paperless-ng</literal> instead.
-        </para>
-        <para>
-          Users can use the <literal>services.paperless-ng</literal>
-          module as a replacement while noting the following
-          incompatibilities:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>services.paperless.ocrLanguages</literal> has no
-              replacement. Users should migrate to
-              <link xlink:href="options.html#opt-services.paperless-ng.extraConfig"><literal>services.paperless-ng.extraConfig</literal></link>
-              instead:
-            </para>
-          </listitem>
-        </itemizedlist>
-        <programlisting language="bash">
-{
-  services.paperless-ng.extraConfig = {
-    # Provide languages as ISO 639-2 codes
-    # separated by a plus (+) sign.
-    # https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
-    PAPERLESS_OCR_LANGUAGE = &quot;deu+eng+jpn&quot;; # German &amp; English &amp; Japanse
-  };
-}
-</programlisting>
-        <itemizedlist>
-          <listitem>
-            <para>
-              If you previously specified
-              <literal>PAPERLESS_CONSUME_MAIL_*</literal> settings in
-              <literal>services.paperless.extraConfig</literal> you
-              should remove those options now. You now
-              <emphasis>must</emphasis> define those settings in the
-              admin interface of paperless-ng.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Option <literal>services.paperless.manage</literal> no
-              longer exists. Use the script at
-              <literal>${services.paperless-ng.dataDir}/paperless-ng-manage</literal>
-              instead. Note that this script only exists after the
-              <literal>paperless-ng</literal> service has been started
-              at least once.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              After switching to the new system configuration you should
-              run the Django management command to reindex your
-              documents and optionally create a user, if you don’t have
-              one already.
-            </para>
-            <para>
-              To do so, enter the data directory (the value of
-              <literal>services.paperless-ng.dataDir</literal>,
-              <literal>/var/lib/paperless</literal> by default), switch
-              to the paperless user and execute the management command
-              like below:
-            </para>
-            <programlisting>
-$ cd /var/lib/paperless
-$ su paperless -s /bin/sh
-$ ./paperless-ng-manage document_index reindex
-# if not already done create a user account, paperless-ng requires a login
-$ ./paperless-ng-manage createsuperuser
-Username (leave blank to use 'paperless'): my-user-name
-Email address: me@example.com
-Password: **********
-Password (again): **********
-Superuser created successfully.
-</programlisting>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>staticjinja</literal> package has been upgraded
-          from 1.0.4 to 4.1.1
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Firefox v91 does not support addons with invalid signature
-          anymore. Firefox ESR needs to be used for nix addon support.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>erigon</literal> ethereum node has moved to a new
-          database format in <literal>2021-05-04</literal>, and requires
-          a full resync
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>erigon</literal> ethereum node has moved it’s
-          database location in <literal>2021-08-03</literal>, users
-          upgrading must manually move their chaindata (see
-          <link xlink:href="https://github.com/ledgerwatch/erigon/releases/tag/v2021.08.03">release
-          notes</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="options.html#opt-users.users._name_.group">users.users.&lt;name&gt;.group</link>
-          no longer defaults to <literal>nogroup</literal>, which was
-          insecure. Out-of-tree modules are likely to require
-          adaptation: instead of
-        </para>
-        <programlisting language="bash">
-{
-  users.users.foo = {
-    isSystemUser = true;
-  };
-}
-</programlisting>
-        <para>
-          also create a group for your user:
-        </para>
-        <programlisting language="bash">
-{
-  users.users.foo = {
-    isSystemUser = true;
-    group = &quot;foo&quot;;
-  };
-  users.groups.foo = {};
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.geoip-updater</literal> was broken and has
-          been replaced by
-          <link xlink:href="options.html#opt-services.geoipupdate.enable">services.geoipupdate</link>.
-        </para>
-      </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>
-      </listitem>
-      <listitem>
-        <para>
-          Those making use of <literal>buildBazelPackage</literal> will
-          need to regenerate the fetch hashes (preferred), or set
-          <literal>fetchConfigured = false;</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>consul</literal> was upgraded to a new major release
-          with breaking changes, see
-          <link xlink:href="https://github.com/hashicorp/consul/releases/tag/v1.10.0">upstream
-          changelog</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          fsharp41 has been removed in preference to use the latest
-          dotnet-sdk
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The following F#-related packages have been removed for being
-          unmaintaned. Please use <literal>fetchNuGet</literal> for
-          specific packages.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              ExtCore
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Fake
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Fantomas
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsCheck
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsCheck262
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsCheckNunit
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpAutoComplete
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCompilerCodeDom
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCompilerService
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCompilerTools
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCore302
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCore3125
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCore4001
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpCore4117
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpData
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpData225
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpDataSQLProvider
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FSharpFormatting
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsLexYacc
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsLexYacc706
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsLexYaccRuntime
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsPickler
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              FsUnit
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Projekt
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Suave
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              UnionArgParser
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              ExcelDnaRegistration
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              MathNetNumerics
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs.x2goserver</literal> is now
-          <literal>services.x2goserver</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The following dotnet-related packages have been removed for
-          being unmaintaned. Please use <literal>fetchNuGet</literal>
-          for specific packages.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              Autofac
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              SystemValueTuple
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              MicrosoftDiaSymReader
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              MicrosoftDiaSymReaderPortablePdb
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              SystemCollectionsImmutable
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              SystemCollectionsImmutable131
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              SystemReflectionMetadata
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              NUnit350
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Deedle
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              ExcelDna
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              GitVersionTree
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              NDeskOptions
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-    </itemizedlist>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The <literal>antlr</literal> package now defaults to the 4.x
-          release instead of the old 2.7.7 version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pulseeffects</literal> package updated to
-          <link xlink:href="https://github.com/wwmm/easyeffects/releases/tag/v6.0.0">version
-          4.x</link> and renamed to <literal>easyeffects</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>libwnck</literal> package now defaults to the 3.x
-          release instead of the old 2.31.0 version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>bitwarden_rs</literal> packages and modules were
-          renamed to <literal>vaultwarden</literal>
-          <link xlink:href="https://github.com/dani-garcia/vaultwarden/discussions/1642">following
-          upstream</link>. More specifically,
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>pkgs.bitwarden_rs</literal>,
-              <literal>pkgs.bitwarden_rs-sqlite</literal>,
-              <literal>pkgs.bitwarden_rs-mysql</literal> and
-              <literal>pkgs.bitwarden_rs-postgresql</literal> were
-              renamed to <literal>pkgs.vaultwarden</literal>,
-              <literal>pkgs.vaultwarden-sqlite</literal>,
-              <literal>pkgs.vaultwarden-mysql</literal> and
-              <literal>pkgs.vaultwarden-postgresql</literal>,
-              respectively.
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  Old names are preserved as aliases for backwards
-                  compatibility, but may be removed in the future.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  The <literal>bitwarden_rs</literal> executable was
-                  also renamed to <literal>vaultwarden</literal> in all
-                  packages.
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>pkgs.bitwarden_rs-vault</literal> was renamed to
-              <literal>pkgs.vaultwarden-vault</literal>.
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  <literal>pkgs.bitwarden_rs-vault</literal> is
-                  preserved as an alias for backwards compatibility, but
-                  may be removed in the future.
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  The static files were moved from
-                  <literal>/usr/share/bitwarden_rs</literal> to
-                  <literal>/usr/share/vaultwarden</literal>.
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>services.bitwarden_rs</literal> config module
-              was renamed to <literal>services.vaultwarden</literal>.
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  <literal>services.bitwarden_rs</literal> is preserved
-                  as an alias for backwards compatibility, but may be
-                  removed in the future.
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>systemd.services.bitwarden_rs</literal>,
-              <literal>systemd.services.backup-bitwarden_rs</literal>
-              and <literal>systemd.timers.backup-bitwarden_rs</literal>
-              were renamed to
-              <literal>systemd.services.vaultwarden</literal>,
-              <literal>systemd.services.backup-vaultwarden</literal> and
-              <literal>systemd.timers.backup-vaultwarden</literal>,
-              respectively.
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  Old names are preserved as aliases for backwards
-                  compatibility, but may be removed in the future.
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>users.users.bitwarden_rs</literal> and
-              <literal>users.groups.bitwarden_rs</literal> were renamed
-              to <literal>users.users.vaultwarden</literal> and
-              <literal>users.groups.vaultwarden</literal>, respectively.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The data directory remains located at
-              <literal>/var/lib/bitwarden_rs</literal>, for backwards
-              compatibility.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-    </itemizedlist>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>yggdrasil</literal> was upgraded to a new major
-          release with breaking changes, see
-          <link xlink:href="https://github.com/yggdrasil-network/yggdrasil-go/releases/tag/v0.4.0">upstream
-          changelog</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>icingaweb2</literal> was upgraded to a new release
-          which requires a manual database upgrade, see
-          <link xlink:href="https://github.com/Icinga/icingaweb2/releases/tag/v2.9.0">upstream
-          changelog</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>isabelle</literal> package has been upgraded from
-          2020 to 2021
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          the <literal>mingw-64</literal> package has been upgraded from
-          6.0.0 to 9.0.0
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>tt-rss</literal> was upgraded to the commit on
-          2021-06-21, which has breaking changes. If you use
-          <literal>services.tt-rss.extraConfig</literal> you should
-          migrate to the <literal>putenv</literal>-style configuration.
-          See
-          <link xlink:href="https://community.tt-rss.org/t/rip-config-php-hello-classes-config-php/4337">this
-          Discourse post</link> in the tt-rss forums for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The following Visual Studio Code extensions were renamed to
-          keep the naming convention uniform.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>bbenoist.Nix</literal> -&gt;
-              <literal>bbenoist.nix</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>CoenraadS.bracket-pair-colorizer</literal> -&gt;
-              <literal>coenraads.bracket-pair-colorizer</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>golang.Go</literal> -&gt;
-              <literal>golang.go</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.uptimed</literal> now uses
-          <literal>/var/lib/uptimed</literal> as its stateDirectory
-          instead of <literal>/var/spool/uptimed</literal>. Make sure to
-          move all files to the new directory.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Deprecated package aliases in <literal>emacs.pkgs.*</literal>
-          have been removed. These aliases were remnants of the old
-          Emacs package infrastructure. We now use exact upstream names
-          wherever possible.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs.neovim.runtime</literal> switched to a
-          <literal>linkFarm</literal> internally, making it impossible
-          to use wildcards in the <literal>source</literal> argument.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>openrazer</literal> and
-          <literal>openrazer-daemon</literal> packages as well as the
-          <literal>hardware.openrazer</literal> module now require users
-          to be members of the <literal>openrazer</literal> group
-          instead of <literal>plugdev</literal>. With this change, users
-          no longer need be granted the entire set of
-          <literal>plugdev</literal> group permissions, which can
-          include permissions other than those required by
-          <literal>openrazer</literal>. This is desirable from a
-          security point of view. The setting
-          <link xlink:href="options.html#opt-services.hardware.openrazer.users"><literal>harware.openrazer.users</literal></link>
-          can be used to add users to the <literal>openrazer</literal>
-          group.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The fontconfig service’s dpi option has been removed.
-          Fontconfig should use Xft settings by default so there’s no
-          need to override one value in multiple places. The user can
-          set DPI via ~/.Xresources properly, or at the system level per
-          monitor, or as a last resort at the system level with
-          <literal>services.xserver.dpi</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>yambar</literal> package has been split into
-          <literal>yambar</literal> and
-          <literal>yambar-wayland</literal>, corresponding to the xorg
-          and wayland backend respectively. Please switch to
-          <literal>yambar-wayland</literal> if you are on wayland.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.minio</literal> module gained an
-          additional option <literal>consoleAddress</literal>, that
-          configures the address and port the web UI is listening, it
-          defaults to <literal>:9001</literal>. To be able to access the
-          web UI this port needs to be opened in the firewall.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>varnish</literal> package was upgraded from 6.3.x
-          to 7.x. <literal>varnish60</literal> for the last LTS release
-          is also still available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>kubernetes</literal> package was upgraded to
-          1.22. The <literal>kubernetes.apiserver.kubeletHttps</literal>
-          option was removed and HTTPS is always used.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The attribute <literal>linuxPackages_latest_hardened</literal>
-          was dropped because the hardened patches lag behind the
-          upstream kernel which made version bumps harder. If you want
-          to use a hardened kernel, please pin it explicitly with a
-          versioned attribute such as
-          <literal>linuxPackages_5_10_hardened</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nomad</literal> package now defaults to a 1.1.x
-          release instead of 1.0.x
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If <literal>exfat</literal> is included in
-          <literal>boot.supportedFilesystems</literal> and when using
-          kernel 5.7 or later, the <literal>exfatprogs</literal>
-          user-space utilities are used instead of
-          <literal>exfat</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>todoman</literal> package was upgraded from 3.9.0
-          to 4.0.0. This introduces breaking changes in the
-          <link xlink:href="https://todoman.readthedocs.io/en/stable/configure.html#configuration-file">configuration
-          file</link> format.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>datadog-agent</literal>,
-          <literal>datadog-integrations-core</literal> and
-          <literal>datadog-process-agent</literal> packages were
-          upgraded from 6.11.2 to 7.30.2, git-2018-09-18 to 7.30.1 and
-          6.11.1 to 7.30.2, respectively. As a result
-          <literal>services.datadog-agent</literal> has had breaking
-          changes to the configuration file. For details, see the
-          <link xlink:href="https://github.com/DataDog/datadog-agent/blob/main/CHANGELOG.rst">upstream
-          changelog</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>opencv2</literal> no longer includes the non-free
-          libraries by default, and consequently
-          <literal>pfstools</literal> no longer includes OpenCV support
-          by default. Both packages now support an
-          <literal>enableUnfree</literal> option to re-enable this
-          functionality.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.xserver.displayManager.defaultSession = &quot;plasma5&quot;</literal>
-          does not work anymore, instead use either
-          <literal>&quot;plasma&quot;</literal> for the Plasma X11
-          session or <literal>&quot;plasmawayland&quot;</literal> for
-          the Plasma Wayland sesison.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>boot.kernelParams</literal> now only accepts one
-          command line parameter per string. This change is aimed to
-          reduce common mistakes like <quote>param = 12</quote>, which
-          would be parsed as 3 parameters.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nix.daemonNiceLevel</literal> and
-          <literal>nix.daemonIONiceLevel</literal> have been removed in
-          favour of the new options
-          <link xlink:href="options.html#opt-nix.daemonCPUSchedPolicy"><literal>nix.daemonCPUSchedPolicy</literal></link>,
-          <link xlink:href="options.html#opt-nix.daemonIOSchedClass"><literal>nix.daemonIOSchedClass</literal></link>
-          and
-          <link xlink:href="options.html#opt-nix.daemonIOSchedPriority"><literal>nix.daemonIOSchedPriority</literal></link>.
-          Please refer to the options documentation and the
-          <literal>sched(7)</literal> and
-          <literal>ioprio_set(2)</literal> man pages for guidance on how
-          to use them.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>coursier</literal> package’s binary was renamed
-          from <literal>coursier</literal> to <literal>cs</literal>.
-          Completions which haven’t worked for a while should now work
-          with the renamed binary. To keep using
-          <literal>coursier</literal>, you can create a shell alias.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.mosquitto</literal> module has been
-          rewritten to support multiple listeners and per-listener
-          configuration. Module configurations from previous releases
-          will no longer work and must be updated.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>fluidsynth_1</literal> attribute has been
-          removed, as this legacy version is no longer needed in
-          nixpkgs. The actively maintained 2.x series is available as
-          <literal>fluidsynth</literal> unchanged.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nextcloud 20 (<literal>pkgs.nextcloud20</literal>) has been
-          dropped because it was EOLed by upstream in 2021-10.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>virtualisation.pathsInNixDB</literal> option was
-          renamed
-          <link xlink:href="options.html#opt-virtualisation.additionalPaths"><literal>virtualisation.additionalPaths</literal></link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.ddclient.password</literal> option was
-          removed, and replaced with
-          <literal>services.ddclient.passwordFile</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default GNAT version has been changed: The
-          <literal>gnat</literal> attribute now points to
-          <literal>gnat11</literal> instead of <literal>gnat9</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>retroArchCores</literal> has been removed. This means
-          that using <literal>nixpkgs.config.retroarch</literal> to
-          customize RetroArch cores is not supported anymore. Instead,
-          use package overrides, for example:
-          <literal>retroarch.override { cores = with libretro; [ citra snes9x ]; };</literal>.
-          Also, <literal>retroarchFull</literal> derivation is available
-          for those who want to have all RetroArch cores available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Linux kernel for security reasons now restricts access to
-          BPF syscalls via <literal>BPF_UNPRIV_DEFAULT_OFF=y</literal>.
-          Unprivileged access can be reenabled via the
-          <literal>kernel.unprivileged_bpf_disabled</literal> sysctl
-          knob.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>/usr</literal> will always be included in the initial
-          ramdisk. See the
-          <literal>fileSystems.&lt;name&gt;.neededForBoot</literal>
-          option. If any files exist under <literal>/usr</literal>
-          (which is not typical for NixOS), they will be included in the
-          initial ramdisk, increasing its size to a possibly problematic
-          extent.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-21.11-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The linux kernel package infrastructure was moved out of
-          <literal>all-packages.nix</literal>, and restructured. Linux
-          related functions and attributes now live under the
-          <literal>pkgs.linuxKernel</literal> attribute set. In
-          particular the versioned <literal>linuxPackages_*</literal>
-          package sets (such as <literal>linuxPackages_5_4</literal>)
-          and kernels from <literal>pkgs</literal> were moved there and
-          now live under <literal>pkgs.linuxKernel.packages.*</literal>.
-          The unversioned ones (such as
-          <literal>linuxPackages_latest</literal>) remain untouched.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In NixOS virtual machines (QEMU), the
-          <literal>virtualisation</literal> module has been updated with
-          new options:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-virtualisation.forwardPorts"><literal>forwardPorts</literal></link>
-              to configure IPv4 port forwarding,
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-virtualisation.sharedDirectories"><literal>sharedDirectories</literal></link>
-              to set up shared host directories,
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-virtualisation.resolution"><literal>resolution</literal></link>
-              to set the screen resolution,
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-virtualisation.useNixStoreImage"><literal>useNixStoreImage</literal></link>
-              to use a disk image for the Nix store instead of 9P.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          In addition, the default
-          <link xlink:href="options.html#opt-virtualisation.msize"><literal>msize</literal></link>
-          parameter in 9P filesystems (including /nix/store and all
-          shared directories) has been increased to 16K for improved
-          performance.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The setting
-          <link xlink:href="options.html#opt-services.openssh.logLevel"><literal>services.openssh.logLevel</literal></link>
-          <literal>&quot;VERBOSE&quot;</literal>
-          <literal>&quot;INFO&quot;</literal>. This brings NixOS in line
-          with upstream and other Linux distributions, and reduces log
-          spam on servers due to bruteforcing botnets.
-        </para>
-        <para>
-          However, if
-          <link xlink:href="options.html#opt-services.fail2ban.enable"><literal>services.fail2ban.enable</literal></link>
-          is <literal>true</literal>, the <literal>fail2ban</literal>
-          will override the verbosity to
-          <literal>&quot;VERBOSE&quot;</literal>, so that
-          <literal>fail2ban</literal> can observe the failed login
-          attempts from the SSH logs.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.xserver.extraLayouts"><literal>services.xserver.extraLayouts</literal></link>
-          no longer cause additional rebuilds when a layout is added or
-          modified.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Sway: The terminal emulator <literal>rxvt-unicode</literal> is
-          no longer installed by default via
-          <literal>programs.sway.extraPackages</literal>. The current
-          default configuration uses <literal>alacritty</literal> (and
-          soon <literal>foot</literal>) so this is only an issue when
-          using a customized configuration and not installing
-          <literal>rxvt-unicode</literal> explicitly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>python3</literal> now defaults to Python 3.9. Python
-          3.9 introduces many deprecation warnings, please look at the
-          <link xlink:href="https://docs.python.org/3/whatsnew/3.9.html">What’s
-          New In Python 3.9 post</link> for more information.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>qtile</literal> hase been updated from
-          <quote>0.16.0</quote> to <quote>0.18.0</quote>, please check
-          <link xlink:href="https://github.com/qtile/qtile/blob/master/CHANGELOG">qtile
-          changelog</link> for changes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>claws-mail</literal> package now references the
-          new GTK+ 3 release branch, major version 4. To use the GTK+ 2
-          releases, one can install the
-          <literal>claws-mail-gtk2</literal> package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The wordpress module provides a new interface which allows to
-          use different webservers with the new option
-          <link xlink:href="options.html#opt-services.wordpress.webserver"><literal>services.wordpress.webserver</literal></link>.
-          Currently <literal>httpd</literal>, <literal>caddy</literal>
-          and <literal>nginx</literal> are supported. The definitions of
-          wordpress sites should now be set in
-          <link xlink:href="options.html#opt-services.wordpress.sites"><literal>services.wordpress.sites</literal></link>.
-        </para>
-        <para>
-          Sites definitions that use the old interface are automatically
-          migrated in the new option. This backward compatibility will
-          be removed in 22.05.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The dokuwiki module provides a new interface which allows to
-          use different webservers with the new option
-          <link xlink:href="options.html#opt-services.dokuwiki.webserver"><literal>services.dokuwiki.webserver</literal></link>.
-          Currently <literal>caddy</literal> and
-          <literal>nginx</literal> are supported. The definitions of
-          dokuwiki sites should now be set in
-          <link xlink:href="options.html#opt-services.dokuwiki.sites"><literal>services.dokuwiki.sites</literal></link>.
-        </para>
-        <para>
-          Sites definitions that use the old interface are automatically
-          migrated in the new option. This backward compatibility will
-          be removed in 22.05.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The order of NSS (host) modules has been brought in line with
-          upstream recommendations:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              The <literal>myhostname</literal> module is placed before
-              the <literal>resolve</literal> (optional) and
-              <literal>dns</literal> entries, but after
-              <literal>file</literal> (to allow overriding via
-              <literal>/etc/hosts</literal> /
-              <literal>networking.extraHosts</literal>, and prevent ISPs
-              with catchall-DNS resolvers from hijacking
-              <literal>.localhost</literal> domains)
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>mymachines</literal> module, which provides
-              hostname resolution for local containers (registered with
-              <literal>systemd-machined</literal>) is placed to the
-              front, to make sure its mappings are preferred over other
-              resolvers.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              If systemd-networkd is enabled, the
-              <literal>resolve</literal> module is placed before
-              <literal>files</literal> and
-              <literal>myhostname</literal>, as it provides the same
-              logic internally, with caching.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>mdns(_minimal)</literal> module has been
-              updated to the new priorities.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          If you use your own NSS host modules, make sure to update your
-          priorities according to these rules:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              NSS modules which should be queried before
-              <literal>resolved</literal> DNS resolution should use
-              mkBefore.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              NSS modules which should be queried after
-              <literal>resolved</literal>, <literal>files</literal> and
-              <literal>myhostname</literal>, but before
-              <literal>dns</literal> should use the default priority
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              NSS modules which should come after <literal>dns</literal>
-              should use mkAfter.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-networking.wireless.enable">networking.wireless</link>
-          module (based on wpa_supplicant) has been heavily reworked,
-          solving a number of issues and adding useful features:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              The automatic discovery of wireless interfaces at boot has
-              been made reliable again (issues
-              <link xlink:href="https://github.com/NixOS/nixpkgs/issues/101963">#101963</link>,
-              <link xlink:href="https://github.com/NixOS/nixpkgs/issues/23196">#23196</link>).
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              WPA3 and Fast BSS Transition (802.11r) are now enabled by
-              default for all networks.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Secrets like pre-shared keys and passwords can now be
-              handled safely, meaning without including them in a
-              world-readable file
-              (<literal>wpa_supplicant.conf</literal> under /nix/store).
-              This is achieved by storing the secrets in a secured
-              <link xlink:href="options.html#opt-networking.wireless.environmentFile">environmentFile</link>
-              and referring to them though environment variables that
-              are expanded inside the configuration.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              With multiple interfaces declared, independent
-              wpa_supplicant daemons are started, one for each interface
-              (the services are named
-              <literal>wpa_supplicant-wlan0</literal>,
-              <literal>wpa_supplicant-wlan1</literal>, etc.).
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The generated <literal>wpa_supplicant.conf</literal> file
-              is now formatted for easier reading.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              A new
-              <link xlink:href="options.html#opt-networking.wireless.scanOnLowSignal">scanOnLowSignal</link>
-              option has been added to facilitate fast roaming between
-              access points (enabled by default).
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              A new
-              <link xlink:href="options.html#opt-networking.wireless.networks._name_.authProtocols">networks.&lt;name&gt;.authProtocols</link>
-              option has been added to change the authentication
-              protocols used when connecting to a network.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-networking.wireless.iwd.enable">networking.wireless.iwd</link>
-          module has a new
-          <link xlink:href="options.html#opt-networking.wireless.iwd.settings">networking.wireless.iwd.settings</link>
-          option.
-        </para>
-      </listitem>
-      <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
-          and didn’t clean up after execution. You can manually look
-          this up for your pools by running
-          <literal>zfs allow your-pool-name</literal> and use
-          <literal>zfs unallow syncoid your-pool-name</literal> to clean
-          this up.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Zfs: <literal>latestCompatibleLinuxPackages</literal> is now
-          exported on the zfs package. One can use
-          <literal>boot.kernelPackages = config.boot.zfs.package.latestCompatibleLinuxPackages;</literal>
-          to always track the latest compatible kernel with a given
-          version of zfs.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Nginx will use the value of
-          <literal>sslTrustedCertificate</literal> if provided for a
-          virtual host, even if <literal>enableACME</literal> is set.
-          This is useful for providers not using the same certificate to
-          sign OCSP responses and server certificates.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.formats.yaml</literal>’s
-          <literal>generate</literal> will not generate JSON anymore,
-          but instead use more of the YAML-specific syntax.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          MariaDB was upgraded from 10.5.x to 10.6.x. Please read the
-          <link xlink:href="https://mariadb.com/kb/en/changes-improvements-in-mariadb-106/">upstream
-          release notes</link> for changes and upgrade instructions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The MariaDB C client library, also known as libmysqlclient or
-          mariadb-connector-c, was upgraded from 3.1.x to 3.2.x. While
-          this should hopefully not have any impact, this upgrade comes
-          with some changes to default behavior, so you might want to
-          review the
-          <link xlink:href="https://mariadb.com/kb/en/changes-and-improvements-in-mariadb-connector-c-32/">upstream
-          release notes</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GNOME desktop environment now enables
-          <literal>QGnomePlatform</literal> as the Qt platform theme,
-          which should avoid crashes when opening file chooser dialogs
-          in Qt apps by using XDG desktop portal. Additionally, it will
-          make the apps fit better visually.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>rofi</literal> has been updated from
-          <quote>1.6.1</quote> to <quote>1.7.0</quote>, one important
-          thing is the removal of the old xresources based configuration
-          setup. Read more
-          <link xlink:href="https://github.com/davatorium/rofi/blob/cb12e6fc058f4a0f4f/Changelog#L1">in
-          rofi’s changelog</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          ipfs now defaults to not listening on you local network. This
-          setting was change as server providers won’t accept port
-          scanning on their private network. If you have several ipfs
-          instances running on a network you own, feel free to change
-          the setting <literal>ipfs.localDiscovery = true;</literal>.
-          localDiscovery enables different instances to discover each
-          other and share data.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lua</literal> and <literal>luajit</literal>
-          interpreters have been patched to avoid looking into /usr/lib
-          directories, thus increasing the purity of the build.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Three new options,
-          <link linkend="opt-xdg.mime.addedAssociations">xdg.mime.addedAssociations</link>,
-          <link linkend="opt-xdg.mime.defaultApplications">xdg.mime.defaultApplications</link>,
-          and
-          <link linkend="opt-xdg.mime.removedAssociations">xdg.mime.removedAssociations</link>
-          have been added to the
-          <link linkend="opt-xdg.mime.enable">xdg.mime</link> module to
-          allow the configuration of
-          <literal>/etc/xdg/mimeapps.list</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Kopia was upgraded from 0.8.x to 0.9.x. Please read the
-          <link xlink:href="https://github.com/kopia/kopia/releases/tag/v0.9.0">upstream
-          release notes</link> for changes and upgrade instructions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>systemd.network</literal> module has gained
-          support for the FooOverUDP link type.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>networking</literal> module has a new
-          <literal>networking.fooOverUDP</literal> option to configure
-          Foo-over-UDP encapsulations.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>networking.sits</literal> now supports Foo-over-UDP
-          encapsulation.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>virtualisation.libvirtd</literal> module has been
-          refactored and updated with new options:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>virtualisation.libvirtd.qemu*</literal> options
-              (e.g.:
-              <literal>virtualisation.libvirtd.qemuRunAsRoot</literal>)
-              were moved to
-              <link xlink:href="options.html#opt-virtualisation.libvirtd.qemu"><literal>virtualisation.libvirtd.qemu</literal></link>
-              submodule,
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              software TPM1/TPM2 support (e.g.: Windows 11 guests)
-              (<link xlink:href="options.html#opt-virtualisation.libvirtd.qemu.swtpm"><literal>virtualisation.libvirtd.qemu.swtpm</literal></link>),
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              custom OVMF package (e.g.:
-              <literal>pkgs.OVMFFull</literal> with HTTP, CSM and Secure
-              Boot support)
-              (<link xlink:href="options.html#opt-virtualisation.libvirtd.qemu.ovmf.package"><literal>virtualisation.libvirtd.qemu.ovmf.package</literal></link>).
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>cawbird</literal> Twitter client now uses its own
-          API keys to count as different application than upstream
-          builds. This is done to evade application-level rate limiting.
-          While existing accounts continue to work, users may want to
-          remove and re-register their account in the client to enjoy a
-          better user experience and benefit from this change.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A new option
-          <literal>services.prometheus.enableReload</literal> has been
-          added which can be enabled to reload the prometheus service
-          when its config file changes instead of restarting.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.prometheus.environmentFile</literal> has
-          been removed since it was causing
-          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/126083">issues</link>
-          and Prometheus now has native support for secret files, i.e.
-          <literal>basic_auth.password_file</literal> and
-          <literal>authorization.credentials_file</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Dokuwiki now supports caddy! However
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              the nginx option has been removed, in the new
-              configuration, please use the
-              <literal>dokuwiki.webserver = &quot;nginx&quot;</literal>
-              instead.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <quote>${hostname}</quote> option has been deprecated,
-              please use
-              <literal>dokuwiki.sites = [ &quot;${hostname}&quot; ]</literal>
-              instead
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link xlink:href="options.html#opt-services.unifi.enable">services.unifi</link>
-          module has been reworked, solving a number of issues. This
-          leads to several user facing changes:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              The <literal>services.unifi.dataDir</literal> option is
-              removed and the data is now always located under
-              <literal>/var/lib/unifi/data</literal>. This is done to
-              make better use of systemd state direcotiry and thus
-              making the service restart more reliable.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The unifi logs can now be found under:
-              <literal>/var/log/unifi</literal> instead of
-              <literal>/var/lib/unifi/logs</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The unifi run directory can now be found under:
-              <literal>/run/unifi</literal> instead of
-              <literal>/var/lib/unifi/run</literal>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security.pam.services.&lt;name&gt;.makeHomeDir</literal>
-          now uses <literal>umask=0077</literal> instead of
-          <literal>umask=0022</literal> when creating the home
-          directory.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Loki has had another release. Some default values have been
-          changed for the configuration and some configuration options
-          have been renamed. For more details, please check
-          <link xlink:href="https://grafana.com/docs/loki/latest/upgrading/#240">the
-          upgrade guide</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>julia</literal> now refers to
-          <literal>julia-stable</literal> instead of
-          <literal>julia-lts</literal>. In practice this means it has
-          been upgraded from <literal>1.0.4</literal> to
-          <literal>1.5.4</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          RetroArch has been upgraded from version
-          <literal>1.8.5</literal> to <literal>1.9.13.2</literal>. Since
-          the previous release was quite old, if you’re having issues
-          after the upgrade, please delete your
-          <literal>$XDG_CONFIG_HOME/retroarch/retroarch.cfg</literal>
-          file.
-        </para>
-      </listitem>
-      <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
deleted file mode 100644
index 02201861234b..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ /dev/null
@@ -1,2829 +0,0 @@
-<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/30)</title>
-  <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>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-<literallayout>Nix has been updated from 2.3 to 2.8. This mainly brings experimental support for Flakes, but also marks the <literal>nix</literal> command as experimental which now has to be enabled via the configuration explicitly. For more information and instructions for upgrades, see the relase notes for <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.4.html">nix-2.4</link>,
-<link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.5.html">nix-2.5</link>, <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.6.html">nix-2.6</link>, <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.7.html">nix-2.7</link> and <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.8.html">nix-2.8</link></literallayout>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>firefox</literal> browser on
-          <literal>x86_64-linux</literal> now makes use of
-          profile-guided optimisation, resulting in a much more
-          responsive browsing experience.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          GNOME has been upgraded to 42. Please take a look at their
-          <link xlink:href="https://release.gnome.org/42/">Release
-          Notes</link> for details. In particular, it replaces gedit
-          with GNOME Text Editor, GNOME Terminal with GNOME Console
-          (formerly King’s Cross) and GNOME Screenshot by a tool
-          integrated into the Shell.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP 8.1 is now available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          systemd services can now set
-          <link linkend="opt-systemd.services">systemd.services.&lt;name&gt;.reloadTriggers</link>
-          instead of <literal>reloadIfChanged</literal> for a more
-          granular distinction between reloads and restarts.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Systemd has been upgraded to the version 250.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Pulseaudio has been updated to version 15.0 and now optionally
-          <link xlink:href="https://www.freedesktop.org/wiki/Software/PulseAudio/Notes/15.0/#supportforldacandaptxbluetoothcodecsplussbcxqsbcwithhigher-qualityparameters">supports
-          additional Bluetooth audio codecs</link> such as aptX or LDAC,
-          with codec switching available in
-          <literal>pavucontrol</literal>. This feature is disabled by
-          default, but can be enabled with the option
-          <literal>hardware.pulseaudio.package = pkgs.pulseaudioFull;</literal>.
-          Existing third-party modules that offered similar functions,
-          such as <literal>pulseaudio-modules-bt</literal> or
-          <literal>pulseaudio-hsphfpd</literal>, are obsolete and have
-          been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PostgreSQL now defaults to major version 14.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Module authors can use
-          <literal>mkRenamedOptionModuleWith</literal> to automate the
-          deprecation cycle without annoying out-of-tree module authors
-          and their users.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default GHC version has been updated from 8.10.7 to 9.0.2.
-          <literal>pkgs.haskellPackages</literal> and
-          <literal>pkgs.ghc</literal> will now use this version by
-          default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The GNOME and Plasma installation CDs now use
-          <literal>pkgs.calamares</literal> and
-          <literal>pkgs.calamares-nixos-extensions</literal> to allow
-          users to easily install and set up NixOS with a GUI.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security.acme.defaults</literal> has been added to
-          simplify the configuration of settings for many certificates
-          at once. This also opens up the option to use DNS-01
-          validation when using <literal>enableACME</literal> web server
-          virtual hosts (e.g.
-          <literal>services.nginx.virtualHosts.*.enableACME</literal>).
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-22.05-new-services">
-    <title>New Services</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <link xlink:href="https://1password.com/">1password</link>,
-          command-lines and graphic interface for 1Password. Available
-          as
-          <link linkend="opt-programs._1password.enable">programs._1password</link>
-          and
-          <link linkend="opt-programs._1password.enable">programs._1password-gui</link>.
-        </para>
-      </listitem>
-      <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://github.com/mbrubeck/agate">agate</link>,
-          a very simple server for the Gemini hypertext protocol.
-          Available as
-          <link linkend="opt-services.agate.enable">services.agate</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/linux-apfs/linux-apfs-rw">apfs</link>,
-          a kernel module for mounting the Apple File System (APFS).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://gitlab.com/DarkElvenAngel/argononed">argonone</link>,
-          a replacement daemon for the Raspberry Pi Argon One power
-          button and cooler. Available at
-          <link xlink:href="options.html#opt-services.hardware.argonone.enable">services.hardware.argonone</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm">ArchiSteamFarm</link>,
-          a C# application with primary purpose of idling Steam cards
-          from multiple accounts simultaneously. Available as
-          <link linkend="opt-services.archisteamfarm.enable">services.archisteamfarm</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://loic-sharma.github.io/BaGet/">BaGet</link>,
-          a lightweight NuGet and symbol server. Available at
-          <link linkend="opt-services.baget.enable">services.baget</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/xddxdd/bird-lg-go">bird-lg</link>,
-          a BGP looking glass for Bird Routing. Available as
-          <link linkend="opt-services.bird-lg.package">services.bird-lg</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://0xerr0r.github.io/blocky/">blocky</link>,
-          fast and lightweight DNS proxy as ad-blocker for local network
-          with many features. Available as
-          <link linkend="opt-services.blocky.enable">services.blocky</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/kissgyorgy/cloudflare-dyndns">cloudflare-dyndns</link>,
-          CloudFlare Dynamic DNS client. Available as
-          <link linkend="opt-services.cloudflare-dyndns.enable">services.cloudflare-dyndns</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://corosync.github.io/corosync/">Corosync</link>
-          and
-          <link xlink:href="https://clusterlabs.org/pacemaker/">Pacemaker</link>,
-          A open-source high availability resource manager. Available as
-          <link linkend="opt-services.corosync.enable">services.corosync</link>
-          and
-          <link linkend="opt-services.pacemaker.enable">services.pacemaker</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/lakinduakash/linux-wifi-hotspot">create_ap</link>,
-          a module for creating wifi hotspots using the program
-          linux-wifi-hotspot. Available as
-          <link linkend="opt-services.create_ap.enable">services.create_ap</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.envoyproxy.io/">Envoy</link>, a
-          high-performance reverse proxy. Available as
-          <link linkend="opt-services.envoy.enable">services.envoy</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://ergo.chat">ergochat</link>, a modern
-          IRC with IRCv3 features. Available as
-          <link linkend="opt-services.ergochat.enable">services.ergochat</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/audreyt/ethercalc">ethercalc</link>,
-          an online collaborative spreadsheet. Available as
-          <link linkend="opt-services.ethercalc.enable">services.ethercalc</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://frrouting.org/">FRRouting</link>, a
-          popular suite of Internet routing protocol daemons (BGP, BFD,
-          OSPF, IS-IS, VRRP and others). Available as
-          <link linkend="opt-services.frr.babel.enable">services.frr</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://grafana.com/oss/mimir/">Grafana
-          Mimir</link>, an open source, horizontally scalable, highly
-          available, multi-tenant, long-term storage for Prometheus.
-          Available as
-          <link linkend="opt-services.mimir.enable">services.mimir</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://hastebin.com/about.md">Haste</link>,
-          a pastebin written in node.js. Available as
-          <link linkend="opt-services.haste-server.enable">services.haste</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/juanfont/headscale">headscale</link>,
-          an Open Source implementation of the
-          <link xlink:href="https://tailscale.io">Tailscale</link>
-          Control Server. Available as
-          <link linkend="opt-services.headscale.enable">services.headscale</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/hifi/heisenbridge">heisenbridge</link>,
-          a bouncer-style Matrix IRC bridge. Available as
-          <link linkend="opt-services.heisenbridge.enable">services.heisenbridge</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/aarond10/https_dns_proxy">https-dns-proxy</link>,
-          DNS to DNS over HTTPS (DoH) proxy. Available as
-          <link linkend="opt-services.https-dns-proxy.enable">services.https-dns-proxy</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/sezanzeb/input-remapper">input-remapper</link>,
-          an easy to use tool to change the mapping of your input device
-          buttons. Available at
-          <link linkend="opt-services.input-remapper.enable">services.input-remapper</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://invoiceplane.com">InvoicePlane</link>,
-          web application for managing and creating invoices. Available
-          at
-          <link linkend="opt-services.invoiceplane.sites._name_.enable">services.invoiceplane</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://userbase.kde.org/K3b">k3b</link>,
-          the KDE disk burning application. Available as
-          <link linkend="opt-programs.k3b.enable">programs.k3b</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.scorchworks.com/K40whisperer/k40whisperer.html">K40-Whisperer</link>,
-          a program to control cheap Chinese laser cutters. Available as
-          <link linkend="opt-programs.k40-whisperer.enable">programs.k40-whisperer.enable</link>.
-          Users must add themselves to the <literal>k40</literal> group
-          to be able to access the device.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://kanidm.github.io/kanidm/stable/">kanidm</link>,
-          an identity management server written in Rust. Available as
-          <link linkend="opt-services.kanidm.enableServer">services.kanidm</link>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://maddy.email/">Maddy</link>, a free
-          an open source mail server. Availabe as
-          <link linkend="opt-services.maddy.enable">services.maddy</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://conduit.rs/">matrix-conduit</link>,
-          a simple, fast and reliable chat server powered by matrix.
-          Available as
-          <link xlink:href="option.html#opt-services.matrix-conduit.enable">services.matrix-conduit</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://moosefs.com">Moosefs</link>, fault
-          tolerant petabyte distributed file system. Available as
-          <link linkend="opt-services.moosefs.master.enable">moosefs</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/mozilla-mobile/mozilla-vpn-client">mozillavpn</link>,
-          the client for the
-          <link xlink:href="https://vpn.mozilla.org/">Mozilla VPN</link>
-          service. Available as
-          <link linkend="opt-services.mozillavpn.enable">services.mozillavpn</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/mgumz/mtr-exporter">mtr-exporter</link>,
-          a Prometheus exporter for mtr metrics. Available as
-          <link linkend="opt-services.mtr-exporter.enable">services.mtr-exporter</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://nbd.sourceforge.io/">nbd</link>, a
-          Network Block Device server. Available as
-          <link linkend="opt-services.nbd.server.enable">services.nbd</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/netbox-community/netbox">netbox</link>,
-          infrastructure resource modeling (IRM) tool. Available as
-          <link linkend="opt-services.netbox.enable">services.netbox</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/vvilhonen/nethoscope">nethoscope</link>,
-          listen to your network traffic. Available as
-          <link linkend="opt-programs.nethoscope.enable">programs.nethoscope</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://nifi.apache.org">nifi</link>, an
-          easy to use, powerful, and reliable system to process and
-          distribute data. Available as
-          <link linkend="opt-services.nifi.enable">services.nifi</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/Mic92/nix-ld">nix-ld</link>,
-          Run unpatched dynamic binaries on NixOS. Available as
-          <link linkend="opt-programs.nix-ld.enable">programs.nix-ld</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="http://www.nncpgo.org">NNCP</link>, NNCP
-          (Node to Node copy) utilities and configuration, Available as
-          <link linkend="opt-programs.nncp.enable">programs.nncp</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/postgres/pgadmin4">pgadmin4</link>,
-          an admin interface for the PostgreSQL database. Available at
-          <link linkend="opt-services.pgadmin.enable">services.pgadmin</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 linkend="opt-services.powerdns-admin.enable">services.powerdns-admin</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/prometheus-pve/prometheus-pve-exporter">prometheus-pve-exporter</link>,
-          a tool that exposes information from the Proxmox VE API for
-          use by Prometheus. Available as
-          <link linkend="opt-services.prometheus.exporters.pve.enable">services.prometheus.exporters.pve</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/ThomasLeister/prosody-filer">prosody-filer</link>,
-          a server for handling XMPP HTTP Upload requests. Available at
-          <link linkend="opt-services.prosody-filer.enable">services.prosody-filer</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://public-inbox.org">Public
-          Inbox</link>, an <quote>archives first</quote> approach to
-          mailing lists. Available as
-          <link linkend="opt-services.public-inbox.enable">services.public-inbox</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/fleaz/r53-ddns">r53-ddns</link>,
-          a small tool to run your own DDNS service via AWS Route53.
-          Available as
-          <link linkend="opt-services.r53-ddns.enable">services.r53-ddns</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://ddvk.github.io/rmfakecloud/">rmfakecloud</link>,
-          a clone of the cloud sync the remarkable tablet. Available as
-          <link linkend="opt-services.rmfakecloud.enable">services.rmfakecloud</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 linkend="opt-virtualisation.docker.rootless.enable">virtualisation.docker.rootless.enable</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.rstudio.com/products/rstudio/#rstudio-server">rstudio-server</link>,
-          a browser-based version of the RStudio IDE for the R
-          programming language. Available as
-          <link linkend="opt-services.rstudio-server.enable">services.rstudio-server</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/aler9/rtsp-simple-server">rtsp-simple-server</link>,
-          ready-to-use RTSP / RTMP / HLS server and proxy that allows to
-          read, publish and proxy video and audio streams. Available as
-          <link linkend="opt-services.rtsp-simple-server.enable">services.rtsp-simple-server</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://snipeitapp.com">Snipe-IT</link>, a
-          free open source IT asset/license management system. Available
-          as
-          <link linkend="opt-services.snipe-it.enable">services.snipe-it</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://snowflake.torproject.org/">snowflake-proxy</link>,
-          a system to defeat internet censorship. Available as
-          <link linkend="opt-services.snowflake-proxy.enable">services.snowflake-proxy</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://sslmate.com/">sslmate-agent</link>,
-          a daemon for managing SSL/TLS certificates on a server.
-          Available as
-          <link xlink:href="services.sslmate-agent.enable">services.sslmate-agent</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://starship.rs">starship</link>, a
-          minimal, blazing-fast, and infinitely customizable prompt for
-          any shell. Available at
-          <link linkend="opt-programs.starship.enable">programs.startship</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/rfjakob/systembus-notify">systembus-notify</link>,
-          allow system level notifications to reach the users. Available
-          as
-          <link xlink:href="opt-services.systembus-notify.enable">services.systembus-notify</link>.
-          Please keep in mind that this service should only be enabled
-          on machines with fully trusted users, as any local user is
-          able to DoS user sessions by spamming notifications.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://goteleport.com">teleport</link>,
-          allows engineers and security professionals to unify access
-          for SSH servers, Kubernetes clusters, web applications, and
-          databases across all environments. Available at
-          <link linkend="opt-services.teleport.enable">services.teleport</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>
-      <listitem>
-        <para>
-          <link xlink:href="https://upterm.dev">uptermd</link>, an
-          open-source solution for sharing terminal sessions instantly
-          over the public internet via secure tunnels. Available at
-          <link linkend="opt-services.uptermd.enable">services.uptermd</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/darrylb123/usbrelay">usbrelayd</link>,
-          an USB Relay MQTT daemon. Available as
-          <link linkend="opt-services.usbrelayd.enable">services.usbrelayd</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/miquels/webdav-server-rs">webdav-server-rs</link>,
-          Webdav server in rust. Available as
-          <link linkend="opt-services.webdav-server-rs.enable">services.webdav-server-rs</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/gin66/wg_netmanager">wg-netmanager</link>,
-          the Wireguard network manager. Available as
-          <link linkend="opt-services.wg-netmanager.enable">services.wg-netmanager</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://zammad.org/">Zammad</link>, a
-          web-based, open source user support/ticketing solution.
-          Available as
-          <link linkend="opt-services.zammad.enable">services.zammad</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.ghc.withPackages</literal> as well as
-          <literal>haskellPackages.ghcWithPackages</literal> etc. now
-          needs be overridden directly, as opposed to overriding the
-          result of calling it. Additionally, the
-          <literal>withLLVM</literal> parameter has been renamed to
-          <literal>useLLVM</literal>. So instead of
-          <literal>(ghc.withPackages (p: [])).override { withLLVM = true; }</literal>,
-          one needs to use
-          <literal>(ghc.withPackages.override { useLLVM = true; }) (p: [])</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The update of the haskell package set brings with it a new
-          version of the <literal>xmonad</literal> module, which will
-          break your configuration if you use <literal>launch</literal>
-          as entrypoint. The example code the corresponding nixos module
-          was adjusted, you may want to have a look at it.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>home-assistant</literal> module now requires
-          users that don’t want their configuration to be managed
-          declaratively to set
-          <literal>services.home-assistant.config = null;</literal>.
-          This is required due to the way default settings are handled
-          with the new settings style.
-        </para>
-        <para>
-          Additionally the default list of
-          <literal>extraComponents</literal> now includes the minimal
-          dependencies to successfully complete the
-          <link xlink:href="https://www.home-assistant.io/getting-started/onboarding/">onboarding</link>
-          procedure.
-        </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>
-          The configuration and state directories used by
-          <literal>nixos-containers</literal> have been moved from
-          <literal>/etc/containers</literal> and
-          <literal>/var/lib/containers</literal> to
-          <literal>/etc/nixos-containers</literal> and
-          <literal>/var/lib/nixos-containers</literal>.
-        </para>
-        <para>
-          If you are changing <literal>system.stateVersion</literal> to
-          <literal>&quot;22.05&quot;</literal> manually on an existing
-          system you are responsible for migrating these directories
-          yourself.
-        </para>
-        <para>
-          This is to improve compatibility with
-          <literal>libcontainer</literal> based software such as Podman
-          and Skopeo which assumes they have ownership over
-          <literal>/etc/containers</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.systems.supported</literal> has been removed, as
-          it was overengineered for determining the systems to support
-          in the nixpkgs flake. The list of systems exposed by the
-          nixpkgs flake can now be accessed as
-          <literal>lib.systems.flakeExposed</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          For new installations
-          <literal>virtualisation.oci-containers.backend</literal> is
-          now set to <literal>podman</literal> by default. If you still
-          want to use Docker on systems where
-          <literal>system.stateVersion</literal> is set to to
-          <literal>&quot;22.05&quot;</literal> set
-          <literal>virtualisation.oci-containers.backend = &quot;docker&quot;;</literal>.Old
-          systems with older <literal>stateVersion</literal>s stay with
-          <quote>docker</quote>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security.klogd</literal> was removed. Logging of
-          kernel messages is handled by systemd since Linux 3.5.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.ssmtp</literal> has been dropped due to the
-          program being unmaintained. <literal>pkgs.msmtp</literal> can
-          be used instead as a substitute <literal>sendmail</literal>
-          implementation. The corresponding options
-          <literal>services.ssmtp.*</literal> have been removed as well.
-          <literal>programs.msmtp.*</literal> can be used instead for an
-          equivalent setup. For example:
-        </para>
-        <programlisting language="bash">
-{
-  # Original ssmtp configuration:
-  services.ssmtp = {
-    enable = true;
-    useTLS = true;
-    useSTARTTLS = true;
-    hostName = &quot;smtp.example:587&quot;;
-    authUser = &quot;someone&quot;;
-    authPassFile = &quot;/secrets/password.txt&quot;;
-  };
-
-  # Equivalent msmtp configuration:
-  programs.msmtp = {
-    enable = true;
-    accounts.default = {
-      tls = true;
-      tls_starttls = true;
-      auth = true;
-      host = &quot;smtp.example&quot;;
-      port = 587;
-      user = &quot;someone&quot;;
-      passwordeval = &quot;cat /secrets/password.txt&quot;;
-    };
-  };
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.kubernetes.addons.dashboard</literal> was
-          removed due to it being an outdated version.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.kubernetes.scheduler.{port,address}</literal>
-          now set <literal>--secure-port</literal> and
-          <literal>--bind-address</literal> instead of
-          <literal>--port</literal> and <literal>--address</literal>,
-          since the former have been deprecated and are no longer
-          functional in kubernetes&gt;=1.23. Ensure that you are not
-          relying on the insecure behaviour before upgrading.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          In the PowerDNS Recursor module
-          (<literal>services.pdns-recursor</literal>), default values of
-          several IP address-related NixOS options have been updated to
-          match the default upstream behavior. In particular, Recursor
-          by default will:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              listen on (and allows connections from) both IPv4 and IPv6
-              addresses
-              (<literal>services.pdns-recursor.dns.address</literal>,
-              <literal>services.pdns-recursor.dns.allowFrom</literal>);
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              allow only local connections to the REST API server
-              (<literal>services.pdns-recursor.api.allowFrom</literal>).
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          In the ncdns module, the default value of
-          <literal>services.ncdns.address</literal> has been changed to
-          the IPv6 loopback address (<literal>::1</literal>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>openldap</literal> (and therefore the slapd LDAP
-          server) were updated to version 2.6.2. The project introduced
-          backwards-incompatible changes, namely the removal of the bdb,
-          hdb, ndb, and shell backends in slapd. Therefore before
-          updating, dump your database <literal>slapcat -n 1</literal>
-          in LDIF format, and reimport it after updating your
-          <literal>services.openldap.settings</literal>, which
-          represents your <literal>cn=config</literal>.
-        </para>
-        <para>
-          Additionally with 2.5 the argon2 module was included in the
-          standard distrubtion and renamed from
-          <literal>pw-argon2</literal> to <literal>argon2</literal>.
-          Remember to update your <literal>olcModuleLoad</literal> entry
-          in <literal>cn=config</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>openssh</literal> has been update to 8.9p1, changing
-          the FIDO security key middleware interface.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>git</literal> no longer hardcodes the path to
-          openssh’ ssh binary to reduce the amount of rebuilds. If you
-          are using git with ssh remotes and do not have a ssh binary in
-          your enviroment consider adding <literal>openssh</literal> to
-          it or switching to <literal>gitFull</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.k3s.enable</literal> no longer implies
-          <literal>systemd.enableUnifiedCgroupHierarchy = false</literal>,
-          and will default to the <quote>systemd</quote> cgroup driver
-          when using <literal>services.k3s.docker = true</literal>. This
-          change may require a reboot to take effect, and k3s may not be
-          able to run if the boot cgroup hierarchy does not match its
-          configuration. The previous behavior may be retained by
-          explicitly setting
-          <literal>systemd.enableUnifiedCgroupHierarchy = false</literal>
-          in your configuration.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>fonts.fonts</literal> no longer includes ancient
-          bitmap fonts when both
-          <literal>config.services.xserver.enable</literal> and
-          <literal>config.nixpkgs.config.allowUnfree</literal> are
-          enabled. If you still want these fonts, use:
-        </para>
-        <programlisting language="bash">
-{
-  fonts.fonts = [
-    pkgs.xorg.fontbhlucidatypewriter100dpi
-    pkgs.xorg.fontbhlucidatypewriter75dpi
-    pkgs.xorg.fontbh100dpi
-  ];
-}
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.prometheus.alertManagerTimeout</literal> has
-          been removed as it has been deprecated upstream and has no
-          effect.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The DHCP server (<literal>services.dhcpd4</literal>,
-          <literal>services.dhcpd6</literal>) has been hardened. The
-          service is now using the systemd’s
-          <literal>DynamicUser</literal> mechanism to run as an
-          unprivileged dynamically-allocated user with limited
-          capabilities. The dhcpd state files are now always stored in
-          <literal>/var/lib/dhcpd{4,6}</literal> and the
-          <literal>services.dhcpd4.stateDir</literal> and
-          <literal>service.dhcpd6.stateDir</literal> options have been
-          removed. If you were depending on root privileges or
-          set{uid,gid,cap} binaries in dhcpd shell hooks, you may give
-          dhcpd more capabilities with e.g.
-          <literal>systemd.services.dhcpd6.serviceConfig.AmbientCapabilities</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>mailpile</literal> email webclient
-          (<literal>services.mailpile</literal>) has been removed due to
-          its reliance on python2.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.ipfs.extraFlags</literal> is now escaped
-          with <literal>utils.escapeSystemdExecArgs</literal>. If you
-          rely on systemd interpolating <literal>extraFlags</literal> in
-          the service <literal>ExecStart</literal>, this will no longer
-          work.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hbase</literal> version 0.98.24 has been removed. The
-          package now defaults to version 2.4.11. Versions 1.7.1 and
-          3.0.0-alpha-2 are also available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.paperless-ng</literal> was renamed to
-          <literal>services.paperless</literal>. Accordingly, the
-          <literal>paperless-ng-manage</literal> script (located in
-          <literal>dataDir</literal>) was renamed to
-          <literal>paperless-manage</literal>.
-          <literal>services.paperless</literal> now uses
-          <literal>paperless-ngx</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>matrix-synapse</literal> service
-          (<literal>services.matrix-synapse</literal>) has been
-          converted to use the <literal>settings</literal> option
-          defined in RFC42. This means that options that are part of
-          your <literal>homeserver.yaml</literal> configuration, and
-          that were specified at the top-level of the module
-          (<literal>services.matrix-synapse</literal>) now need to be
-          moved into
-          <literal>services.matrix-synapse.settings</literal>. And while
-          not all options you may use are defined in there, they are
-          still supported, because you can set arbitrary values in this
-          freeform type.
-        </para>
-        <para>
-          The <literal>listeners.*.bind_address</literal> option was
-          renamed to <literal>bind_addresses</literal> in order to match
-          the upstream <literal>homeserver.yaml</literal> option name.
-          It is now also a list of strings instead of a string.
-        </para>
-        <para>
-          An example to make the required migration clearer:
-        </para>
-        <para>
-          Before:
-        </para>
-        <programlisting language="bash">
-{
-  services.matrix-synapse = {
-    enable = true;
-
-    server_name = &quot;example.com&quot;;
-    public_baseurl = &quot;https://example.com:8448&quot;;
-
-    enable_registration = false;
-    registration_shared_secret = &quot;xohshaeyui8jic7uutuDogahkee3aehuaf6ei3Xouz4iicie5thie6nohNahceut&quot;;
-    macaroon_secret_key = &quot;xoo8eder9seivukaiPh1cheikohquuw8Yooreid0The4aifahth3Ou0aiShaiz4l&quot;;
-
-    tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
-    tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
-
-    listeners = [ {
-      port = 8448;
-      bind_address = &quot;&quot;;
-      type = &quot;http&quot;;
-      tls = true;
-      resources = [ {
-        names = [ &quot;client&quot; ];
-        compress = true;
-      } {
-        names = [ &quot;federation&quot; ];
-        compress = false;
-      } ];
-    } ];
-
-  };
-}
-</programlisting>
-        <para>
-          After:
-        </para>
-        <programlisting language="bash">
-{
-  services.matrix-synapse = {
-    enable = true;
-
-    # this attribute set holds all values that go into your homeserver.yaml configuration
-    # See https://github.com/matrix-org/synapse/blob/develop/docs/sample_config.yaml for
-    # possible values.
-    settings = {
-      server_name = &quot;example.com&quot;;
-      public_baseurl = &quot;https://example.com:8448&quot;;
-
-      enable_registration = false;
-      # pass `registration_shared_secret` and `macaroon_secret_key` via `extraConfigFiles` instead
-
-      tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
-      tls_certificate_path = &quot;/var/lib/acme/example.com/fullchain.pem&quot;;
-
-      listeners = [ {
-        port = 8448;
-        bind_addresses = [
-          &quot;::&quot;
-          &quot;0.0.0.0&quot;
-        ];
-        type = &quot;http&quot;;
-        tls = true;
-        resources = [ {
-          names = [ &quot;client&quot; ];
-          compress = true;
-        } {
-          names = [ &quot;federation&quot; ];
-          compress = false;
-        } ];
-      } ];
-    };
-
-    extraConfigFiles = [
-      &quot;/run/keys/matrix-synapse/secrets.yaml&quot;
-    ];
-  };
-}
-</programlisting>
-        <para>
-          The secrets in your original config should be migrated into a
-          YAML file that is included via
-          <literal>extraConfigFiles</literal>. The filename must be
-          quoted to prevent nix from copying it to the (world readable)
-          store.
-        </para>
-        <para>
-          Additionally a few option defaults have been synced up with
-          upstream default values, for example the
-          <literal>max_upload_size</literal> grew from
-          <literal>10M</literal> to <literal>50M</literal>. For the same
-          reason, the default <literal>media_store_path</literal> was
-          changed from <literal>${dataDir}/media</literal> to
-          <literal>${dataDir}/media_store</literal> if
-          <literal>system.stateVersion</literal> is at least
-          <literal>22.05</literal>. Files will need to be manually moved
-          to the new location if the <literal>stateVersion</literal> is
-          updated.
-        </para>
-        <para>
-          As of Synapse 1.58.0, the old groups/communities feature has
-          been disabled by default. It will be completely removed with
-          Synapse 1.61.0.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Keycloak package (<literal>pkgs.keycloak</literal>) has
-          been switched from the Wildfly version, which will soon be
-          deprecated, to the Quarkus based version. The Keycloak service
-          (<literal>services.keycloak</literal>) has been updated to
-          accommodate the change and now differs from the previous
-          version in a few ways:
-        </para>
-        <itemizedlist>
-          <listitem>
-            <para>
-              <literal>services.keycloak.extraConfig</literal> has been
-              removed in favor of the new
-              <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">settings-style</link>
-              <link linkend="opt-services.keycloak.settings"><literal>services.keycloak.settings</literal></link>
-              option. The available options correspond directly to
-              parameters in <literal>conf/keycloak.conf</literal>. Some
-              of the most important parameters are documented as
-              suboptions, the rest can be found in the
-              <link xlink:href="https://www.keycloak.org/server/all-config">All
-              configuration section of the Keycloak Server Installation
-              and Configuration Guide</link>. While the new
-              configuration is much simpler and cleaner than the old
-              JBoss CLI one, this unfortunately mean that there’s no
-              straightforward way to convert an old configuration to the
-              new format and some settings may not even be available
-              anymore.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.keycloak.frontendUrl</literal> was
-              removed and the frontend URL is now configured through the
-              <literal>hostname</literal> family of settings in
-              <link linkend="opt-services.keycloak.settings"><literal>services.keycloak.settings</literal></link>
-              instead. See the
-              <link xlink:href="https://www.keycloak.org/server/hostname">Hostname
-              section of the Keycloak Server Installation and
-              Configuration Guide</link> for more details. Additionally,
-              <literal>/auth</literal> was removed from the default
-              context path and needs to be added back in
-              <link linkend="opt-services.keycloak.settings.http-relative-path"><literal>services.keycloak.settings.http-relative-path</literal></link>
-              if you want to keep compatibility with your current
-              clients.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>services.keycloak.bindAddress</literal>,
-              <literal>services.keycloak.forceBackendUrlToFrontendUrl</literal>,
-              <literal>services.keycloak.httpPort</literal> and
-              <literal>services.keycloak.httpsPort</literal> have been
-              removed in favor of their equivalent options in
-              <link linkend="opt-services.keycloak.settings"><literal>services.keycloak.settings</literal></link>.
-              <literal>httpPort</literal> and
-              <literal>httpsPort</literal> have additionally had their
-              types changed from <literal>str</literal> to
-              <literal>port</literal>.
-            </para>
-            <para>
-              The new names are as follows:
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  <literal>bindAddress</literal>:
-                  <link linkend="opt-services.keycloak.settings.http-host"><literal>services.keycloak.settings.http-host</literal></link>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>forceBackendUrlToFrontendUrl</literal>:
-                  <link linkend="opt-services.keycloak.settings.hostname-strict-backchannel"><literal>services.keycloak.settings.hostname-strict-backchannel</literal></link>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>httpPort</literal>:
-                  <link linkend="opt-services.keycloak.settings.http-port"><literal>services.keycloak.settings.http-port</literal></link>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>httpsPort</literal>:
-                  <link linkend="opt-services.keycloak.settings.https-port"><literal>services.keycloak.settings.https-port</literal></link>
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-        </itemizedlist>
-        <para>
-          For example, when using a reverse proxy the migration could
-          look like this:
-        </para>
-        <para>
-          Before:
-        </para>
-        <programlisting language="bash">
-  services.keycloak = {
-    enable = true;
-    httpPort = &quot;8080&quot;;
-    frontendUrl = &quot;https://keycloak.example.com/auth&quot;;
-    database.passwordFile = &quot;/run/keys/db_password&quot;;
-    extraConfig = {
-      &quot;subsystem=undertow&quot;.&quot;server=default-server&quot;.&quot;http-listener=default&quot;.proxy-address-forwarding = true;
-    };
-  };
-</programlisting>
-        <para>
-          After:
-        </para>
-        <programlisting language="bash">
-  services.keycloak = {
-    enable = true;
-    settings = {
-      http-port = 8080;
-      hostname = &quot;keycloak.example.com&quot;;
-      http-relative-path = &quot;/auth&quot;;
-      proxy = &quot;edge&quot;;
-    };
-    database.passwordFile = &quot;/run/keys/db_password&quot;;
-  };
-</programlisting>
-      </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>
-          Services in the <literal>hadoop</literal> module previously
-          set <literal>openFirewall</literal> to true by default. This
-          has now been changed to false. Node definitions for multi-node
-          clusters would need <literal>openFirewall = true;</literal> to
-          be added to to hadoop services when upgrading from NixOS
-          21.11.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.hadoop.yarn.nodemanager</literal> now uses
-          cgroup-based CPU limit enforcement by default. Additionally,
-          the option <literal>useCGroups</literal> was added to
-          nodemanagers as an easy way to switch back to the old
-          behavior.
-        </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>
-          <literal>buildGoModule</literal> was updated to use
-          <literal>go_1_17</literal>, third party derivations that
-          specify &gt;= go 1.17 in the main <literal>go.mod</literal>
-          will need to regenerate their <literal>vendorSha256</literal>
-          hash.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>gnome-passwordsafe</literal> package updated to
-          <link xlink:href="https://gitlab.gnome.org/World/secrets/-/tags/6.0">version
-          6.x</link> and renamed to <literal>gnome-secrets</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.gnome.experimental-features.realtime-scheduling</literal>
-          option has been removed, as GNOME Shell now
-          <link xlink:href="https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2060">uses
-          rtkit</link>. Use
-          <literal>security.rtkit.enable = true;</literal> instead. As
-          before, you will need to have it enabled using GSettings.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.telepathy</literal> will no longer be
-          enabled by default for GNOME desktops, one should enable it in
-          their configs if using Empathy or Polari.
-        </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>
-          Ntopng (<literal>services.ntopng</literal>) is updated to
-          5.2.1 and uses a separate Redis instance if
-          <literal>system.stateVersion</literal> is at least
-          <literal>22.05</literal>. Existing setups shouldn’t be
-          affected.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The backward compatibility in
-          <literal>services.wordpress</literal> to configure sites with
-          the old interface has been removed. Please use
-          <literal>services.wordpress.sites</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The backward compatibility in
-          <literal>services.dokuwiki</literal> to configure sites with
-          the old interface has been removed. Please use
-          <literal>services.dokuwiki.sites</literal> instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          opensmtpd-extras is no longer build with python2 scripting
-          support due to python2 deprecation in nixpkgs
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.miniflux.adminCredentialFiles</literal> is
-          now required, instead of defaulting to
-          <literal>admin</literal> and <literal>password</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>taskserver</literal> module no longer implicitly
-          opens ports in the firewall configuration. This is now
-          controlled through the option
-          <literal>services.taskserver.openFirewall</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>
-          <literal>teleport</literal> has been upgraded to major version
-          9. Please see upstream
-          <link xlink:href="https://goteleport.com/docs/setup/operations/upgrading/">upgrade
-          instructions</link> and
-          <link xlink:href="https://goteleport.com/docs/changelog/#900">release
-          notes</link>.
-        </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>
-      <listitem>
-        <para>
-          Normal users (with <literal>isNormalUser = true</literal>)
-          which have non-empty <literal>subUidRanges</literal> or
-          <literal>subGidRanges</literal> set no longer have additional
-          implicit ranges allocated. To enable automatic allocation back
-          set <literal>autoSubUidGidRange = true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>idris2</literal> now requires
-          <literal>--package</literal> when using packages
-          <literal>contrib</literal> and <literal>network</literal>,
-          while previously these idris2 packages were automatically
-          loaded.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The iputils package, which is installed by default, no longer
-          provides the legacy tools <literal>tftpd</literal> and
-          <literal>traceroute6</literal>. More tools
-          (<literal>ninfod</literal>, <literal>rarpd</literal>, and
-          <literal>rdisc</literal>) are going to be removed in the next
-          release. See
-          <link xlink:href="https://github.com/iputils/iputils/releases/tag/20211215">upstream’s
-          release notes</link> for more details and available
-          replacements.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.thelounge.private</literal> was removed in
-          favor of <literal>services.thelounge.public</literal>, to
-          follow with upstream changes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.docbookrx</literal> was removed since it’s
-          unmaintained
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs._7zz</literal> is now correctly licensed as
-          LGPL3+ and BSD3 with optional unfree unRAR licensed code
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>vim.customize</literal> function produced by
-          <literal>vimUtils.makeCustomizable</literal> now has a
-          slightly different interface:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              The wrapper now includes everything in the given Vim
-              derivation if <literal>name</literal> is
-              <literal>&quot;vim&quot;</literal> (the default). This
-              makes the <literal>wrapManual</literal> argument obsolete,
-              but this behavior can be overriden by setting the
-              <literal>standalone</literal> argument.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              All the executables present in the given derivation (or,
-              in <literal>standalone</literal> mode, only the
-              <literal>*vim</literal> ones) are wrapped. This makes the
-              <literal>wrapGui</literal> argument obsolete.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>vimExecutableName</literal> and
-              <literal>gvimExecutableName</literal> arguments were
-              replaced by a single <literal>executableName</literal>
-              argument in which the shell variable
-              <literal>$exe</literal> can be used to refer to the
-              wrapped executable’s name.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          See the comments in
-          <literal>pkgs/applications/editors/vim/plugins/vim-utils.nix</literal>
-          for more details.
-        </para>
-        <para>
-          <literal>vimUtils.vimWithRC</literal> was removed. You should
-          instead use <literal>customize</literal> on a Vim derivation,
-          which now accepts <literal>vimrcFile</literal> and
-          <literal>gvimrcFile</literal> arguments.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>tilp2</literal> was removed together with its module
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The F-PROT antivirus (<literal>fprot</literal> package) and
-          its service module were removed because it reached
-          <link xlink:href="https://kb.cyren.com/av-support/index.php?/Knowledgebase/Article/View/434/0/end-of-sale--end-of-life-for-f-prot-and-csam">end-of-life</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>bird1</literal> and its modules
-          <literal>services.bird</literal> as well as
-          <literal>services.bird6</literal> have been removed. Upgrade
-          to <literal>services.bird2</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The options
-          <literal>networking.interfaces.&lt;name&gt;.ipv4.routes</literal>
-          and
-          <literal>networking.interfaces.&lt;name&gt;.ipv6.routes</literal>
-          are no longer ignored when using networkd instead of the
-          default scripted network backend by setting
-          <literal>networking.useNetworkd</literal> to
-          <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>miller</literal> package has been upgraded from
-          5.10.3 to
-          <link xlink:href="https://github.com/johnkerl/miller/releases/tag/v6.2.0">6.2.0</link>.
-          See
-          <link xlink:href="https://miller.readthedocs.io/en/latest/new-in-miller-6">What’s
-          new in Miller 6</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          MultiMC has been replaced with the fork PolyMC due to upstream
-          developers being hostile to 3rd party package maintainers.
-          PolyMC removes all MultiMC branding and is aimed at providing
-          proper 3rd party packages like the one contained in Nixpkgs.
-          This change affects the data folder where game instances and
-          other save and configuration files are stored. Users with
-          existing installations should rename
-          <literal>~/.local/share/multimc</literal> to
-          <literal>~/.local/share/polymc</literal>. The main config
-          file’s path has also moved from
-          <literal>~/.local/share/multimc/multimc.cfg</literal> to
-          <literal>~/.local/share/polymc/polymc.cfg</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>systemd-nspawn@.service</literal> settings have been
-          reverted to the default systemd behaviour. User namespaces are
-          now activated by default. If you want to keep running nspawn
-          containers without user namespaces you need to set
-          <literal>systemd.nspawn.&lt;name&gt;.execConfig.PrivateUsers = false</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>systemd-shutdown</literal> is now properly linked on
-          shutdown to unmount all filesystems and device mapper devices
-          cleanly. This can be disabled using
-          <literal>systemd.shutdownRamfs.enable</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Tor SOCKS proxy is now actually disabled if
-          <literal>services.tor.client.enable</literal> is set to
-          <literal>false</literal> (the default). If you are using this
-          functionality but didn’t change the setting or set it to
-          <literal>false</literal>, you now need to set it to
-          <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.github-runner</literal> has been hardened.
-          Notably address families and system calls have been
-          restricted, which may adversely affect some kinds of testing,
-          e.g. using <literal>AF_BLUETOOTH</literal> to test bluetooth
-          devices.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The terraform 0.12 compatibility has been removed and the
-          <literal>terraform.withPlugins</literal> and
-          <literal>terraform-providers.mkProvider</literal>
-          implementations simplified. Providers now need to be stored
-          under
-          <literal>$out/libexec/terraform-providers/&lt;registry&gt;/&lt;owner&gt;/&lt;name&gt;/&lt;version&gt;/&lt;os&gt;_&lt;arch&gt;/terraform-provider-&lt;name&gt;_v&lt;version&gt;</literal>
-          (which mkProvider does).
-        </para>
-        <para>
-          This breaks back-compat so it’s not possible to mix-and-match
-          with previous versions of nixpkgs. In exchange, it now becomes
-          possible to use the providers from
-          <link xlink:href="https://github.com/numtide/nixpkgs-terraform-providers-bin">nixpkgs-terraform-providers-bin</link>
-          directly.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>dendrite</literal> package has been upgraded from
-          0.5.1 to
-          <link xlink:href="https://github.com/matrix-org/dendrite/releases/tag/v0.6.5">0.6.5</link>.
-          Instances configured with split sqlite databases, which has
-          been the default in NixOS, require merging of the federation
-          sender and signing key databases. See upstream
-          <link xlink:href="https://github.com/matrix-org/dendrite/releases/tag/v0.6.0">release
-          notes</link> on version 0.6.0 for details on database changes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The existing <literal>pkgs.opentelemetry-collector</literal>
-          has been moved to
-          <literal>pkgs.opentelemetry-collector-contrib</literal> to
-          match the actual source being the <quote>contrib</quote>
-          edition. <literal>pkgs.opentelemetry-collector</literal> is
-          now the actual core release of opentelemetry-collector. If you
-          use the community contributions you should change the package
-          you refer to. If you don’t need them update your commands from
-          <literal>otelcontribcol</literal> to
-          <literal>otelcorecol</literal> and enjoy a 7x smaller binary.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.zookeeper</literal> has a new option
-          <literal>jre</literal> for specifying the JRE to start
-          zookeeper with. It defaults to the JRE that
-          <literal>pkgs.zookeeper</literal> was wrapped with, instead of
-          <literal>pkgs.jre</literal>. This changes the JRE to
-          <literal>pkgs.jdk11_headless</literal> by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.pgadmin</literal> now refers to
-          <literal>pkgs.pgadmin4</literal>. <literal>pgadmin3</literal>
-          has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.minetestclient_4</literal> and
-          <literal>pkgs.minetestserver_4</literal> have been removed, as
-          the last 4.x release was in 2018.
-          <literal>pkgs.minetestclient</literal> (equivalent to
-          <literal>pkgs.minetest</literal> ) and
-          <literal>pkgs.minetestserver</literal> can be used instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.noto-fonts-cjk</literal> is now deprecated in
-          favor of <literal>pkgs.noto-fonts-cjk-sans</literal> and
-          <literal>pkgs.noto-fonts-cjk-serif</literal> because they each
-          have different release schedules. To maintain compatibility
-          with prior releases of Nixpkgs,
-          <literal>pkgs.noto-fonts-cjk</literal> is currently an alias
-          of <literal>pkgs.noto-fonts-cjk-sans</literal> and doesn’t
-          include serif fonts.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.epgstation</literal> has been upgraded from v1
-          to v2, resulting in incompatible changes in the database
-          scheme and configuration format.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Some top-level settings under
-          <link linkend="opt-services.epgstation.enable">services.epgstation</link>
-          is now deprecated because it was redudant due to the same
-          options being present in
-          <link linkend="opt-services.epgstation.settings">services.epgstation.settings</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>services.epgstation.basicAuth</literal>
-          was removed because basic authentication support was dropped
-          by upstream.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <link linkend="opt-services.epgstation.database.passwordFile">services.epgstation.database.passwordFile</link>
-          no longer has a default value. Make sure to set this option
-          explicitly before upgrading. Change the database password if
-          necessary.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The
-          <link linkend="opt-services.epgstation.settings">services.epgstation.settings</link>
-          option now expects options for <literal>config.yml</literal>
-          in EPGStation v2.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Existing data for the
-          <link linkend="opt-services.epgstation.enable">services.epgstation</link>
-          module would have to be backed up prior to the upgrade. To
-          back up exising data to
-          <literal>/tmp/epgstation.bak</literal>, run
-          <literal>sudo -u epgstation epgstation run backup /tmp/epgstation.bak</literal>.
-          To import that data after to the upgrade, run
-          <literal>sudo -u epgstation epgstation run v1migrate /tmp/epgstation.bak</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>switch-to-configuration</literal> (the script that is
-          run when running <literal>nixos-rebuild switch</literal> for
-          example) has been reworked
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              The interface that allows activation scripts to restart
-              units has been streamlined. Restarting and reloading is
-              now done by a single file
-              <literal>/run/nixos/activation-restart-list</literal> that
-              honors <literal>restartIfChanged</literal> and
-              <literal>reloadIfChanged</literal> of the units.
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  Preferring to reload instead of restarting can still
-                  be achieved using
-                  <literal>/run/nixos/activation-reload-list</literal>.
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-          <listitem>
-            <para>
-              The script now uses a proper ini-file parser to parse
-              systemd units. Some values are now only searched in one
-              section instead of in the entire unit. This is only
-              relevant for units that don’t use the NixOS systemd moule.
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  <literal>RefuseManualStop</literal>,
-                  <literal>X-OnlyManualStart</literal>,
-                  <literal>X-StopOnRemoval</literal>,
-                  <literal>X-StopOnReconfiguration</literal> are only
-                  searched in the <literal>[Unit]</literal> section
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>X-ReloadIfChanged</literal>,
-                  <literal>X-RestartIfChanged</literal>,
-                  <literal>X-StopIfChanged</literal> are only searched
-                  in the <literal>[Service]</literal> section
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.bookstack.cacheDir</literal> option has
-          been removed, since the cache directory is now handled by
-          systemd.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.bookstack.extraConfig</literal> option
-          has been replaced by
-          <literal>services.bookstack.config</literal> which implements
-          a
-          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">settings-style</link>
-          configuration.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>lib.assertMsg</literal> and
-          <literal>lib.assertOneOf</literal> no longer return
-          <literal>false</literal> if the passed condition is
-          <literal>false</literal>, <literal>throw</literal>ing the
-          given error message instead (which makes the resulting error
-          message less cluttered). This will not impact the behaviour of
-          code using these functions as intended, namely as top-level
-          wrapper for <literal>assert</literal> conditions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>vpnc</literal> package has been changed to use
-          GnuTLS instead of OpenSSL by default for licensing reasons.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default version of <literal>nextcloud</literal> is
-          <emphasis role="strong">nextcloud24</emphasis>. Please note
-          that it’s <emphasis role="strong">not</emphasis> possible to
-          upgrade <literal>nextcloud</literal> across multiple major
-          versions! This means it’s e.g. not possible to upgrade from
-          <literal>nextcloud22</literal> to
-          <literal>nextcloud24</literal> in a single deploy and most
-          <literal>21.11</literal> users will have to upgrade to
-          <literal>nextcloud23</literal> first.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.vimPlugins.onedark-nvim</literal> now refers to
-          <link xlink:href="https://github.com/navarasu/onedark.nvim">navarasu/onedark.nvim</link>
-          (formerly refers to
-          <link xlink:href="https://github.com/olimorris/onedarkpro.nvim">olimorris/onedarkpro.nvim</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.pipewire.enable</literal> will default to
-          enabling the WirePlumber session manager instead of
-          pipewire-media-session. pipewire-media-session is deprecated
-          by upstream and not recommended, but can still be manually
-          enabled by setting
-          <literal>services.pipewire.media-session.enable</literal> to
-          <literal>true</literal> and
-          <literal>services.pipewire.wireplumber.enable</literal> to
-          <literal>false</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.makeDesktopItem</literal> has been refactored to
-          provide a more idiomatic API. Specifically:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              All valid options as of FDO Desktop Entry specification
-              version 1.4 can now be passed in as explicit arguments
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>exec</literal> can now be null, for entries that
-              are not of type Application
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>mimeType</literal> argument is renamed to
-              <literal>mimeTypes</literal> for consistency
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>mimeTypes</literal>,
-              <literal>categories</literal>,
-              <literal>implements</literal>,
-              <literal>keywords</literal>, <literal>onlyShowIn</literal>
-              and <literal>notShowIn</literal> take lists of strings
-              instead of one string with semicolon separators
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>extraDesktopEntries</literal> renamed to
-              <literal>extraConfig</literal> for consistency
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Actions should now be provided as an attrset
-              <literal>actions</literal>, the <literal>Actions</literal>
-              line will be autogenerated.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>extraEntries</literal> is removed.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Additional validation is added both at eval time and at
-              build time.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          See the <literal>vscode</literal> package for a more detailed
-          example.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Existing <literal>resholve*</literal> functions have been
-          renamed and nested under <literal>pkgs.resholve</literal>.
-          Update uses to:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>resholvePackage</literal> -&gt;
-              <literal>resholve.mkDerivation</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>resholveScript</literal> -&gt;
-              <literal>resholve.writeScript</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>resholveScriptBin</literal> -&gt;
-              <literal>resholve.writeScriptBin</literal>
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.cosmopolitan</literal> no longer provides the
-          <literal>cosmoc</literal> command. It has been moved to
-          <literal>pkgs.cosmoc</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.graalvmXX-ce</literal> packages no longer
-          provide support for Python/Ruby/WASM, instead focusing only in
-          Java and Native Image Support. If you need to add support
-          back, please see the
-          <literal>pkgs.graalvmCEPackages.mkGraal</literal> function to
-          create your own customized version of GraalVM with support for
-          what you need.
-        </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 option
-          <link linkend="opt-virtualisation.vmVariant">virtualisation.vmVariant</link>
-          was added to allow users to make changes to the
-          <literal>nixos-rebuild build-vm</literal> configuration that
-          do not apply to their normal system.
-        </para>
-        <para>
-          The <literal>config.system.build.vm</literal> attribute now
-          always exists and defaults to the value from
-          <literal>vmVariant</literal>. Configurations that import the
-          <literal>virtualisation/qemu-vm.nix</literal> module
-          themselves will override this value, such that
-          <literal>vmVariant</literal> is not used.
-        </para>
-        <para>
-          Similarly
-          <link linkend="opt-virtualisation.vmVariantWithBootLoader">virtualisation.vmVariantWithBootloader</link>
-          was added.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The configuration portion of the <literal>nix-daemon</literal>
-          module has been reworked and exposed as
-          <link xlink:href="options.html#opt-nix-settings">nix.settings</link>:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              Legacy options have been mapped to the corresponding
-              options under under
-              <link xlink:href="options.html#opt-nix.settings">nix.settings</link>
-              and will be deprecated when NixOS 21.11 reaches end of
-              life.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <link xlink:href="options.html#opt-nix.buildMachines.publicHostKey">nix.buildMachines.publicHostKey</link>
-              has been added.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://kops.sigs.k8s.io"><literal>kops</literal></link>
-          defaults to 1.23.2, which will enable
-          <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html">Instance
-          Metadata Service Version 2</link> and require tokens on new
-          clusters with Kubernetes &gt;= 1.22. This will increase
-          security by default, but may break some types of workloads.
-          The default behaviour for
-          <literal>spec.kubeDNS.nodeLocalDNS.forwardToKubeDNS</literal>
-          has changed from <literal>true</literal> to
-          <literal>false</literal>. Cilium now has
-          <literal>disable-cnp-status-updates: true</literal> by
-          default. Set this to false if you rely on the
-          CiliumNetworkPolicy status fields. Support for Kubernetes
-          1.17, the Lyft CNI, Weave CNI on Kubernetes &gt;= 1.23, CentOS
-          7 and 8, Debian 9, RHEL 7, and Ubuntu 16.05 (Xenial) has been
-          removed. See the
-          <link xlink:href="https://kops.sigs.k8s.io/releases/1.22-notes/">1.22
-          release notes</link> and
-          <link xlink:href="https://kops.sigs.k8s.io/releases/1.23-notes/">1.23
-          release notes</link> for more details, including other
-          significant changes.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Mattermost has been upgraded to extended support version 6.3
-          as the previously packaged extended support version 5.37 is
-          <link xlink:href="https://docs.mattermost.com/upgrade/extended-support-release.html">reaching
-          end of life</link>. Migration may take some time, see the
-          <link xlink:href="https://docs.mattermost.com/install/self-managed-changelog.html#release-v6-3-extended-support-release">changelog</link>
-          and
-          <link xlink:href="https://docs.mattermost.com/upgrade/important-upgrade-notes.html">important
-          upgrade notes</link>.
-        </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>
-          Some improvements have been made to the
-          <literal>hadoop</literal> module:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              A <literal>gatewayRole</literal> option has been added,
-              for deploying hadoop cluster configuration files to a node
-              that does not have any active services
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Support for older versions of hadoop have been added to
-              the module
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Overriding and extending site XML files has been made
-              easier
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The auto-upgrade service now accepts persistent (default:
-          true) parameter. By default auto-upgrade will now run
-          immediately if it would have been triggered at least once
-          during the time when the timer was inactive.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Mastodon now uses <literal>services.redis.servers</literal> to
-          start a new redis server, instead of using a global redis
-          server. This improves compatibility with other services that
-          use redis.
-        </para>
-        <para>
-          Note that this will recreate the redis database, although
-          according to the
-          <link xlink:href="https://docs.joinmastodon.org/admin/backups/">Mastodon
-          docs</link>, this is almost harmless:
-        </para>
-        <blockquote>
-          <para>
-            Losing the Redis database is almost harmless: The only
-            irrecoverable data will be the contents of the Sidekiq
-            queues and scheduled retries of previously failed jobs. The
-            home and list feeds are stored in Redis, but can be
-            regenerated with tootctl.
-          </para>
-        </blockquote>
-        <para>
-          If you do want to save the redis database, you can use the
-          following commands:
-        </para>
-        <programlisting language="bash">
-redis-cli save
-cp /var/lib/redis/dump.rdb &quot;/var/lib/redis-mastodon/dump.rdb&quot;
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          Peertube now uses services.redis.servers to start a new redis
-          server, instead of using a global redis server. This improves
-          compatibility with other services that use redis.
-        </para>
-        <para>
-          Redis database is used for storage only cache and job queue.
-          More information can be found here -
-          <link xlink:href="https://docs.joinpeertube.org/contribute-architecture">Peertube
-          architecture</link>.
-        </para>
-        <para>
-          If you do want to save the redis database, you can use the
-          following commands before upgrade OS:
-        </para>
-        <programlisting language="bash">
-redis-cli save
-sudo mkdir /var/lib/redis-peertube
-sudo cp /var/lib/redis/dump.rdb /var/lib/redis-peertube/dump.rdb
-</programlisting>
-      </listitem>
-      <listitem>
-        <para>
-          Added the <literal>keter</literal> NixOS module. Keter reverse
-          proxies requests to your loaded application based on virtual
-          hostnames.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          If you are using Wayland you can choose to use the Ozone
-          Wayland support in Chrome and several Electron apps by setting
-          the environment variable <literal>NIXOS_OZONE_WL=1</literal>
-          (for example via
-          <literal>environment.sessionVariables.NIXOS_OZONE_WL = &quot;1&quot;</literal>).
-          This is not enabled by default because Ozone Wayland is still
-          under heavy development and behavior is not always flawless.
-          Furthermore, not all Electron apps use the latest Electron
-          versions.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A new option group
-          <literal>systemd.network.wait-online</literal> was added, with
-          options to configure
-          <literal>systemd-networkd-wait-online.service</literal>:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>anyInterface</literal> allows specifying that the
-              network should be considered online when <emphasis>at
-              least one</emphasis> interface is online (useful on
-              laptops)
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>timeout</literal> defines how long to wait for
-              the network to come online
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>extraArgs</literal> for everything else
-            </para>
-          </listitem>
-        </itemizedlist>
-      </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>unifi</literal> package was switched from
-          <literal>unifi6</literal> to <literal>unifi7</literal>. Direct
-          downgrades from Unifi 7 to Unifi 6 are not possible and
-          require restoring from a backup made by Unifi 6.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs.zsh.autosuggestions.strategy</literal> now
-          takes a list of strings instead of a string.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>asterisk</literal> and
-          <literal>asterisk-stable</literal> packages were switched from
-          <literal>asterisk_18</literal> to the newly-packaged
-          <literal>asterisk_19</literal>. Asterisk 13 and 17 have been
-          removed as they have reached their end of life.
-        </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>
-          The <literal>services.unifi-video.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>
-          MariaDB is now offered in several versions, not just the
-          newest one. So if you have a need for running MariaDB 10.4 for
-          example, you can now just set
-          <literal>services.mysql.package = pkgs.mariadb_104;</literal>.
-          In general, it is recommended to run the newest version, to
-          get the newest features, while sticking with an LTS version
-          will most likely provide a more stable experience. Sometimes
-          software is also incompatible with the newest version of
-          MariaDB.
-        </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>
-          <link linkend="opt-programs.ssh.knownHosts">programs.ssh.knownHosts</link>
-          has gained an <literal>extraHostNames</literal> option to
-          augment <literal>hostNames</literal>. It is now possible to
-          use the attribute name of a <literal>knownHosts</literal>
-          entry as the primary host name and specify secondary host
-          names using <literal>extraHostNames</literal> without having
-          to duplicate the primary host name.
-        </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
-          <link linkend="opt-services.xserver.desktopManager.runXdgAutostartIfNone">services.xserver.desktopManager.runXdgAutostartIfNone</link>
-          was added in order to automatically run XDG autostart files
-          for sessions without a desktop manager. This replaces helpers
-          like the <literal>dex</literal> package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          When setting
-          <link linkend="opt-i18n.inputMethod.enabled">i18n.inputMethod.enabled</link>
-          to <literal>fcitx5</literal>, it no longer creates
-          corresponding systemd user services. It now relies on XDG
-          autostart files to start and work properly in your desktop
-          sessions. If you are using only a window manager without a
-          desktop manager, you need to enable
-          <literal>services.xserver.desktopManager.runXdgAutostartIfNone</literal>
-          or using the <literal>dex</literal> package to make
-          <literal>fcitx5</literal> work.
-        </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>
-      <listitem>
-        <para>
-          The options <literal>boot.extraModprobeConfig</literal> and
-          <literal>boot.blacklistedKernelModules</literal> now also take
-          effect in the initrd by copying the file
-          <literal>/etc/modprobe.d/nixos.conf</literal> into the initrd.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nixos-generate-config</literal> now puts the dhcp
-          configuration in <literal>hardware-configuration.nix</literal>
-          instead of <literal>configuration.nix</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          ORY Kratos was updated to version 0.9.0-alpha.3, which
-          introduces some breaking changes:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              All endpoints at the Admin API are now exposed at
-              <literal>/admin/</literal>. For example, endpoint
-              <literal>https://kratos:4434/identities</literal> is now
-              exposed at
-              <literal>https://kratos:4434/admin/identities</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Configuration key
-              <literal>selfservice.whitelisted_return_urls</literal> has
-              been renamed to <literal>allowed_return_urls</literal>
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>password_identifier</literal> form field of
-              the password login strategy has been renamed to
-              <literal>identifier</literal> to make compatibility with
-              passwordless flows possible.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Instead of having a global
-              <literal>default_schema_url</literal> which developers
-              used to update their schema, you now need to define the
-              <literal>default_schema_id</literal> which must reference
-              schema ID in your config.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              Calling <literal>/self-service/recovery</literal> without
-              flow ID or with an invalid flow ID while authenticated
-              will now respond with an error instead of redirecting to
-              the default page.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              If you are relying on the SQLite images, update your
-              Docker Pull commands as follows:
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  <literal>docker pull oryd/kratos:{version}</literal>
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-          <listitem>
-            <para>
-              Additionally, all passwords now have to be at least 8
-              characters long.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              For more details, see:
-            </para>
-            <itemizedlist spacing="compact">
-              <listitem>
-                <para>
-                  <link xlink:href="https://github.com/ory/kratos/releases/tag/v0.8.1-alpha.1">Release
-                  Notes for v0.8.1-alpha-1</link>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <link xlink:href="https://github.com/ory/kratos/releases/tag/v0.8.2-alpha.1">Release
-                  Notes for v0.8.2-alpha-1</link>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <link xlink:href="https://github.com/ory/kratos/releases/tag/v0.9.0-alpha.1">Release
-                  Notes for v0.9.0-alpha-1</link>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <link xlink:href="https://github.com/ory/kratos/releases/tag/v0.9.0-alpha.3">Release
-                  Notes for v0.9.0-alpha-3</link>
-                </para>
-              </listitem>
-            </itemizedlist>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>fetchFromSourcehut</literal> now allows fetching
-          repositories recursively using <literal>fetchgit</literal> or
-          <literal>fetchhg</literal> if the argument
-          <literal>fetchSubmodules</literal> is set to
-          <literal>true</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A module for declarative configuration of openconnect VPN
-          profiles was added under
-          <literal>networking.openconnect</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>element-desktop</literal> package now has an
-          <literal>useKeytar</literal> option (defaults to
-          <literal>true</literal>), which allows disabling
-          <literal>keytar</literal> and in turn
-          <literal>libsecret</literal> usage (which binds to native
-          credential managers / keychain libraries).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>services.thelounge.plugins</literal> has
-          been added to allow installing plugins for The Lounge. Plugins
-          can be found in
-          <literal>pkgs.theLoungePlugins.plugins</literal> and
-          <literal>pkgs.theLoungePlugins.themes</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <literal>services.xserver.videoDriver = [ &quot;nvidia&quot; ];</literal>
-          will now also install
-          <link xlink:href="https://github.com/elFarto/nvidia-vaapi-driver">nvidia
-          VA-API drivers</link> by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>firmwareLinuxNonfree</literal> package has been
-          renamed to <literal>linux-firmware</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          It is now possible to specify wordlists to include as handy to
-          access environment variables using the
-          <literal>config.environment.wordlist</literal> configuration
-          options.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.mbpfan</literal> module was converted to
-          a
-          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
-          0042</link> configuration.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default value for
-          <literal>programs.spacefm.settings.graphical_su</literal> got
-          unset. It previously pointed to <literal>gksu</literal> which
-          has been removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <link xlink:href="https://dino.im">Dino</link> XMPP client
-          was updated to 0.3, adding support for audio and video calls.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.mattermost.plugins</literal> has been added
-          to allow the declarative installation of Mattermost plugins.
-          Plugins are automatically repackaged using autoPatchelf.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link linkend="opt-services.logrotate.enable">services.logrotate.enable</link>
-          now defaults to true if any rotate path has been defined, and
-          some paths have been added by default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The logrotate module also has been updated to freeform syntax:
-          <link linkend="opt-services.logrotate.paths">services.logrotate.paths</link>
-          and
-          <link linkend="opt-services.logrotate.extraConfig">services.logrotate.extraConfig</link>
-          will work, but issue deprecation warnings and
-          <link linkend="opt-services.logrotate.settings">services.logrotate.settings</link>
-          should now be used instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>security.pam.ussh</literal> has been added, which
-          allows authorizing PAM sessions based on SSH
-          <emphasis>certificates</emphasis> held within an SSH agent,
-          using
-          <link xlink:href="https://github.com/uber/pam-ussh">pam-ussh</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>vscode-extensions.ionide.ionide-fsharp</literal>
-          package has been updated to 6.0.0 and now requires .NET 6.0.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>phpPackages.box</literal> package has been
-          updated from 2.7.5 to 3.16.0. See the
-          <link xlink:href="https://github.com/box-project/box/blob/master/UPGRADE.md#from-27-to-30">upgrade
-          guide</link> for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>zrepl</literal> package has been updated from
-          0.4.0 to 0.5:
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              The RPC protocol version was bumped; all zrepl daemons in
-              a setup must be updated and restarted before replication
-              can resume.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              A bug involving encrypt-on-receive has been fixed. Read
-              the
-              <link xlink:href="https://zrepl.github.io/configuration/sendrecvoptions.html#job-recv-options-placeholder">zrepl
-              documentation</link> and check the output of
-              <literal>zfs get -r encryption,zrepl:placeholder PATH_TO_ROOTFS</literal>
-              on the receiver.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>polybar</literal> package has been updated from
-          3.5.7 to 3.6.2. See
-          <link xlink:href="https://github.com/polybar/polybar/releases/tag/3.6.0">the
-          changelog</link> for more details.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              Breaking changes include changes to escaping rules in
-              configuration values, changes in behavior when
-              encountering invalid tag names, and changes to
-              inter-process-messaging (IPC).
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          Renamed option
-          <literal>services.openssh.challengeResponseAuthentication</literal>
-          to
-          <literal>services.openssh.kbdInteractiveAuthentication</literal>.
-          Reason is that the old name has been deprecated upstream.
-          Using the old option name will still work, but produce a
-          warning.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.autorandr</literal> now allows for adding
-          hooks and profiles declaratively.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pomerium-cli</literal> command has been moved out
-          of the <literal>pomerium</literal> package into the
-          <literal>pomerium-cli</literal> package, following upstream’s
-          repository split. If you are using the
-          <literal>pomerium-cli</literal> command, you should now
-          install the <literal>pomerium-cli</literal> package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <link linkend="opt-networking.networkmanager.enableFccUnlock">services.networking.networkmanager.enableFccUnlock</link>
-          was added to support FCC unlock procedures. Since release
-          1.18.4, the ModemManager daemon no longer automatically
-          performs the FCC unlock procedure by default. See
-          <link xlink:href="https://modemmanager.org/docs/modemmanager/fcc-unlock/">the
-          docs</link> for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>programs.tmux</literal> has a new option
-          <literal>plugins</literal> that accepts a list of packages
-          from the <literal>tmuxPlugins</literal> group. The specified
-          packages are added to the system and loaded by
-          <literal>tmux</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The polkit service, available at
-          <literal>security.polkit.enable</literal>, is now disabled by
-          default. It will automatically be enabled through services and
-          desktop environments as needed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>mercury</literal> was updated to 22.01.1, which has
-          some breaking changes
-          (<link xlink:href="https://dl.mercurylang.org/release/release-notes-22.01.html">Mercury
-          22.01 news</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          xfsprogs was update to version 5.15, which enables inobtcount
-          and bigtime by default on filesystem creation. Support for
-          these features was added in kernel 5.10 and deemed stable in
-          kernel 5.15. If you want to be able to mount XFS filesystems
-          created with this release of xfsprogs on kernel releases older
-          than 5.10, you need to format them with
-          <literal>mkfs.xfs -m bigtime=0 -m inobtcount=0</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.xserver.desktopManager.xfce</literal> now
-          includes Xfce’s screen locker,
-          <literal>xfce4-screensaver</literal> that is enabled by
-          default. You can disable it by setting
-          <literal>false</literal> to
-          <link linkend="opt-services.xserver.desktopManager.xfce.enableScreensaver">services.xserver.desktopManager.xfce.enableScreensaver</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>hadoop</literal> package has added support for
-          <literal>aarch64-linux</literal> and
-          <literal>aarch64-darwin</literal> as of 3.3.1
-          (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/158613">#158613</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>R</literal> package now builds again on
-          <literal>aarch64-darwin</literal>
-          (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/158992">#158992</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nss</literal> package was split into
-          <literal>nss_esr</literal> and <literal>nss_latest</literal>,
-          with <literal>nss</literal> being an alias for
-          <literal>nss_esr</literal>. This was done to ease maintenance
-          of <literal>nss</literal> and dependent high-profile packages
-          like <literal>firefox</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The default <literal>scribus</literal> version is now 1.5,
-          while version 1.4 is still available as
-          <literal>scribus_1_4</literal>
-          (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/172700">#172700</link>).
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Nextcloud module now supports to create a Mysql database
-          automatically with
-          <literal>services.nextcloud.database.createLocally</literal>
-          enabled.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Nextcloud module now allows setting the value of the
-          <literal>max-age</literal> directive of the
-          <literal>Strict-Transport-Security</literal> HTTP header,
-          which is now controlled by the
-          <literal>services.nextcloud.https</literal> option, rather
-          than <literal>services.nginx.recommendedHttpHeaders</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>spark3</literal> package has been updated from
-          3.1.2 to 3.2.1
-          (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/160075">#160075</link>):
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              Testing has been enabled for
-              <literal>aarch64-linux</literal> in addition to
-              <literal>x86_64-linux</literal>.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              The <literal>spark3</literal> package is now usable on
-              <literal>aarch64-darwin</literal> as a result of
-              <link xlink:href="https://github.com/NixOS/nixpkgs/pull/158613">#158613</link>
-              and
-              <link xlink:href="https://github.com/NixOS/nixpkgs/pull/158992">#158992</link>.
-            </para>
-          </listitem>
-        </itemizedlist>
-      </listitem>
-      <listitem>
-        <para>
-          The option <literal>services.snapserver.openFirewall</literal>
-          will no longer default to <literal>true</literal> starting
-          with NixOS 22.11. Enable it explicitly if you need to control
-          Snapserver remotely or connect streamig clients from other
-          hosts.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The option
-          <link xlink:href="options.html#opt-networking.useDHCP">networking.useDHCP</link>
-          isn’t deprecated anymore. When using
-          <link xlink:href="options.html#opt-networking.useNetworkd"><literal>systemd-networkd</literal></link>,
-          a generic <literal>.network</literal>-unit is added which
-          enables DHCP for each interface matching
-          <literal>en*</literal>, <literal>eth*</literal> or
-          <literal>wl*</literal> with priority 99 (which means that it
-          doesn’t have any effect if such an interface is matched by a
-          <literal>.network-</literal>unit with a lower priority). In
-          case of scripted networking, no behavior was changed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The new
-          <link xlink:href="https://nixos.org/manual/nixpkgs/stable/#sec-postgresqlTestHook"><literal>postgresqlTestHook</literal></link>
-          runs a PostgreSQL server for the duration of package checks.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>zfs</literal> was updated from 2.1.4 to 2.1.5,
-          enabling it to be used with Linux kernel 5.18.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>stdenv.mkDerivation</literal> now supports a
-          self-referencing <literal>finalAttrs:</literal> parameter
-          containing the final <literal>mkDerivation</literal> arguments
-          including overrides. <literal>drv.overrideAttrs</literal> now
-          supports two parameters
-          <literal>finalAttrs: previousAttrs:</literal>. This allows
-          packaging configuration to be overridden in a consistent
-          manner by providing an alternative to
-          <literal>rec {}</literal> syntax.
-        </para>
-        <para>
-          Additionally, <literal>passthru</literal> can now reference
-          <literal>finalAttrs.finalPackage</literal> containing the
-          final package, including attributes such as the output paths
-          and <literal>overrideAttrs</literal>.
-        </para>
-        <para>
-          New language integrations can be simplified by overriding a
-          <quote>prototype</quote> package containing the
-          language-specific logic. This removes the need for a extra
-          layer of overriding for the <quote>generic builder</quote>
-          arguments, thus removing a usability problem and source of
-          error.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
deleted file mode 100644
index e8cd84783e4c..000000000000
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ /dev/null
@@ -1,505 +0,0 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-22.11">
-  <title>Release 22.11 (“Raccoon”, 2022.11/??)</title>
-  <para>
-    Support is planned until the end of June 2023, handing over to
-    23.05.
-  </para>
-  <section xml:id="sec-release-22.11-highlights">
-    <title>Highlights</title>
-    <para>
-      In addition to numerous new and upgraded packages, this release
-      has the following highlights:
-    </para>
-    <itemizedlist>
-      <listitem>
-        <para>
-          During cross-compilation, tests are now executed if the test
-          suite can be executed by the build platform. This is the case
-          when doing “native” cross-compilation where the build and host
-          platforms are largely the same, but the nixpkgs’ cross
-          compilation infrastructure is used, e.g.
-          <literal>pkgsStatic</literal> and <literal>pkgsLLVM</literal>.
-          Another possibility is that the build platform is a superset
-          of the host platform, e.g. when cross-compiling from
-          <literal>x86_64-unknown-linux</literal> to
-          <literal>i686-unknown-linux</literal>. The predicate gating
-          test suite execution is the newly added
-          <literal>canExecute</literal> predicate: You can e.g. check if
-          <literal>stdenv.buildPlatform</literal> can execute binaries
-          built for <literal>stdenv.hostPlatform</literal> (i.e.
-          produced by <literal>stdenv.cc</literal>) by evaluating
-          <literal>stdenv.buildPlatform.canExecute stdenv.hostPlatform</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nixpkgs.hostPlatform</literal> and
-          <literal>nixpkgs.buildPlatform</literal> options have been
-          added. These cover and override the
-          <literal>nixpkgs.{system,localSystem,crossSystem}</literal>
-          options.
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>hostPlatform</literal> is the platform or
-              <quote><literal>system</literal></quote> string of the
-              NixOS system described by the configuration.
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>buildPlatform</literal> is the platform that is
-              responsible for building the NixOS configuration. It
-              defaults to the <literal>hostPlatform</literal>, for a
-              non-cross build configuration. To cross compile, set
-              <literal>buildPlatform</literal> to a different value.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          The new options convey the same information, but with fewer
-          options, and following the Nixpkgs terminology.
-        </para>
-        <para>
-          The existing options
-          <literal>nixpkgs.{system,localSystem,crossSystem}</literal>
-          have not been formally deprecated, to allow for evaluation of
-          the change and to allow for a transition period so that in
-          time the ecosystem can switch without breaking compatibility
-          with any supported NixOS release.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>nixos-generate-config</literal> now generates
-          configurations that can be built in pure mode. This is
-          achieved by setting the new
-          <literal>nixpkgs.hostPlatform</literal> option.
-        </para>
-        <para>
-          You may have to unset the <literal>system</literal> parameter
-          in <literal>lib.nixosSystem</literal>, or similarly remove
-          definitions of the
-          <literal>nixpkgs.{system,localSystem,crossSystem}</literal>
-          options.
-        </para>
-        <para>
-          Alternatively, you can remove the
-          <literal>hostPlatform</literal> line and use NixOS like you
-          would in NixOS 22.05 and earlier.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP now defaults to PHP 8.1, updated from 8.0.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Cinnamon has been updated to 5.4.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>hardware.nvidia</literal> has a new option
-          <literal>open</literal> that can be used to opt in the
-          opensource version of NVIDIA kernel driver. Note that the
-          driver’s support for GeForce and Workstation GPUs is still
-          alpha quality, see
-          <link xlink:href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/">NVIDIA
-          Releases Open-Source GPU Kernel Modules</link> for the
-          official announcement.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-22.11-new-services">
-    <title>New Services</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/jollheef/appvm">appvm</link>,
-          Nix based app VMs. Available as
-          <link xlink:href="options.html#opt-virtualisation.appvm.enable">virtualisation.appvm</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/mozilla-services/syncstorage-rs">syncstorage-rs</link>,
-          a self-hostable sync server for Firefox. Available as
-          <link xlink:href="options.html#opt-services.firefox-syncserver.enable">services.firefox-syncserver</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://dragonflydb.io/">dragonflydb</link>,
-          a modern replacement for Redis and Memcached. Available as
-          <link linkend="opt-services.dragonflydb.enable">services.dragonflydb</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://komga.org/">Komga</link>, a free and
-          open source comics/mangas media server. Available as
-          <link linkend="opt-services.komga.enable">services.komga</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://hbase.apache.org/">HBase
-          cluster</link>, a distributed, scalable, big data store.
-          Available as
-          <link xlink:href="options.html#opt-services.hadoop.hbase.enable">services.hadoop.hbase</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/leetronics/infnoise">infnoise</link>,
-          a hardware True Random Number Generator dongle. Available as
-          <link xlink:href="options.html#opt-services.infnoise.enable">services.infnoise</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/jtroo/kanata">kanata</link>,
-          a tool to improve keyboard comfort and usability with advanced
-          customization. Available as
-          <link xlink:href="options.html#opt-services.kanata.enable">services.kanata</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/aiberia/persistent-evdev">persistent-evdev</link>,
-          a daemon to add virtual proxy devices that mirror a physical
-          input device but persist even if the underlying hardware is
-          hot-plugged. Available as
-          <link linkend="opt-services.persistent-evdev.enable">services.persistent-evdev</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://schleuder.org/">schleuder</link>, a
-          mailing list manager with PGP support. Enable using
-          <link linkend="opt-services.schleuder.enable">services.schleuder</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.expressvpn.com">expressvpn</link>,
-          the CLI client for ExpressVPN. Available as
-          <link linkend="opt-services.expressvpn.enable">services.expressvpn</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://www.grafana.com/oss/tempo/">Grafana
-          Tempo</link>, a distributed tracing store. Available as
-          <link linkend="opt-services.tempo.enable">services.tempo</link>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <link xlink:href="https://github.com/zalando/patroni">Patroni</link>,
-          a template for PostgreSQL HA with ZooKeeper, etcd or Consul.
-          Available as
-          <link xlink:href="options.html#opt-services.patroni.enable">services.patroni</link>.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-22.11-incompatibilities">
-    <title>Backward Incompatibilities</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The <literal>isCompatible</literal> predicate checking CPU
-          compatibility is no longer exposed by the platform sets
-          generated using <literal>lib.systems.elaborate</literal>. In
-          most cases you will want to use the new
-          <literal>canExecute</literal> predicate instead which also
-          considers the kernel / syscall interface. It is briefly
-          described in the release’s
-          <link linkend="sec-release-22.11-highlights">highlights
-          section</link>.
-          <literal>lib.systems.parse.isCompatible</literal> still
-          exists, but has changed semantically: Architectures with
-          differing endianness modes are <emphasis>no longer considered
-          compatible</emphasis>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>ngrok</literal> has been upgraded from 2.3.40 to
-          3.0.4. Please see
-          <link xlink:href="https://ngrok.com/docs/guides/upgrade-v2-v3">the
-          upgrade guide</link> and
-          <link xlink:href="https://ngrok.com/docs/ngrok-agent/changelog">changelog</link>.
-          Notably, breaking changes are that the config file format has
-          changed and support for single hypen arguments was dropped.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>i18n.supportedLocales</literal> is now by default
-          only generated with the locales set in
-          <literal>i18n.defaultLocale</literal> and
-          <literal>i18n.extraLocaleSettings</literal>. This got
-          partially copied over from the minimal profile and reduces the
-          final system size by up to 200MB. If you require all locales
-          installed set the option to
-          <literal>[ &quot;all&quot; ]</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>isPowerPC</literal> predicate, found on
-          <literal>platform</literal> attrsets
-          (<literal>hostPlatform</literal>,
-          <literal>buildPlatform</literal>,
-          <literal>targetPlatform</literal>, etc) has been removed in
-          order to reduce confusion. The predicate was was defined such
-          that it matches only the 32-bit big-endian members of the
-          POWER/PowerPC family, despite having a name which would imply
-          a broader set of systems. If you were using this predicate,
-          you can replace <literal>foo.isPowerPC</literal> with
-          <literal>(with foo; isPower &amp;&amp; is32bit &amp;&amp; isBigEndian)</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>fetchgit</literal> fetcher now uses
-          <link xlink:href="https://www.git-scm.com/docs/git-sparse-checkout/2.37.0#_internalscone_mode_handling">cone
-          mode</link> by default for sparse checkouts.
-          <link xlink:href="https://www.git-scm.com/docs/git-sparse-checkout/2.37.0#_internalsnon_cone_problems">Non-cone
-          mode</link> can be enabled by passing
-          <literal>nonConeMode = true</literal>, but note that non-cone
-          mode is deprecated and this option may be removed alongside a
-          future Git update without notice.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>bsp-layout</literal> no longer uses the command
-          <literal>cycle</literal> to switch to other window layouts, as
-          it got replaced by the commands <literal>previous</literal>
-          and <literal>next</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Barco ClickShare driver/client package
-          <literal>pkgs.clickshare-csc1</literal> and the option
-          <literal>programs.clickshare-csc1.enable</literal> have been
-          removed, as it requires <literal>qt4</literal>, which reached
-          its end-of-life 2015 and will no longer be supported by
-          nixpkgs.
-          <link xlink:href="https://www.barco.com/de/support/knowledge-base/4380-can-i-use-linux-os-with-clickshare-base-units">According
-          to Barco</link> many of their base unit models can be used
-          with Google Chrome and the Google Cast extension.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>services.hbase</literal> has been renamed to
-          <literal>services.hbase-standalone</literal>. For production
-          HBase clusters, use <literal>services.hadoop.hbase</literal>
-          instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP 7.4 is no longer supported due to upstream not supporting
-          this version for the entire lifecycle of the 22.11 release.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>pkgs.cosign</literal> does not provide the
-          <literal>cosigned</literal> binary anymore.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          riak package removed along with
-          <literal>services.riak</literal> module, due to lack of
-          maintainer to update the package.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          xow package removed along with the
-          <literal>hardware.xow</literal> module, due to the project
-          being deprecated in favor of <literal>xone</literal>, which is
-          available via the <literal>hardware.xone</literal> module.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          virtlyst package and <literal>services.virtlyst</literal>
-          module removed, due to lack of maintainers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>services.graphite.api</literal> and
-          <literal>services.graphite.beacon</literal> NixOS options, and
-          the <literal>python3.pkgs.graphite_api</literal>,
-          <literal>python3.pkgs.graphite_beacon</literal> and
-          <literal>python3.pkgs.influxgraph</literal> packages, have
-          been removed due to lack of upstream maintenance.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>meta.mainProgram</literal> attribute of packages
-          in <literal>wineWowPackages</literal> now defaults to
-          <literal>&quot;wine64&quot;</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          (Neo)Vim can not be configured with
-          <literal>configure.pathogen</literal> anymore to reduce
-          maintainance burden. Use <literal>configure.packages</literal>
-          instead.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>k3s</literal> no longer supports docker as runtime
-          due to upstream dropping support.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="sec-release-22.11-notable-changes">
-    <title>Other Notable Changes</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The <literal>xplr</literal> package has been updated from
-          0.18.0 to 0.19.0, which brings some breaking changes. See the
-          <link xlink:href="https://github.com/sayanarijit/xplr/releases/tag/v0.19.0">upstream
-          release notes</link> for more details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>github-runner</literal> gained support for ephemeral
-          runners and registrations using a personal access token (PAT)
-          instead of a registration token. See
-          <literal>services.github-runner.ephemeral</literal> and
-          <literal>services.github-runner.tokenFile</literal> for
-          details.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          A new module was added for the Saleae Logic device family,
-          providing the options
-          <literal>hardware.saleae-logic.enable</literal> and
-          <literal>hardware.saleae-logic.package</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The Redis module now disables RDB persistence when
-          <literal>services.redis.servers.&lt;name&gt;.save = []</literal>
-          instead of using the Redis default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Neo4j was updated from version 3 to version 4. See this
-          <link xlink:href="https://neo4j.com/docs/upgrade-migration-guide/current/">migration
-          guide</link> on how to migrate your Neo4j instance.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Matrix Synapse now requires entries in the
-          <literal>state_group_edges</literal> table to be unique, in
-          order to prevent accidentally introducing duplicate
-          information (for example, because a database backup was
-          restored multiple times). If your Synapse database already has
-          duplicate rows in this table, this could fail with an error
-          and require manual remediation.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>dockerTools.buildImage</literal> deprecates the
-          misunderstood <literal>contents</literal> parameter, in favor
-          of <literal>copyToRoot</literal>. Use
-          <literal>copyToRoot = buildEnv { ... };</literal> or similar
-          if you intend to add packages to <literal>/bin</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          memtest86+ was updated from 5.00-coreboot-002 to 6.00-beta2.
-          It is now the upstream version from https://www.memtest.org/,
-          as coreboot’s fork is no longer available.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The udisks2 service, available at
-          <literal>services.udisks2.enable</literal>, is now disabled by
-          default. It will automatically be enabled through services and
-          desktop environments as needed. This also means that polkit
-          will now actually be disabled by default. The default for
-          <literal>security.polkit.enable</literal> was already flipped
-          in the previous release, but udisks2 being enabled by default
-          re-enabled it.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Add udev rules for the Teensy family of microcontrollers.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>pass-secret-service</literal> package now
-          includes systemd units from upstream, so adding it to the
-          NixOS <literal>services.dbus.packages</literal> option will
-          make it start automatically as a systemd user service when an
-          application tries to talk to the libsecret D-Bus API.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new module for AMD SEV CPU functionality, which
-          grants access to the hardware.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new module for the <literal>thunar</literal>
-          program (the Xfce file manager), which depends on the
-          <literal>xfconf</literal> dbus service, and also has a dbus
-          service and a systemd unit. The option
-          <literal>services.xserver.desktopManager.xfce.thunarPlugins</literal>
-          has been renamed to
-          <literal>programs.thunar.plugins</literal>, and in a future
-          release it may be removed.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          There is a new module for the <literal>xfconf</literal>
-          program (the Xfce configuration storage system), which has a
-          dbus service.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          The <literal>nomad</literal> package now defaults to 1.3,
-          which no longer has a downgrade path to releases 1.2 or older.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</section>
diff --git a/nixpkgs/nixos/doc/manual/installation/building-nixos.chapter.md b/nixpkgs/nixos/doc/manual/installation/building-nixos.chapter.md
index 17da261fbdaa..7b0b5ea1c447 100644
--- a/nixpkgs/nixos/doc/manual/installation/building-nixos.chapter.md
+++ b/nixpkgs/nixos/doc/manual/installation/building-nixos.chapter.md
@@ -9,7 +9,7 @@ You have two options:
 - Combine them with (any of) your host config(s)
 
 System images, such as the live installer ones, know how to enforce configuration settings
-on wich they immediately depend in order to work correctly.
+on which they immediately depend in order to work correctly.
 
 However, if you are confident, you can opt to override those
 enforced values with `mkForce`.
@@ -75,6 +75,6 @@ configuration values upon which the correct functioning of the image depends.
 For example, the iso base image overrides those file systems which it needs at a minimum
 for correct functioning, while the installer base image overrides the entire file system
 layout because there can't be any other guarantees on a live medium than those given
-by the live medium itself. The latter is especially true befor formatting the target
+by the live medium itself. The latter is especially true before formatting the target
 block device(s). On the other hand, the netboot iso only overrides its minimum dependencies
 since netboot images are always made-to-target.
diff --git a/nixpkgs/nixos/doc/manual/installation/changing-config.chapter.md b/nixpkgs/nixos/doc/manual/installation/changing-config.chapter.md
index 8a404f085d7c..11b49ccb1f67 100644
--- a/nixpkgs/nixos/doc/manual/installation/changing-config.chapter.md
+++ b/nixpkgs/nixos/doc/manual/installation/changing-config.chapter.md
@@ -13,7 +13,7 @@ booting, and try to realise the configuration in the running system
 (e.g., by restarting system services).
 
 ::: {.warning}
-This command doesn\'t start/stop [user services](#opt-systemd.user.services)
+This command doesn't start/stop [user services](#opt-systemd.user.services)
 automatically. `nixos-rebuild` only runs a `daemon-reload` for each user with running
 user services.
 :::
@@ -51,7 +51,7 @@ GRUB 2 boot screen by giving it a different *profile name*, e.g.
 ```
 
 which causes the new configuration (and previous ones created using
-`-p test`) to show up in the GRUB submenu "NixOS - Profile \'test\'".
+`-p test`) to show up in the GRUB submenu "NixOS - Profile 'test'".
 This can be useful to separate test configurations from "stable"
 configurations.
 
diff --git a/nixpkgs/nixos/doc/manual/installation/installation.md b/nixpkgs/nixos/doc/manual/installation/installation.md
new file mode 100644
index 000000000000..140594256609
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/installation/installation.md
@@ -0,0 +1,11 @@
+# Installation {#ch-installation}
+
+This section describes how to obtain, install, and configure NixOS for first-time use.
+
+```{=include=} chapters
+obtaining.chapter.md
+installing.chapter.md
+changing-config.chapter.md
+upgrading.chapter.md
+building-nixos.chapter.md
+```
diff --git a/nixpkgs/nixos/doc/manual/installation/installation.xml b/nixpkgs/nixos/doc/manual/installation/installation.xml
deleted file mode 100644
index ba07d71d0ca3..000000000000
--- a/nixpkgs/nixos/doc/manual/installation/installation.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<part xmlns="http://docbook.org/ns/docbook"
-      xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      version="5.0"
-      xml:id="ch-installation">
- <title>Installation</title>
- <partintro xml:id="ch-installation-intro">
-  <para>
-   This section describes how to obtain, install, and configure NixOS for
-   first-time use.
-  </para>
- </partintro>
- <xi:include href="../from_md/installation/obtaining.chapter.xml" />
- <xi:include href="../from_md/installation/installing.chapter.xml" />
- <xi:include href="../from_md/installation/changing-config.chapter.xml" />
- <xi:include href="../from_md/installation/upgrading.chapter.xml" />
- <xi:include href="../from_md/installation/building-nixos.chapter.xml" />
-</part>
diff --git a/nixpkgs/nixos/doc/manual/installation/installing-from-other-distro.section.md b/nixpkgs/nixos/doc/manual/installation/installing-from-other-distro.section.md
index fa8806f791d5..921592fe5357 100644
--- a/nixpkgs/nixos/doc/manual/installation/installing-from-other-distro.section.md
+++ b/nixpkgs/nixos/doc/manual/installation/installing-from-other-distro.section.md
@@ -30,7 +30,7 @@ The first steps to all these are the same:
 
 1.  Switch to the NixOS channel:
 
-    If you\'ve just installed Nix on a non-NixOS distribution, you will
+    If you've just installed Nix on a non-NixOS distribution, you will
     be on the `nixpkgs` channel by default.
 
     ```ShellSession
@@ -49,10 +49,10 @@ The first steps to all these are the same:
 
 1.  Install the NixOS installation tools:
 
-    You\'ll need `nixos-generate-config` and `nixos-install`, but this
+    You'll need `nixos-generate-config` and `nixos-install`, but this
     also makes some man pages and `nixos-enter` available, just in case
     you want to chroot into your NixOS partition. NixOS installs these
-    by default, but you don\'t have NixOS yet..
+    by default, but you don't have NixOS yet..
 
     ```ShellSession
     $ nix-env -f '<nixpkgs>' -iA nixos-install-tools
@@ -70,7 +70,7 @@ The first steps to all these are the same:
     refer to the partitioning, file-system creation, and mounting steps
     of [](#sec-installation)
 
-    If you\'re about to install NixOS in place using `NIXOS_LUSTRATE`
+    If you're about to install NixOS in place using `NIXOS_LUSTRATE`
     there is nothing to do for this step.
 
 1.  Generate your NixOS configuration:
@@ -79,12 +79,12 @@ The first steps to all these are the same:
     $ sudo `which nixos-generate-config` --root /mnt
     ```
 
-    You\'ll probably want to edit the configuration files. Refer to the
+    You'll probably want to edit the configuration files. Refer to the
     `nixos-generate-config` step in [](#sec-installation) for more
     information.
 
     Consider setting up the NixOS bootloader to give you the ability to
-    boot on your existing Linux partition. For instance, if you\'re
+    boot on your existing Linux partition. For instance, if you're
     using GRUB and your existing distribution is running Ubuntu, you may
     want to add something like this to your `configuration.nix`:
 
@@ -148,19 +148,19 @@ The first steps to all these are the same:
     Generate your NixOS configuration:
 
     ```ShellSession
-    $ sudo `which nixos-generate-config` --root /
+    $ sudo `which nixos-generate-config`
     ```
 
     Note that this will place the generated configuration files in
-    `/etc/nixos`. You\'ll probably want to edit the configuration files.
+    `/etc/nixos`. You'll probably want to edit the configuration files.
     Refer to the `nixos-generate-config` step in
     [](#sec-installation) for more information.
 
-    You\'ll likely want to set a root password for your first boot using
-    the configuration files because you won\'t have a chance to enter a
-    password until after you reboot. You can initalize the root password
-    to an empty one with this line: (and of course don\'t forget to set
-    one once you\'ve rebooted or to lock the account with
+    You'll likely want to set a root password for your first boot using
+    the configuration files because you won't have a chance to enter a
+    password until after you reboot. You can initialize the root password
+    to an empty one with this line: (and of course don't forget to set
+    one once you've rebooted or to lock the account with
     `sudo passwd -l root` if you use `sudo`)
 
     ```nix
@@ -186,7 +186,7 @@ The first steps to all these are the same:
     bootup scripts require its presence).
 
     `/etc/NIXOS_LUSTRATE` tells the NixOS bootup scripts to move
-    *everything* that\'s in the root partition to `/old-root`. This will
+    *everything* that's in the root partition to `/old-root`. This will
     move your existing distribution out of the way in the very early
     stages of the NixOS bootup. There are exceptions (we do need to keep
     NixOS there after all), so the NixOS lustrate process will not
@@ -201,10 +201,10 @@ The first steps to all these are the same:
 
     ::: {.note}
     Support for `NIXOS_LUSTRATE` was added in NixOS 16.09. The act of
-    \"lustrating\" refers to the wiping of the existing distribution.
+    "lustrating" refers to the wiping of the existing distribution.
     Creating `/etc/NIXOS_LUSTRATE` can also be used on NixOS to remove
-    all mutable files from your root partition (anything that\'s not in
-    `/nix` or `/boot` gets \"lustrated\" on the next boot.
+    all mutable files from your root partition (anything that's not in
+    `/nix` or `/boot` gets "lustrated" on the next boot.
 
     lustrate /ˈlʌstreɪt/ verb.
 
@@ -212,14 +212,14 @@ The first steps to all these are the same:
     ritual action.
     :::
 
-    Let\'s create the files:
+    Let's create the files:
 
     ```ShellSession
     $ sudo touch /etc/NIXOS
     $ sudo touch /etc/NIXOS_LUSTRATE
     ```
 
-    Let\'s also make sure the NixOS configuration files are kept once we
+    Let's also make sure the NixOS configuration files are kept once we
     reboot on NixOS:
 
     ```ShellSession
@@ -233,7 +233,7 @@ The first steps to all these are the same:
 
     ::: {.warning}
     Once you complete this step, your current distribution will no
-    longer be bootable! If you didn\'t get all the NixOS configuration
+    longer be bootable! If you didn't get all the NixOS configuration
     right, especially those settings pertaining to boot loading and root
     partition, NixOS may not be bootable either. Have a USB rescue
     device ready in case this happens.
@@ -247,7 +247,7 @@ The first steps to all these are the same:
     Cross your fingers, reboot, hopefully you should get a NixOS prompt!
 
 1.  If for some reason you want to revert to the old distribution,
-    you\'ll need to boot on a USB rescue disk and do something along
+    you'll need to boot on a USB rescue disk and do something along
     these lines:
 
     ```ShellSession
@@ -264,14 +264,14 @@ The first steps to all these are the same:
     This may work as is or you might also need to reinstall the boot
     loader.
 
-    And of course, if you\'re happy with NixOS and no longer need the
+    And of course, if you're happy with NixOS and no longer need the
     old distribution:
 
     ```ShellSession
     sudo rm -rf /old-root
     ```
 
-1.  It\'s also worth noting that this whole process can be automated.
+1.  It's also worth noting that this whole process can be automated.
     This is especially useful for Cloud VMs, where provider do not
     provide NixOS. For instance,
     [nixos-infect](https://github.com/elitak/nixos-infect) uses the
diff --git a/nixpkgs/nixos/doc/manual/installation/installing-kexec.section.md b/nixpkgs/nixos/doc/manual/installation/installing-kexec.section.md
index 286cbbda6a69..61d8e8e5999b 100644
--- a/nixpkgs/nixos/doc/manual/installation/installing-kexec.section.md
+++ b/nixpkgs/nixos/doc/manual/installation/installing-kexec.section.md
@@ -30,7 +30,7 @@ This will create a `result` directory containing the following:
 These three files are meant to be copied over to the other already running
 Linux Distribution.
 
-Note it's symlinks pointing elsewhere, so `cd` in, and use
+Note its symlinks pointing elsewhere, so `cd` in, and use
 `scp * root@$destination` to copy it over, rather than rsync.
 
 Once you finished copying, execute `kexec-boot` *on the destination*, and after
diff --git a/nixpkgs/nixos/doc/manual/installation/installing-usb.section.md b/nixpkgs/nixos/doc/manual/installation/installing-usb.section.md
index d893e22e6381..adfe22ea2f00 100644
--- a/nixpkgs/nixos/doc/manual/installation/installing-usb.section.md
+++ b/nixpkgs/nixos/doc/manual/installation/installing-usb.section.md
@@ -1,31 +1,72 @@
-# Booting from a USB Drive {#sec-booting-from-usb}
+# Booting from a USB flash drive {#sec-booting-from-usb}
 
-For systems without CD drive, the NixOS live CD can be booted from a USB
-stick. You can use the `dd` utility to write the image:
-`dd if=path-to-image of=/dev/sdX`. Be careful about specifying the correct
-drive; you can use the `lsblk` command to get a list of block devices.
+The image has to be written verbatim to the USB flash drive for it to be
+bootable on UEFI and BIOS systems. Here are the recommended tools to do that.
 
-::: {.note}
-::: {.title}
-On macOS
-:::
+## Creating bootable USB flash drive with a graphical tool {#sec-booting-from-usb-graphical}
+
+Etcher is a popular and user-friendly tool. It works on Linux, Windows and macOS.
+
+Download it from [balena.io](https://www.balena.io/etcher/), start the program,
+select the downloaded NixOS ISO, then select the USB flash drive and flash it.
 
-```ShellSession
-$ diskutil list
-[..]
-/dev/diskN (external, physical):
-   #:                       TYPE NAME                    SIZE       IDENTIFIER
-[..]
-$ diskutil unmountDisk diskN
-Unmount of all volumes on diskN was successful
-$ sudo dd if=nix.iso of=/dev/rdiskN bs=1M
-```
-
-Using the \'raw\' `rdiskN` device instead of `diskN` completes in
-minutes instead of hours. After `dd` completes, a GUI dialog \"The disk
-you inserted was not readable by this computer\" will pop up, which can
-be ignored.
+::: {.warning}
+Etcher reports errors and usage statistics by default, which can be disabled in
+the settings.
 :::
 
-The `dd` utility will write the image verbatim to the drive, making it
-the recommended option for both UEFI and non-UEFI installations.
+An alternative is [USBImager](https://bztsrc.gitlab.io/usbimager),
+which is very simple and does not connect to the internet. Download the version
+with write-only (wo) interface for your system. Start the program,
+select the image, select the USB flash drive and click "Write".
+
+## Creating bootable USB flash drive from a Terminal on Linux {#sec-booting-from-usb-linux}
+
+1. Plug in the USB flash drive.
+2. Find the corresponding device with `lsblk`. You can distinguish them by
+   their size.
+3. Make sure all partitions on the device are properly unmounted. Replace `sdX`
+   with your device (e.g. `sdb`).
+
+  ```ShellSession
+  sudo umount /dev/sdX*
+  ```
+
+4. Then use the `dd` utility to write the image to the USB flash drive.
+
+  ```ShellSession
+  sudo dd if=<path-to-image> of=/dev/sdX bs=4M conv=fsync
+  ```
+
+## Creating bootable USB flash drive from a Terminal on macOS {#sec-booting-from-usb-macos}
+
+1. Plug in the USB flash drive.
+2. Find the corresponding device with `diskutil list`. You can distinguish them
+   by their size.
+3. Make sure all partitions on the device are properly unmounted. Replace `diskX`
+   with your device (e.g. `disk1`).
+
+  ```ShellSession
+  diskutil unmountDisk diskX
+  ```
+
+4. Then use the `dd` utility to write the image to the USB flash drive.
+
+  ```ShellSession
+  sudo dd if=<path-to-image> of=/dev/rdiskX bs=4m
+  ```
+
+  After `dd` completes, a GUI dialog "The disk
+  you inserted was not readable by this computer" will pop up, which can
+  be ignored.
+
+  ::: {.note}
+  Using the 'raw' `rdiskX` device instead of `diskX` with dd completes in
+  minutes instead of hours.
+  :::
+
+5. Eject the disk when it is finished.
+
+  ```ShellSession
+  diskutil eject /dev/diskX
+  ```
diff --git a/nixpkgs/nixos/doc/manual/installation/installing-virtualbox-guest.section.md b/nixpkgs/nixos/doc/manual/installation/installing-virtualbox-guest.section.md
index e9c2a621c1bb..004838e586be 100644
--- a/nixpkgs/nixos/doc/manual/installation/installing-virtualbox-guest.section.md
+++ b/nixpkgs/nixos/doc/manual/installation/installing-virtualbox-guest.section.md
@@ -1,4 +1,4 @@
-# Installing in a VirtualBox guest {#sec-instaling-virtualbox-guest}
+# Installing in a VirtualBox guest {#sec-installing-virtualbox-guest}
 
 Installing NixOS into a VirtualBox guest is convenient for users who
 want to try NixOS without installing it on bare metal. If you want to
@@ -6,7 +6,7 @@ use a pre-made VirtualBox appliance, it is available at [the downloads
 page](https://nixos.org/nixos/download.html). If you want to set up a
 VirtualBox guest manually, follow these instructions:
 
-1.  Add a New Machine in VirtualBox with OS Type \"Linux / Other Linux\"
+1.  Add a New Machine in VirtualBox with OS Type "Linux / Other Linux"
 
 1.  Base Memory Size: 768 MB or higher.
 
@@ -16,7 +16,7 @@ VirtualBox guest manually, follow these instructions:
 
 1.  Click on Settings / System / Processor and enable PAE/NX
 
-1.  Click on Settings / System / Acceleration and enable \"VT-x/AMD-V\"
+1.  Click on Settings / System / Acceleration and enable "VT-x/AMD-V"
     acceleration
 
 1.  Click on Settings / Display / Screen and select VMSVGA as Graphics
@@ -41,7 +41,7 @@ boot.initrd.checkJournalingFS = false;
 
 Shared folders can be given a name and a path in the host system in the
 VirtualBox settings (Machine / Settings / Shared Folders, then click on
-the \"Add\" icon). Add the following to the
+the "Add" icon). Add the following to the
 `/etc/nixos/configuration.nix` to auto-mount them. If you do not add
 `"nofail"`, the system will not boot properly.
 
diff --git a/nixpkgs/nixos/doc/manual/installation/installing.chapter.md b/nixpkgs/nixos/doc/manual/installation/installing.chapter.md
index 7254f9d18406..53cf9ed14c33 100644
--- a/nixpkgs/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixpkgs/nixos/doc/manual/installation/installing.chapter.md
@@ -1,30 +1,143 @@
 # Installing NixOS {#sec-installation}
 
-## Booting the system {#sec-installation-booting}
+## Booting from the install medium {#sec-installation-booting}
+
+To begin the installation, you have to boot your computer from the install drive.
+
+1.   Plug in the install drive. Then turn on or restart your computer.
+
+2.   Open the boot menu by pressing the appropriate key, which is usually shown
+     on the display on early boot.
+     Select the USB flash drive (the option usually contains the word "USB").
+     If you choose the incorrect drive, your computer will likely continue to
+     boot as normal. In that case restart your computer and pick a
+     different drive.
+
+     ::: {.note}
+     The key to open the boot menu is different across computer brands and even
+     models. It can be [F12]{.keycap}, but also [F1]{.keycap},
+     [F9]{.keycap}, [F10]{.keycap}, [Enter]{.keycap}, [Del]{.keycap},
+     [Esc]{.keycap} or another function key. If you are unsure and don't see
+     it on the early boot screen, you can search online for your computers
+     brand, model followed by "boot from usb".
+     The computer might not even have that feature, so you have to go into the
+     BIOS/UEFI settings to change the boot order. Again, search online for
+     details about your specific computer model.
+
+     For Apple computers with Intel processors press and hold the [⌥]{.keycap}
+     (Option or Alt) key until you see the boot menu. On Apple silicon press
+     and hold the power button.
+     :::
+
+     ::: {.note}
+     If your computer supports both BIOS and UEFI boot, choose the UEFI option.
+     :::
+
+     ::: {.note}
+     If you use a CD for the installation, the computer will probably boot from
+     it automatically. If not, choose the option containing the word "CD" from
+     the boot menu.
+     :::
+
+3.   Shortly after selecting the appropriate boot drive, you should be
+     presented with a menu with different installer options. Leave the default
+     and wait (or press [Enter]{.keycap} to speed up).
+
+4.   The graphical images will start their corresponding desktop environment
+     and the graphical installer, which can take some time. The minimal images
+     will boot to a command line. You have to follow the instructions in
+     [](#sec-installation-manual) there.
+
+## Graphical Installation {#sec-installation-graphical}
+
+The graphical installer is recommended for desktop users and will guide you
+through the installation.
+
+1.   In the "Welcome" screen, you can select the language of the Installer and
+     the installed system.
+
+     ::: {.tip}
+     Leaving the language as "American English" will make it easier to search for
+     error messages in a search engine or to report an issue.
+     :::
+
+2.   Next you should choose your location to have the timezone set correctly.
+     You can actually click on the map!
+
+     ::: {.note}
+     The installer will use an online service to guess your location based on
+     your public IP address.
+     :::
+
+3.   Then you can select the keyboard layout. The default keyboard model should
+     work well with most desktop keyboards. If you have a special keyboard or
+     notebook, your model might be in the list. Select the language you are most
+     comfortable typing in.
+
+4.   On the "Users" screen, you have to type in your display name, login name
+     and password. You can also enable an option to automatically login to the
+     desktop.
+
+5.   Then you have the option to choose a desktop environment. If you want to
+     create a custom setup with a window manager, you can select "No desktop".
+
+     ::: {.tip}
+     If you don't have a favorite desktop and don't know which one to choose,
+     you can stick to either GNOME or Plasma. They have a quite different
+     design, so you should choose whichever you like better.
+     They are both popular choices and well tested on NixOS.
+     :::
+
+6.   You have the option to allow unfree software in the next screen.
+
+7.   The easiest option in the "Partitioning" screen is "Erase disk", which will
+     delete all data from the selected disk and install the system on it.
+     Also select "Swap (with Hibernation)" in the dropdown below it.
+     You have the option to encrypt the whole disk with LUKS.
+
+     ::: {.note}
+     At the top left you see if the Installer was booted with BIOS or UEFI. If
+     you know your system supports UEFI and it shows "BIOS", reboot with the
+     correct option.
+     :::
+
+     ::: {.warning}
+     Make sure you have selected the correct disk at the top and that no
+     valuable data is still on the disk! It will be deleted when
+     formatting the disk.
+     :::
+
+8.   Check the choices you made in the "Summary" and click "Install".
+
+     ::: {.note}
+     The installation takes about 15 minutes. The time varies based on the
+     selected desktop environment, internet connection speed and disk write speed.
+     :::
+
+9.  When the install is complete, remove the USB flash drive and
+    reboot into your new system!
+
+## Manual Installation {#sec-installation-manual}
 
 NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
-installation is by and large the same as a BIOS installation. The
-differences are mentioned in the steps that follow.
+installation is broadly the same as for a BIOS installation. The differences
+are mentioned in the following steps.
 
-The installation media can be burned to a CD, or now more commonly,
-"burned" to a USB drive (see [](#sec-booting-from-usb)).
+The NixOS manual is available by running `nixos-help` in the command line
+or from the application menu in the desktop environment.
 
-The installation media contains a basic NixOS installation. When it's
-finished booting, it should have detected most of your hardware.
-
-The NixOS manual is available by running `nixos-help`.
+To have access to the command line on the graphical images, open
+Terminal (GNOME) or Konsole (Plasma) from the application menu.
 
 You are logged-in automatically as `nixos`. The `nixos` user account has
 an empty password so you can use `sudo` without a password:
+
 ```ShellSession
 $ sudo -i
 ```
 
-If you downloaded the graphical ISO image, you can run `systemctl
-start display-manager` to start the desktop environment. If you want
-to continue on the terminal, you can use `loadkeys` to switch to your
-preferred keyboard layout. (We even provide neo2 via `loadkeys de
-neo`!)
+You can use `loadkeys` to switch to your preferred keyboard layout.
+(We even provide neo2 via `loadkeys de neo`!)
 
 If the text is too small to be legible, try `setfont ter-v32n` to
 increase the font size.
@@ -33,7 +146,8 @@ To install over a serial port connect with `115200n8` (e.g.
 `picocom -b 115200 /dev/ttyUSB0`). When the bootloader lists boot
 entries, select the serial console boot entry.
 
-### Networking in the installer {#sec-installation-booting-networking}
+### Networking in the installer {#sec-installation-manual-networking}
+[]{#sec-installation-booting-networking} <!-- legacy anchor -->
 
 The boot process should have brought up networking (check `ip
 a`). Networking is necessary for the installer, since it will
@@ -48,7 +162,7 @@ network manually, disable NetworkManager with
 `systemctl stop NetworkManager`.
 
 On the minimal installer, NetworkManager is not available, so
-configuration must be perfomed manually. To configure the wifi, first
+configuration must be performed manually. To configure the wifi, first
 start wpa_supplicant with `sudo systemctl start wpa_supplicant`, then
 run `wpa_cli`. For most home networks, you need to type in the following
 commands:
@@ -100,7 +214,8 @@ placed by mounting the image on a different machine). Alternatively you
 must set a password for either `root` or `nixos` with `passwd` to be
 able to login.
 
-## Partitioning and formatting {#sec-installation-partitioning}
+### Partitioning and formatting {#sec-installation-manual-partitioning}
+[]{#sec-installation-partitioning} <!-- legacy anchor -->
 
 The NixOS installer doesn't do any partitioning or formatting, so you
 need to do that yourself.
@@ -112,13 +227,14 @@ below use `parted`, but also provides `fdisk`, `gdisk`, `cfdisk`, and
 The recommended partition scheme differs depending if the computer uses
 *Legacy Boot* or *UEFI*.
 
-### UEFI (GPT) {#sec-installation-partitioning-UEFI}
+#### UEFI (GPT) {#sec-installation-manual-partitioning-UEFI}
+[]{#sec-installation-partitioning-UEFI} <!-- legacy anchor -->
 
-Here\'s an example partition scheme for UEFI, using `/dev/sda` as the
+Here's an example partition scheme for UEFI, using `/dev/sda` as the
 device.
 
 ::: {.note}
-You can safely ignore `parted`\'s informational message about needing to
+You can safely ignore `parted`'s informational message about needing to
 update /etc/fstab.
 :::
 
@@ -158,15 +274,16 @@ update /etc/fstab.
     ```
 
 Once complete, you can follow with
-[](#sec-installation-partitioning-formatting).
+[](#sec-installation-manual-partitioning-formatting).
 
-### Legacy Boot (MBR) {#sec-installation-partitioning-MBR}
+#### Legacy Boot (MBR) {#sec-installation-manual-partitioning-MBR}
+[]{#sec-installation-partitioning-MBR} <!-- legacy anchor -->
 
-Here\'s an example partition scheme for Legacy Boot, using `/dev/sda` as
+Here's an example partition scheme for Legacy Boot, using `/dev/sda` as
 the device.
 
 ::: {.note}
-You can safely ignore `parted`\'s informational message about needing to
+You can safely ignore `parted`'s informational message about needing to
 update /etc/fstab.
 :::
 
@@ -183,8 +300,14 @@ update /etc/fstab.
     # parted /dev/sda -- mkpart primary 1MB -8GB
     ```
 
-3.  Finally, add a *swap* partition. The size required will vary
-    according to needs, here a 8GiB one is created.
+3.  Set the root partition's boot flag to on. This allows the disk to be booted from.
+
+    ```ShellSession
+    # parted /dev/sda -- set 1 boot on
+    ```
+
+4.  Finally, add a *swap* partition. The size required will vary
+    according to needs, here a 8GB one is created.
 
     ```ShellSession
     # parted /dev/sda -- mkpart primary linux-swap -8GB 100%
@@ -196,9 +319,10 @@ update /etc/fstab.
     :::
 
 Once complete, you can follow with
-[](#sec-installation-partitioning-formatting).
+[](#sec-installation-manual-partitioning-formatting).
 
-### Formatting {#sec-installation-partitioning-formatting}
+#### Formatting {#sec-installation-manual-partitioning-formatting}
+[]{#sec-installation-partitioning-formatting} <!-- legacy anchor -->
 
 Use the following commands:
 
@@ -233,7 +357,8 @@ Use the following commands:
 
 -   For creating software RAID devices, use `mdadm`.
 
-## Installing {#sec-installation-installing}
+### Installing {#sec-installation-manual-installing}
+[]{#sec-installation-installing} <!-- legacy anchor -->
 
 1.  Mount the target file system on which NixOS should be installed on
     `/mnt`, e.g.
@@ -296,14 +421,14 @@ Use the following commands:
         specify on which disk the GRUB boot loader is to be installed.
         Without it, NixOS cannot boot.
 
-    :   If there are other operating systems running on the machine before
+        If there are other operating systems running on the machine before
         installing NixOS, the [](#opt-boot.loader.grub.useOSProber)
         option can be set to `true` to automatically add them to the grub
         menu.
 
     UEFI systems
 
-    :   You must select a boot-loader, either system-boot or GRUB. The recommended
+    :   You must select a boot-loader, either systemd-boot or GRUB. The recommended
         option is systemd-boot: set the option [](#opt-boot.loader.systemd-boot.enable)
         to `true`. `nixos-generate-config` should do this automatically
         for new configurations when booted in UEFI mode.
@@ -313,13 +438,13 @@ Use the following commands:
         [`boot.loader.systemd-boot`](#opt-boot.loader.systemd-boot.enable)
         as well.
 
-    :   If you want to use GRUB, set [](#opt-boot.loader.grub.device) to `nodev` and
+        If you want to use GRUB, set [](#opt-boot.loader.grub.device) to `nodev` and
         [](#opt-boot.loader.grub.efiSupport) to `true`.
 
-    :   With system-boot, you should not need any special configuration to detect
+        With systemd-boot, you should not need any special configuration to detect
         other installed systems. With GRUB, set [](#opt-boot.loader.grub.useOSProber)
-        to `true`, but this will only detect windows partitions, not other linux
-        distributions. If you dual boot another linux distribution, use system-boot
+        to `true`, but this will only detect windows partitions, not other Linux
+        distributions. If you dual boot another Linux distribution, use systemd-boot
         instead.
 
     If you need to configure networking for your machine the
@@ -404,7 +529,8 @@ Use the following commands:
     You may also want to install some software. This will be covered in
     [](#sec-package-management).
 
-## Installation summary {#sec-installation-summary}
+### Installation summary {#sec-installation-manual-summary}
+[]{#sec-installation-summary} <!-- legacy anchor -->
 
 To summarise, [Example: Commands for Installing NixOS on `/dev/sda`](#ex-install-sequence)
 shows a typical sequence of commands for installing NixOS on an empty hard
@@ -412,33 +538,28 @@ drive (here `/dev/sda`). [Example: NixOS Configuration](#ex-config) shows a
 corresponding configuration Nix expression.
 
 ::: {#ex-partition-scheme-MBR .example}
-::: {.title}
-**Example: Example partition schemes for NixOS on `/dev/sda` (MBR)**
-:::
+### Example partition schemes for NixOS on `/dev/sda` (MBR)
 ```ShellSession
 # parted /dev/sda -- mklabel msdos
-# parted /dev/sda -- mkpart primary 1MiB -8GiB
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+# parted /dev/sda -- mkpart primary 1MB -8GB
+# parted /dev/sda -- mkpart primary linux-swap -8GB 100%
 ```
 :::
 
 ::: {#ex-partition-scheme-UEFI .example}
-::: {.title}
-**Example: Example partition schemes for NixOS on `/dev/sda` (UEFI)**
-:::
+### Example partition schemes for NixOS on `/dev/sda` (UEFI)
 ```ShellSession
 # parted /dev/sda -- mklabel gpt
-# parted /dev/sda -- mkpart primary 512MiB -8GiB
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
-# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+# parted /dev/sda -- mkpart primary 512MB -8GB
+# parted /dev/sda -- mkpart primary linux-swap -8GB 100%
+# parted /dev/sda -- mkpart ESP fat32 1MB 512MB
 # parted /dev/sda -- set 3 esp on
 ```
 :::
 
 ::: {#ex-install-sequence .example}
-::: {.title}
-**Example: Commands for Installing NixOS on `/dev/sda`**
-:::
+### Commands for Installing NixOS on `/dev/sda`
+
 With a partitioned disk.
 
 ```ShellSession
@@ -457,9 +578,7 @@ With a partitioned disk.
 :::
 
 ::: {#ex-config .example}
-::: {.title}
-**Example: NixOS Configuration**
-:::
+### Example: NixOS Configuration
 ```ShellSession
 { config, pkgs, ... }: {
   imports = [
@@ -483,11 +602,11 @@ With a partitioned disk.
 
 ## Additional installation notes {#sec-installation-additional-notes}
 
-```{=docbook}
-<xi:include href="installing-usb.section.xml" />
-<xi:include href="installing-pxe.section.xml" />
-<xi:include href="installing-kexec.section.xml" />
-<xi:include href="installing-virtualbox-guest.section.xml" />
-<xi:include href="installing-from-other-distro.section.xml" />
-<xi:include href="installing-behind-a-proxy.section.xml" />
+```{=include=} sections
+installing-usb.section.md
+installing-pxe.section.md
+installing-kexec.section.md
+installing-virtualbox-guest.section.md
+installing-from-other-distro.section.md
+installing-behind-a-proxy.section.md
 ```
diff --git a/nixpkgs/nixos/doc/manual/installation/obtaining.chapter.md b/nixpkgs/nixos/doc/manual/installation/obtaining.chapter.md
index 832ec6146a9d..a72194ecf985 100644
--- a/nixpkgs/nixos/doc/manual/installation/obtaining.chapter.md
+++ b/nixpkgs/nixos/doc/manual/installation/obtaining.chapter.md
@@ -1,24 +1,21 @@
 # Obtaining NixOS {#sec-obtaining}
 
 NixOS ISO images can be downloaded from the [NixOS download
-page](https://nixos.org/nixos/download.html). There are a number of
-installation options. If you happen to have an optical drive and a spare
-CD, burning the image to CD and booting from that is probably the
-easiest option. Most people will need to prepare a USB stick to boot
-from. [](#sec-booting-from-usb) describes the preferred method to
-prepare a USB stick. A number of alternative methods are presented in
-the [NixOS Wiki](https://nixos.wiki/wiki/NixOS_Installation_Guide#Making_the_installation_media).
+page](https://nixos.org/download.html#nixos-iso). Follow the instructions in
+[](#sec-booting-from-usb) to create a bootable USB flash drive.
+
+If you have a very old system that can't boot from USB, you can burn the image
+to an empty CD. NixOS might not work very well on such systems.
 
 As an alternative to installing NixOS yourself, you can get a running
 NixOS system through several other means:
 
 -   Using virtual appliances in Open Virtualization Format (OVF) that
     can be imported into VirtualBox. These are available from the [NixOS
-    download page](https://nixos.org/nixos/download.html).
+    download page](https://nixos.org/download.html#nixos-virtualbox).
 
--   Using AMIs for Amazon's EC2. To find one for your region and
-    instance type, please refer to the [list of most recent
-    AMIs](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/ec2-amis.nix).
+-   Using AMIs for Amazon's EC2. To find one for your region, please refer
+    to the [download page](https://nixos.org/download.html#nixos-amazon).
 
 -   Using NixOps, the NixOS-based cloud deployment tool, which allows
     you to provision VirtualBox and EC2 NixOS instances from declarative
diff --git a/nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md b/nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md
index 2644979bc9db..d39e1b786d83 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-22.05`](https://nixos.org/channels/nixos-22.05).
+-   *Stable channels*, such as [`nixos-23.05`](https://channels.nixos.org/nixos-23.05).
     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
@@ -14,13 +14,13 @@ passed and all packages have been built. These channels are:
     Stable channels are generally maintained until the next stable
     branch is created.
 
--   The *unstable channel*, [`nixos-unstable`](https://nixos.org/channels/nixos-unstable).
+-   The *unstable channel*, [`nixos-unstable`](https://channels.nixos.org/nixos-unstable).
     This corresponds to NixOS's main development branch, and may thus see
     radical changes between channel updates. It's not recommended for
     production systems.
 
--   *Small channels*, such as [`nixos-22.05-small`](https://nixos.org/channels/nixos-22.05-small)
-    or [`nixos-unstable-small`](https://nixos.org/channels/nixos-unstable-small).
+-   *Small channels*, such as [`nixos-23.05-small`](https://channels.nixos.org/nixos-23.05-small)
+    or [`nixos-unstable-small`](https://channels.nixos.org/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
     faster than the regular channels (for instance, when a critical security patch
@@ -28,7 +28,7 @@ passed and all packages have been built. These channels are:
     built from source than usual. They're mostly intended for server environments
     and as such contain few GUI applications.
 
-To see what channels are available, go to <https://nixos.org/channels>.
+To see what channels are available, go to <https://channels.nixos.org>.
 (Note that the URIs of the various channels redirect to a directory that
 contains the channel's latest version and includes ISO images and
 VirtualBox appliances.) Please note that during the release process,
@@ -38,38 +38,38 @@ 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 22.05 ISO, you will be subscribed to
-the `nixos-22.05` channel. To see which NixOS channel you're subscribed
+instance, if you installed from a 23.05 ISO, you will be subscribed to
+the `nixos-23.05` channel. To see which NixOS channel you're subscribed
 to, run the following as root:
 
 ```ShellSession
 # nix-channel --list | grep nixos
-nixos https://nixos.org/channels/nixos-unstable
+nixos https://channels.nixos.org/nixos-unstable
 ```
 
 To switch to a different NixOS channel, do
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/channel-name nixos
+# nix-channel --add https://channels.nixos.org/channel-name nixos
 ```
 
 (Be sure to include the `nixos` parameter at the end.) For instance, to
-use the NixOS 22.05 stable channel:
+use the NixOS 23.05 stable channel:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-22.05 nixos
+# nix-channel --add https://channels.nixos.org/nixos-23.05 nixos
 ```
 
 If you have a server, you may want to use the "small" channel instead:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-22.05-small nixos
+# nix-channel --add https://channels.nixos.org/nixos-23.05-small nixos
 ```
 
 And if you want to live on the bleeding edge:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-unstable nixos
+# nix-channel --add https://channels.nixos.org/nixos-unstable nixos
 ```
 
 You can then upgrade NixOS to the latest version in your chosen channel
@@ -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-22.05;
+system.autoUpgrade.channel = "https://channels.nixos.org/nixos-23.05";
 ```
diff --git a/nixpkgs/nixos/doc/manual/man-configuration.xml b/nixpkgs/nixos/doc/manual/man-configuration.xml
deleted file mode 100644
index ddb1408fdcf5..000000000000
--- a/nixpkgs/nixos/doc/manual/man-configuration.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><filename>configuration.nix</filename>
-  </refentrytitle><manvolnum>5</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><filename>configuration.nix</filename></refname>
-  <refpurpose>NixOS system configuration specification</refpurpose>
- </refnamediv>
- <refsection>
-  <title>Description</title>
-  <para>
-   The file <filename>/etc/nixos/configuration.nix</filename> contains the
-   declarative specification of your NixOS system configuration. The command
-   <command>nixos-rebuild</command> takes this file and realises the system
-   configuration specified therein.
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   You can use the following options in <filename>configuration.nix</filename>.
-  </para>
-  <xi:include href="./generated/options-db.xml"
-            xpointer="configuration-variable-list" />
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-build-vms.xml b/nixpkgs/nixos/doc/manual/man-nixos-build-vms.xml
deleted file mode 100644
index fa7c8c0c6d79..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-build-vms.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-build-vms</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><command>nixos-build-vms</command></refname>
-  <refpurpose>build a network of virtual machines from a network of NixOS configurations</refpurpose>
- </refnamediv>
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-build-vms</command>
-   <arg>
-    <option>--show-trace</option>
-   </arg>
-
-   <arg>
-    <option>--no-out-link</option>
-   </arg>
-
-   <arg>
-    <option>--help</option>
-  </arg>
-
-  <arg>
-    <option>--option</option>
-    <replaceable>name</replaceable>
-    <replaceable>value</replaceable>
-  </arg>
-
-   <arg choice="plain">
-    <replaceable>network.nix</replaceable>
-   </arg>
-  </cmdsynopsis>
- </refsynopsisdiv>
- <refsection>
-  <title>Description</title>
-  <para>
-   This command builds a network of QEMU-KVM virtual machines of a Nix
-   expression specifying a network of NixOS machines. The virtual network can
-   be started by executing the <filename>bin/run-vms</filename> shell script
-   that is generated by this command. By default, a <filename>result</filename>
-   symlink is produced that points to the generated virtual network.
-  </para>
-  <para>
-   A network Nix expression has the following structure:
-<screen>
-{
-  test1 = {pkgs, config, ...}:
-    {
-      services.openssh.enable = true;
-      nixpkgs.localSystem.system = "i686-linux";
-      deployment.targetHost = "test1.example.net";
-
-      # Other NixOS options
-    };
-
-  test2 = {pkgs, config, ...}:
-    {
-      services.openssh.enable = true;
-      services.httpd.enable = true;
-      environment.systemPackages = [ pkgs.lynx ];
-      nixpkgs.localSystem.system = "x86_64-linux";
-      deployment.targetHost = "test2.example.net";
-
-      # Other NixOS options
-    };
-}
-</screen>
-   Each attribute in the expression represents a machine in the network (e.g.
-   <varname>test1</varname> and <varname>test2</varname>) referring to a
-   function defining a NixOS configuration. In each NixOS configuration, two
-   attributes have a special meaning. The
-   <varname>deployment.targetHost</varname> specifies the address (domain name
-   or IP address) of the system which is used by <command>ssh</command> to
-   perform remote deployment operations. The
-   <varname>nixpkgs.localSystem.system</varname> attribute can be used to
-   specify an architecture for the target machine, such as
-   <varname>i686-linux</varname> which builds a 32-bit NixOS configuration.
-   Omitting this property will build the configuration for the same
-   architecture as the host system.
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   This command accepts the following options:
-  </para>
-  <variablelist>
-   <varlistentry>
-    <term>
-     <option>--show-trace</option>
-    </term>
-    <listitem>
-     <para>
-      Shows a trace of the output.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--no-out-link</option>
-    </term>
-    <listitem>
-     <para>
-      Do not create a 'result' symlink.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>-h</option>, <option>--help</option>
-    </term>
-    <listitem>
-     <para>
-      Shows the usage of this command to the user.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--option</option> <replaceable>name</replaceable> <replaceable>value</replaceable>
-    </term>
-    <listitem>
-     <para>Set the Nix configuration option
-      <replaceable>name</replaceable> to <replaceable>value</replaceable>.
-      This overrides settings in the Nix configuration file (see
-      <citerefentry><refentrytitle>nix.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-enter.xml b/nixpkgs/nixos/doc/manual/man-nixos-enter.xml
deleted file mode 100644
index 41f0e6b97515..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-enter.xml
+++ /dev/null
@@ -1,154 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-enter</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><command>nixos-enter</command></refname>
-  <refpurpose>run a command in a NixOS chroot environment</refpurpose>
- </refnamediv>
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-enter</command>
-   <arg>
-    <arg choice='plain'>
-     <option>--root</option>
-    </arg>
-     <replaceable>root</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--system</option>
-    </arg>
-     <replaceable>system</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>-c</option>
-    </arg>
-     <replaceable>shell-command</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--silent</option>
-    </arg>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--help</option>
-    </arg>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--</option>
-    </arg>
-     <replaceable>arguments</replaceable>
-   </arg>
-  </cmdsynopsis>
- </refsynopsisdiv>
- <refsection>
-  <title>Description</title>
-  <para>
-   This command runs a command in a NixOS chroot environment, that is, in a
-   filesystem hierarchy previously prepared using
-   <command>nixos-install</command>.
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   This command accepts the following options:
-  </para>
-  <variablelist>
-   <varlistentry>
-    <term>
-     <option>--root</option>
-    </term>
-    <listitem>
-     <para>
-      The path to the NixOS system you want to enter. It defaults to
-      <filename>/mnt</filename>.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--system</option>
-    </term>
-    <listitem>
-     <para>
-      The NixOS system configuration to use. It defaults to
-      <filename>/nix/var/nix/profiles/system</filename>. You can enter a
-      previous NixOS configuration by specifying a path such as
-      <filename>/nix/var/nix/profiles/system-106-link</filename>.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--command</option>
-    </term>
-    <term>
-     <option>-c</option>
-    </term>
-    <listitem>
-     <para>
-      The bash command to execute.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--silent</option>
-    </term>
-    <listitem>
-     <para>
-       Suppresses all output from the activation script of the target system.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--</option>
-    </term>
-    <listitem>
-     <para>
-      Interpret the remaining arguments as the program name and arguments to be
-      invoked. The program is not executed in a shell.
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
- <refsection>
-  <title>Examples</title>
-  <para>
-   Start an interactive shell in the NixOS installation in
-   <filename>/mnt</filename>:
-  </para>
-<screen>
-<prompt># </prompt>nixos-enter --root /mnt
-</screen>
-  <para>
-   Run a shell command:
-  </para>
-<screen>
-<prompt># </prompt>nixos-enter -c 'ls -l /; cat /proc/mounts'
-</screen>
-  <para>
-   Run a non-shell command:
-  </para>
-<screen>
-# nixos-enter -- cat /proc/mounts
-</screen>
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-generate-config.xml b/nixpkgs/nixos/doc/manual/man-nixos-generate-config.xml
deleted file mode 100644
index 9ac3b918ff69..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-generate-config.xml
+++ /dev/null
@@ -1,214 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-generate-config</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><command>nixos-generate-config</command></refname>
-  <refpurpose>generate NixOS configuration modules</refpurpose>
- </refnamediv>
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-generate-config</command>
-   <arg>
-    <option>--force</option>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--root</option>
-    </arg>
-     <replaceable>root</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--dir</option>
-    </arg>
-     <replaceable>dir</replaceable>
-   </arg>
-  </cmdsynopsis>
- </refsynopsisdiv>
- <refsection>
-  <title>Description</title>
-  <para>
-   This command writes two NixOS configuration modules:
-   <variablelist>
-    <varlistentry>
-     <term>
-      <option>/etc/nixos/hardware-configuration.nix</option>
-     </term>
-     <listitem>
-      <para>
-       This module sets NixOS configuration options based on your current
-       hardware configuration. In particular, it sets the
-       <option>fileSystem</option> option to reflect all currently mounted file
-       systems, the <option>swapDevices</option> option to reflect active swap
-       devices, and the <option>boot.initrd.*</option> options to ensure that
-       the initial ramdisk contains any kernel modules necessary for mounting
-       the root file system.
-      </para>
-      <para>
-       If this file already exists, it is overwritten. Thus, you should not
-       modify it manually. Rather, you should include it from your
-       <filename>/etc/nixos/configuration.nix</filename>, and re-run
-       <command>nixos-generate-config</command> to update it whenever your
-       hardware configuration changes.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry>
-     <term>
-      <option>/etc/nixos/configuration.nix</option>
-     </term>
-     <listitem>
-      <para>
-       This is the main NixOS system configuration module. If it already
-       exists, it’s left unchanged. Otherwise,
-       <command>nixos-generate-config</command> will write a template for you
-       to customise.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   This command accepts the following options:
-  </para>
-  <variablelist>
-   <varlistentry>
-    <term>
-     <option>--root</option>
-    </term>
-    <listitem>
-     <para>
-      If this option is given, treat the directory
-      <replaceable>root</replaceable> as the root of the file system. This
-      means that configuration files will be written to
-      <filename><replaceable>root</replaceable>/etc/nixos</filename>, and that
-      any file systems outside of <replaceable>root</replaceable> are ignored
-      for the purpose of generating the <option>fileSystems</option> option.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--dir</option>
-    </term>
-    <listitem>
-     <para>
-      If this option is given, write the configuration files to the directory
-      <replaceable>dir</replaceable> instead of
-      <filename>/etc/nixos</filename>.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--force</option>
-    </term>
-    <listitem>
-     <para>
-      Overwrite <filename>/etc/nixos/configuration.nix</filename> if it already
-      exists.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--no-filesystems</option>
-    </term>
-    <listitem>
-     <para>
-      Omit everything concerning file systems and swap devices from the
-      hardware configuration.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--show-hardware-config</option>
-    </term>
-    <listitem>
-     <para>
-      Don't generate <filename>configuration.nix</filename> or
-      <filename>hardware-configuration.nix</filename> and print the hardware
-      configuration to stdout only.
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
- <refsection>
-  <title>Examples</title>
-  <para>
-   This command is typically used during NixOS installation to write initial
-   configuration modules. For example, if you created and mounted the target
-   file systems on <filename>/mnt</filename> and
-   <filename>/mnt/boot</filename>, you would run:
-<screen>
-<prompt>$ </prompt>nixos-generate-config --root /mnt
-</screen>
-   The resulting file
-   <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> might look
-   like this:
-<programlisting>
-# Do not modify this file!  It was generated by ‘nixos-generate-config’
-# and may be overwritten by future invocations.  Please make changes
-# to /etc/nixos/configuration.nix instead.
-{ config, pkgs, ... }:
-
-{
-  imports =
-    [ &lt;nixos/modules/installer/scan/not-detected.nix&gt;
-    ];
-
-  boot.initrd.availableKernelModules = [ "ehci_hcd" "ahci" ];
-  boot.kernelModules = [ "kvm-intel" ];
-  boot.extraModulePackages = [ ];
-
-  fileSystems."/" =
-    { device = "/dev/disk/by-label/nixos";
-      fsType = "ext3";
-      options = [ "rw" "data=ordered" "relatime" ];
-    };
-
-  fileSystems."/boot" =
-    { device = "/dev/sda1";
-      fsType = "ext3";
-      options = [ "rw" "errors=continue" "user_xattr" "acl" "barrier=1" "data=writeback" "relatime" ];
-    };
-
-  swapDevices =
-    [ { device = "/dev/sda2"; }
-    ];
-
-  nix.maxJobs = 8;
-}
-</programlisting>
-   It will also create a basic
-   <filename>/mnt/etc/nixos/configuration.nix</filename>, which you should edit
-   to customise the logical configuration of your system. This file includes
-   the result of the hardware scan as follows:
-<programlisting>
-  imports = [ ./hardware-configuration.nix ];
-</programlisting>
-  </para>
-  <para>
-   After installation, if your hardware configuration changes, you can run:
-<screen>
-<prompt>$ </prompt>nixos-generate-config
-</screen>
-   to update <filename>/etc/nixos/hardware-configuration.nix</filename>. Your
-   <filename>/etc/nixos/configuration.nix</filename> will
-   <emphasis>not</emphasis> be overwritten.
-  </para>
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-install.xml b/nixpkgs/nixos/doc/manual/man-nixos-install.xml
deleted file mode 100644
index eb6680b65677..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-install.xml
+++ /dev/null
@@ -1,357 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-install</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><command>nixos-install</command></refname>
-  <refpurpose>install bootloader and NixOS</refpurpose>
- </refnamediv>
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-install</command>
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'>
-      <option>--verbose</option>
-     </arg>
-     <arg choice='plain'>
-      <option>-v</option>
-     </arg>
-    </group>
-   </arg>
-   <arg>
-    <arg choice='plain'>
-     <option>-I</option>
-    </arg>
-     <replaceable>path</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--root</option>
-    </arg>
-     <replaceable>root</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--system</option>
-    </arg>
-     <replaceable>path</replaceable>
-   </arg>
-
-   <arg>
-    <option>--flake</option> <replaceable>flake-uri</replaceable>
-   </arg>
-
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>--impure</option></arg>
-    </group>
-   </arg>
-
-   <arg>
-     <arg choice='plain'>
-       <option>--channel</option>
-     </arg>
-     <replaceable>channel</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--no-channel-copy</option>
-    </arg>
-   </arg>
-
-   <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>
-    <arg choice='plain'>
-     <option>--no-bootloader</option>
-    </arg>
-   </arg>
-
-   <arg>
-    <group choice='req'>
-    <arg choice='plain'>
-     <option>--max-jobs</option>
-    </arg>
-
-    <arg choice='plain'>
-     <option>-j</option>
-    </arg>
-     </group> <replaceable>number</replaceable>
-   </arg>
-
-   <arg>
-    <option>--cores</option> <replaceable>number</replaceable>
-   </arg>
-
-   <arg>
-    <option>--option</option> <replaceable>name</replaceable> <replaceable>value</replaceable>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--show-trace</option>
-    </arg>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--keep-going</option>
-    </arg>
-   </arg>
-
-   <arg>
-    <arg choice='plain'>
-     <option>--help</option>
-    </arg>
-   </arg>
-  </cmdsynopsis>
- </refsynopsisdiv>
- <refsection>
-  <title>Description</title>
-  <para>
-   This command installs NixOS in the file system mounted on
-   <filename>/mnt</filename>, based on the NixOS configuration specified in
-   <filename>/mnt/etc/nixos/configuration.nix</filename>. It performs the
-   following steps:
-   <itemizedlist>
-    <listitem>
-     <para>
-      It copies Nix and its dependencies to
-      <filename>/mnt/nix/store</filename>.
-     </para>
-    </listitem>
-    <listitem>
-     <para>
-      It runs Nix in <filename>/mnt</filename> to build the NixOS configuration
-      specified in <filename>/mnt/etc/nixos/configuration.nix</filename>.
-     </para>
-    </listitem>
-    <listitem>
-      <para>
-        It installs the current channel <quote>nixos</quote> in the target channel
-        profile (unless <option>--no-channel-copy</option> is specified).
-      </para>
-    </listitem>
-    <listitem>
-     <para>
-      It installs the GRUB boot loader on the device specified in the option
-      <option>boot.loader.grub.device</option> (unless
-      <option>--no-bootloader</option> is specified), and generates a GRUB
-      configuration file that boots into the NixOS configuration just
-      installed.
-     </para>
-    </listitem>
-    <listitem>
-     <para>
-      It prompts you for a password for the root account (unless
-      <option>--no-root-password</option> is specified).
-     </para>
-    </listitem>
-   </itemizedlist>
-  </para>
-  <para>
-   This command is idempotent: if it is interrupted or fails due to a temporary
-   problem (e.g. a network issue), you can safely re-run it.
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   This command accepts the following options:
-  </para>
-  <variablelist>
-   <varlistentry>
-    <term><option>--verbose</option> / <option>-v</option></term>
-    <listitem>
-     <para>Increases the level of verbosity of diagnostic messages
-     printed on standard error.  For each Nix operation, the information
-     printed on standard output is well-defined; any diagnostic
-     information is printed on standard error, never on standard
-     output.</para>
-     <para>Please note that this option may be specified repeatedly.</para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--root</option>
-    </term>
-    <listitem>
-     <para>
-      Defaults to <filename>/mnt</filename>. If this option is given, treat the
-      directory <replaceable>root</replaceable> as the root of the NixOS
-      installation.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--system</option>
-    </term>
-    <listitem>
-     <para>
-      If this option is provided, <command>nixos-install</command> will install
-      the specified closure rather than attempt to build one from
-      <filename>/mnt/etc/nixos/configuration.nix</filename>.
-     </para>
-     <para>
-      The closure must be an appropriately configured NixOS system, with boot
-      loader and partition configuration that fits the target host. Such a
-      closure is typically obtained with a command such as <command>nix-build
-      -I nixos-config=./configuration.nix '&lt;nixpkgs/nixos&gt;' -A system
-      --no-out-link</command>
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--flake</option> <replaceable>flake-uri</replaceable>#<replaceable>name</replaceable>
-    </term>
-    <listitem>
-     <para>
-      Build the NixOS system from the specified flake.
-      The flake must contain an output named
-      <literal>nixosConfigurations.<replaceable>name</replaceable></literal>.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-     <term>
-       <option>--channel</option>
-     </term>
-     <listitem>
-       <para>
-         If this option is provided, do not copy the current
-         <quote>nixos</quote> channel to the target host. Instead, use the
-         specified derivation.
-       </para>
-     </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>-I</option>
-    </term>
-    <listitem>
-     <para>
-      Add a path to the Nix expression search path. This option may be given
-      multiple times. See the NIX_PATH environment variable for information on
-      the semantics of the Nix search path. Paths added through
-      <replaceable>-I</replaceable> take precedence over NIX_PATH.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--max-jobs</option>
-    </term>
-    <term>
-     <option>-j</option>
-    </term>
-    <listitem>
-     <para>
-      Sets the maximum number of build jobs that Nix will perform in parallel
-      to the specified number. The default is <literal>1</literal>. A higher
-      value is useful on SMP systems or to exploit I/O latency.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--cores</option>
-    </term>
-    <listitem>
-     <para>
-      Sets the value of the <envar>NIX_BUILD_CORES</envar> environment variable
-      in the invocation of builders. Builders can use this variable at their
-      discretion to control the maximum amount of parallelism. For instance, in
-      Nixpkgs, if the derivation attribute
-      <varname>enableParallelBuilding</varname> is set to
-      <literal>true</literal>, the builder passes the
-      <option>-j<replaceable>N</replaceable></option> flag to GNU Make. The
-      value <literal>0</literal> means that the builder should use all
-      available CPU cores in the system.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--option</option> <replaceable>name</replaceable> <replaceable>value</replaceable>
-    </term>
-    <listitem>
-     <para>
-      Set the Nix configuration option <replaceable>name</replaceable> to
-      <replaceable>value</replaceable>.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--show-trace</option>
-    </term>
-    <listitem>
-     <para>
-      Causes Nix to print out a stack trace in case of Nix expression
-      evaluation errors.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--keep-going</option>
-    </term>
-    <listitem>
-     <para>
-      Causes Nix to continue building derivations as far as possible
-      in the face of failed builds.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>--help</option>
-    </term>
-    <listitem>
-     <para>
-      Synonym for <command>man nixos-install</command>.
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
- <refsection>
-  <title>Examples</title>
-  <para>
-   A typical NixOS installation is done by creating and mounting a file system
-   on <filename>/mnt</filename>, generating a NixOS configuration in
-   <filename>/mnt/etc/nixos/configuration.nix</filename>, and running
-   <command>nixos-install</command>. For instance, if we want to install NixOS
-   on an <literal>ext4</literal> file system created in
-   <filename>/dev/sda1</filename>:
-<screen>
-<prompt>$ </prompt>mkfs.ext4 /dev/sda1
-<prompt>$ </prompt>mount /dev/sda1 /mnt
-<prompt>$ </prompt>nixos-generate-config --root /mnt
-<prompt>$ </prompt># edit /mnt/etc/nixos/configuration.nix
-<prompt>$ </prompt>nixos-install
-<prompt>$ </prompt>reboot
-</screen>
-  </para>
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-option.xml b/nixpkgs/nixos/doc/manual/man-nixos-option.xml
deleted file mode 100644
index b921386d0df0..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-option.xml
+++ /dev/null
@@ -1,134 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-option</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
- <refnamediv>
-  <refname><command>nixos-option</command></refname>
-  <refpurpose>inspect a NixOS configuration</refpurpose>
- </refnamediv>
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-option</command>
-
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>-r</option></arg>
-     <arg choice='plain'><option>--recursive</option></arg>
-    </group>
-   </arg>
-
-   <arg>
-    <option>-I</option> <replaceable>path</replaceable>
-   </arg>
-
-   <arg>
-    <replaceable>option.name</replaceable>
-   </arg>
-  </cmdsynopsis>
- </refsynopsisdiv>
- <refsection>
-  <title>Description</title>
-  <para>
-   This command evaluates the configuration specified in
-   <filename>/etc/nixos/configuration.nix</filename> and returns the properties
-   of the option name given as argument.
-  </para>
-  <para>
-   When the option name is not an option, the command prints the list of
-   attributes contained in the attribute set.
-  </para>
- </refsection>
- <refsection>
-  <title>Options</title>
-  <para>
-   This command accepts the following options:
-  </para>
-  <variablelist>
-   <varlistentry>
-    <term><option>-r</option></term>
-    <term><option>--recursive</option></term>
-    <listitem>
-     <para>
-      Print all the values at or below the specified path recursively.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry>
-    <term>
-     <option>-I</option> <replaceable>path</replaceable>
-    </term>
-    <listitem>
-     <para>
-      This option is passed to the underlying
-      <command>nix-instantiate</command> invocation.
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
- <refsection>
-  <title>Environment</title>
-  <variablelist>
-   <varlistentry>
-    <term>
-     <envar>NIXOS_CONFIG</envar>
-    </term>
-    <listitem>
-     <para>
-      Path to the main NixOS configuration module. Defaults to
-      <filename>/etc/nixos/configuration.nix</filename>.
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
- <refsection>
-  <title>Examples</title>
-  <para>
-   Investigate option values:
-<screen><prompt>$ </prompt>nixos-option boot.loader
-This attribute set contains:
-generationsDir
-grub
-initScript
-
-<prompt>$ </prompt>nixos-option boot.loader.grub.enable
-Value:
-true
-
-Default:
-true
-
-Description:
-Whether to enable the GNU GRUB boot loader.
-
-Declared by:
-  "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix"
-
-Defined by:
-  "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix"
-</screen>
-  </para>
- </refsection>
- <refsection>
-  <title>Bugs</title>
-  <para>
-   The author listed in the following section is wrong. If there is any other
-   bug, please report to Nicolas Pierron.
-  </para>
- </refsection>
- <refsection>
-  <title>See also</title>
-  <para>
-   <citerefentry>
-    <refentrytitle>configuration.nix</refentrytitle>
-    <manvolnum>5</manvolnum>
-   </citerefentry>
-  </para>
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml b/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml
deleted file mode 100644
index ea96f49fa977..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml
+++ /dev/null
@@ -1,716 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-rebuild</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
-<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
- </refmeta>
-
- <refnamediv>
-  <refname><command>nixos-rebuild</command></refname>
-  <refpurpose>reconfigure a NixOS machine</refpurpose>
- </refnamediv>
-
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-rebuild</command><group choice='req'>
-   <arg choice='plain'>
-    <option>switch</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>boot</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>test</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>build</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>dry-build</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>dry-activate</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>edit</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>build-vm</option>
-   </arg>
-
-   <arg choice='plain'>
-    <option>build-vm-with-bootloader</option>
-   </arg>
-    </group>
-    <sbr />
-
-    <arg>
-      <group choice='req'>
-        <arg choice='plain'>
-          <option>--upgrade</option>
-        </arg>
-        <arg choice='plain'>
-          <option>--upgrade-all</option>
-        </arg>
-      </group>
-    </arg>
-
-   <arg>
-    <option>--install-bootloader</option>
-   </arg>
-
-   <arg>
-    <option>--no-build-nix</option>
-   </arg>
-
-   <arg>
-    <option>--fast</option>
-   </arg>
-
-   <arg>
-    <option>--rollback</option>
-   </arg>
-
-   <arg>
-    <option>--builders</option> <replaceable>builder-spec</replaceable>
-   </arg>
-
-   <sbr/>
-
-   <arg>
-    <option>--flake</option> <replaceable>flake-uri</replaceable>
-   </arg>
-
-   <arg>
-    <option>--no-flake</option>
-   </arg>
-
-   <arg>
-    <option>--override-input</option> <replaceable>input-name</replaceable> <replaceable>flake-uri</replaceable>
-   </arg>
-
-   <sbr />
-
-   <arg>
-    <group choice='req'>
-    <arg choice='plain'>
-     <option>--profile-name</option>
-    </arg>
-
-    <arg choice='plain'>
-     <option>-p</option>
-    </arg>
-     </group> <replaceable>name</replaceable>
-   </arg>
-
-   <sbr />
-
-   <arg>
-    <option>--build-host</option> <replaceable>host</replaceable>
-   </arg>
-
-   <arg>
-    <option>--target-host</option> <replaceable>host</replaceable>
-   </arg>
-
-   <arg>
-    <option>--use-remote-sudo</option>
-   </arg>
-
-   <sbr />
-
-   <arg>
-    <option>--show-trace</option>
-   </arg>
-   <arg>
-    <option>-I</option>
-    <replaceable>path</replaceable>
-   </arg>
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>--verbose</option></arg>
-     <arg choice='plain'><option>-v</option></arg>
-    </group>
-   </arg>
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>--impure</option></arg>
-    </group>
-   </arg>
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>--max-jobs</option></arg>
-     <arg choice='plain'><option>-j</option></arg>
-    </group>
-    <replaceable>number</replaceable>
-   </arg>
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>--keep-failed</option></arg>
-     <arg choice='plain'><option>-K</option></arg>
-    </group>
-   </arg>
-   <arg>
-    <group choice='req'>
-     <arg choice='plain'><option>--keep-going</option></arg>
-     <arg choice='plain'><option>-k</option></arg>
-    </group>
-   </arg>
-  </cmdsynopsis>
- </refsynopsisdiv>
-
- <refsection>
-  <title>Description</title>
-
-  <para>
-   This command updates the system so that it corresponds to the
-   configuration specified in
-   <filename>/etc/nixos/configuration.nix</filename> or
-   <filename>/etc/nixos/flake.nix</filename>. Thus, every time you
-   modify the configuration or any other NixOS module, you must run
-   <command>nixos-rebuild</command> to make the changes take
-   effect. It builds the new system in
-   <filename>/nix/store</filename>, runs its activation script, and
-   stop and (re)starts any system services if needed. Please note that
-   user services need to be started manually as they aren't detected
-   by the activation script at the moment.
-  </para>
-
-  <para>
-   This command has one required argument, which specifies the desired
-   operation. It must be one of the following:
-
-   <variablelist>
-    <varlistentry>
-     <term>
-      <option>switch</option>
-     </term>
-     <listitem>
-      <para>
-       Build and activate the new configuration, and make it the boot default.
-       That is, the configuration is added to the GRUB boot menu as the default
-       menu entry, so that subsequent reboots will boot the system into the new
-       configuration. Previous configurations activated with
-       <command>nixos-rebuild switch</command> or <command>nixos-rebuild
-       boot</command> remain available in the GRUB menu.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>boot</option>
-     </term>
-     <listitem>
-      <para>
-       Build the new configuration and make it the boot default (as with
-       <command>nixos-rebuild switch</command>), but do not activate it. That
-       is, the system continues to run the previous configuration until the
-       next reboot.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>test</option>
-     </term>
-     <listitem>
-      <para>
-       Build and activate the new configuration, but do not add it to the GRUB
-       boot menu. Thus, if you reboot the system (or if it crashes), you will
-       automatically revert to the default configuration (i.e. the
-       configuration resulting from the last call to <command>nixos-rebuild
-       switch</command> or <command>nixos-rebuild boot</command>).
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>build</option>
-     </term>
-     <listitem>
-      <para>
-       Build the new configuration, but neither activate it nor add it to the
-       GRUB boot menu. It leaves a symlink named <filename>result</filename> in
-       the current directory, which points to the output of the top-level
-       “system” derivation. This is essentially the same as doing
-<screen>
-<prompt>$ </prompt>nix-build /path/to/nixpkgs/nixos -A system
-</screen>
-       Note that you do not need to be <literal>root</literal> to run
-       <command>nixos-rebuild build</command>.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>dry-build</option>
-     </term>
-     <listitem>
-      <para>
-       Show what store paths would be built or downloaded by any of the
-       operations above, but otherwise do nothing.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>dry-activate</option>
-     </term>
-     <listitem>
-      <para>
-       Build the new configuration, but instead of activating it, show what
-       changes would be performed by the activation (i.e. by
-       <command>nixos-rebuild test</command>). For instance, this command will
-       print which systemd units would be restarted. The list of changes is not
-       guaranteed to be complete.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>edit</option>
-     </term>
-     <listitem>
-      <para>
-       Opens <filename>configuration.nix</filename> in the default editor.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>build-vm</option>
-     </term>
-     <listitem>
-      <para>
-       Build a script that starts a NixOS virtual machine with the desired
-       configuration. It leaves a symlink <filename>result</filename> in the
-       current directory that points (under
-       <filename>result/bin/run-<replaceable>hostname</replaceable>-vm</filename>)
-       at the script that starts the VM. Thus, to test a NixOS configuration in
-       a virtual machine, you should do the following:
-<screen>
-<prompt>$ </prompt>nixos-rebuild build-vm
-<prompt>$ </prompt>./result/bin/run-*-vm
-</screen>
-      </para>
-
-      <para>
-       The VM is implemented using the <literal>qemu</literal> package. For
-       best performance, you should load the <literal>kvm-intel</literal> or
-       <literal>kvm-amd</literal> kernel modules to get hardware
-       virtualisation.
-      </para>
-
-      <para>
-       The VM mounts the Nix store of the host through the 9P file system. The
-       host Nix store is read-only, so Nix commands that modify the Nix store
-       will not work in the VM. This includes commands such as
-       <command>nixos-rebuild</command>; to change the VM’s configuration,
-       you must halt the VM and re-run the commands above.
-      </para>
-
-      <para>
-       The VM has its own <literal>ext3</literal> root file system, which is
-       automatically created when the VM is first started, and is persistent
-       across reboots of the VM. It is stored in
-       <literal>./<replaceable>hostname</replaceable>.qcow2</literal>.
-<!-- The entire file system hierarchy of the host is available in
-      the VM under <filename>/hostfs</filename>.-->
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term>
-      <option>build-vm-with-bootloader</option>
-     </term>
-     <listitem>
-      <para>
-       Like <option>build-vm</option>, but boots using the regular boot loader
-       of your configuration (e.g., GRUB 1 or 2), rather than booting directly
-       into the kernel and initial ramdisk of the system. This allows you to
-       test whether the boot loader works correctly. However, it does not
-       guarantee that your NixOS configuration will boot successfully on the
-       host hardware (i.e., after running <command>nixos-rebuild
-       switch</command>), because the hardware and boot loader configuration in
-       the VM are different. The boot loader is installed on an automatically
-       generated virtual disk containing a <filename>/boot</filename>
-       partition.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </para>
- </refsection>
-
- <refsection>
-  <title>Options</title>
-  <para>
-   This command accepts the following options:
-  </para>
-
-  <variablelist>
-   <varlistentry>
-    <term>
-     <option>--upgrade</option>
-    </term>
-    <term>
-     <option>--upgrade-all</option>
-    </term>
-    <listitem>
-      <para>
-        Update the root user's channel named <literal>nixos</literal>
-        before rebuilding the system.
-      </para>
-      <para>
-        In addition to the <literal>nixos</literal> channel, the root
-        user's channels which have a file named
-        <literal>.update-on-nixos-rebuild</literal> in their base
-        directory will also be updated.
-      </para>
-      <para>
-        Passing <option>--upgrade-all</option> updates all of the root
-        user's channels.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--install-bootloader</option>
-    </term>
-    <listitem>
-     <para>
-      Causes the boot loader to be (re)installed on the device specified by the
-      relevant configuration options.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--no-build-nix</option>
-    </term>
-    <listitem>
-     <para>
-      Normally, <command>nixos-rebuild</command> first builds the
-      <varname>nixUnstable</varname> attribute in Nixpkgs, and uses the
-      resulting instance of the Nix package manager to build the new system
-      configuration. This is necessary if the NixOS modules use features not
-      provided by the currently installed version of Nix. This option disables
-      building a new Nix.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--fast</option>
-    </term>
-    <listitem>
-     <para>
-      Equivalent to <option>--no-build-nix</option>. This option is
-      useful if you call <command>nixos-rebuild</command> frequently
-      (e.g. if you’re hacking on a NixOS module).
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--rollback</option>
-    </term>
-    <listitem>
-     <para>
-      Instead of building a new configuration as specified by
-      <filename>/etc/nixos/configuration.nix</filename>, roll back to the
-      previous configuration. (The previous configuration is defined as the one
-      before the “current” generation of the Nix profile
-      <filename>/nix/var/nix/profiles/system</filename>.)
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--builders</option> <replaceable>builder-spec</replaceable>
-    </term>
-    <listitem>
-     <para>
-      Allow ad-hoc remote builders for building the new system. This requires
-      the user executing <command>nixos-rebuild</command> (usually root) to be
-      configured as a trusted user in the Nix daemon. This can be achieved by
-      using the <literal>nix.settings.trusted-users</literal> NixOS option. Examples
-      values for that option are described in the <literal>Remote builds
-      chapter</literal> in the Nix manual, (i.e. <command>--builders
-      "ssh://bigbrother x86_64-linux"</command>). By specifying an empty string
-      existing builders specified in <filename>/etc/nix/machines</filename> can
-      be ignored: <command>--builders ""</command> for example when they are
-      not reachable due to network connectivity.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--profile-name</option>
-    </term>
-    <term>
-     <option>-p</option>
-    </term>
-    <listitem>
-     <para>
-      Instead of using the Nix profile
-      <filename>/nix/var/nix/profiles/system</filename> to keep track of the
-      current and previous system configurations, use
-      <filename>/nix/var/nix/profiles/system-profiles/<replaceable>name</replaceable></filename>.
-      When you use GRUB 2, for every system profile created with this flag,
-      NixOS will create a submenu named “NixOS - Profile
-      '<replaceable>name</replaceable>'” in GRUB’s boot menu, containing
-      the current and previous configurations of this profile.
-     </para>
-     <para>
-      For instance, if you want to test a configuration file named
-      <filename>test.nix</filename> without affecting the default system
-      profile, you would do:
-<screen>
-<prompt>$ </prompt>nixos-rebuild switch -p test -I nixos-config=./test.nix
-</screen>
-      The new configuration will appear in the GRUB 2 submenu “NixOS -
-      Profile 'test'”.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--build-host</option>
-    </term>
-    <listitem>
-     <para>
-      Instead of building the new configuration locally, use the specified host
-      to perform the build. The host needs to be accessible with ssh, and must
-      be able to perform Nix builds. If the option
-      <option>--target-host</option> is not set, the build will be copied back
-      to the local machine when done.
-     </para>
-     <para>
-      Note that, if <option>--no-build-nix</option> is not specified, Nix will
-      be built both locally and remotely. This is because the configuration
-      will always be evaluated locally even though the building might be
-      performed remotely.
-     </para>
-     <para>
-      You can include a remote user name in the host name
-      (<replaceable>user@host</replaceable>). You can also set ssh options by
-      defining the <envar>NIX_SSHOPTS</envar> environment variable.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--target-host</option>
-    </term>
-    <listitem>
-     <para>
-      Specifies the NixOS target host. By setting this to something other than
-      <replaceable>localhost</replaceable>, the system activation will happen
-      on the remote host instead of the local machine. The remote host needs to
-      be accessible over ssh, and for the commands <option>switch</option>,
-      <option>boot</option> and <option>test</option> you need root access.
-     </para>
-
-     <para>
-      If <option>--build-host</option> is not explicitly specified, building
-      will take place locally.
-     </para>
-
-     <para>
-      You can include a remote user name in the host name
-      (<replaceable>user@host</replaceable>). You can also set ssh options by
-      defining the <envar>NIX_SSHOPTS</envar> environment variable.
-     </para>
-
-     <para>
-      Note that <command>nixos-rebuild</command> honors the
-      <literal>nixpkgs.crossSystem</literal> setting of the given configuration
-      but disregards the true architecture of the target host. Hence the
-      <literal>nixpkgs.crossSystem</literal> setting has to match the target
-      platform or else activation will fail.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--use-substitutes</option>
-    </term>
-    <listitem>
-     <para>
-       When set, nixos-rebuild will add <option>--use-substitutes</option>
-       to each invocation of nix-copy-closure. This will only affect the
-       behavior of nixos-rebuild if <option>--target-host</option> or
-       <option>--build-host</option> is also set. This is useful when
-       the target-host connection to cache.nixos.org is faster than the
-       connection between hosts.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--use-remote-sudo</option>
-    </term>
-    <listitem>
-     <para>
-      When set, nixos-rebuild prefixes remote commands that run on
-      the <option>--build-host</option> and <option>--target-host</option>
-      systems with <command>sudo</command>. Setting this option allows
-      deploying as a non-root user.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--flake</option> <replaceable>flake-uri</replaceable><optional>#<replaceable>name</replaceable></optional>
-    </term>
-    <listitem>
-     <para>
-      Build the NixOS system from the specified flake. It defaults to
-      the directory containing the target of the symlink
-      <filename>/etc/nixos/flake.nix</filename>, if it exists. The
-      flake must contain an output named
-      <literal>nixosConfigurations.<replaceable>name</replaceable></literal>. If
-      <replaceable>name</replaceable> is omitted, it default to the
-      current host name.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--no-flake</option>
-    </term>
-    <listitem>
-     <para>
-      Do not imply <option>--flake</option> if
-      <filename>/etc/nixos/flake.nix</filename> exists. With this
-      option, it is possible to build non-flake NixOS configurations
-      even if the current NixOS systems uses flakes.
-     </para>
-    </listitem>
-   </varlistentry>
-
-  </variablelist>
-
-  <para>
-   In addition, <command>nixos-rebuild</command> accepts various Nix-related
-   flags, including <option>--max-jobs</option> / <option>-j</option>,
-   <option>--show-trace</option>, <option>--keep-failed</option>,
-   <option>--keep-going</option>, <option>--impure</option>, and <option>--verbose</option> /
-   <option>-v</option>. See the Nix manual for details.
-  </para>
- </refsection>
-
- <refsection>
-  <title>Environment</title>
-
-  <variablelist>
-   <varlistentry>
-    <term>
-     <envar>NIXOS_CONFIG</envar>
-    </term>
-    <listitem>
-     <para>
-      Path to the main NixOS configuration module. Defaults to
-      <filename>/etc/nixos/configuration.nix</filename>.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <envar>NIX_SSHOPTS</envar>
-    </term>
-    <listitem>
-     <para>
-      Additional options to be passed to <command>ssh</command> on the command
-      line.
-     </para>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </refsection>
-
- <refsection>
-  <title>Files</title>
-
-  <variablelist>
-
-   <varlistentry>
-    <term>
-     <filename>/etc/nixos/flake.nix</filename>
-    </term>
-    <listitem>
-     <para>
-      If this file exists, then <command>nixos-rebuild</command> will
-      use it as if the <option>--flake</option> option was given. This
-      file may be a symlink to a <filename>flake.nix</filename> in an
-      actual flake; thus <filename>/etc/nixos</filename> need not be a
-      flake.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <filename>/run/current-system</filename>
-    </term>
-    <listitem>
-     <para>
-      A symlink to the currently active system configuration in the Nix store.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <filename>/nix/var/nix/profiles/system</filename>
-    </term>
-    <listitem>
-     <para>
-      The Nix profile that contains the current and previous system
-      configurations. Used to generate the GRUB boot menu.
-     </para>
-    </listitem>
-   </varlistentry>
-
-  </variablelist>
- </refsection>
-
- <refsection>
-  <title>Bugs</title>
-  <para>
-   This command should be renamed to something more descriptive.
-  </para>
- </refsection>
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-version.xml b/nixpkgs/nixos/doc/manual/man-nixos-version.xml
deleted file mode 100644
index fae25721e394..000000000000
--- a/nixpkgs/nixos/doc/manual/man-nixos-version.xml
+++ /dev/null
@@ -1,137 +0,0 @@
-<refentry xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude">
- <refmeta>
-  <refentrytitle><command>nixos-version</command>
-  </refentrytitle><manvolnum>8</manvolnum>
-  <refmiscinfo class="source">NixOS</refmiscinfo>
- </refmeta>
- <refnamediv>
-  <refname><command>nixos-version</command></refname>
-  <refpurpose>show the NixOS version</refpurpose>
- </refnamediv>
- <refsynopsisdiv>
-  <cmdsynopsis>
-   <command>nixos-version</command>
-   <arg>
-    <option>--hash</option>
-   </arg>
-
-   <arg>
-    <option>--revision</option>
-   </arg>
-
-   <arg>
-    <option>--json</option>
-   </arg>
-
-  </cmdsynopsis>
- </refsynopsisdiv>
-
- <refsection>
-  <title>Description</title>
-  <para>
-   This command shows the version of the currently active NixOS configuration.
-   For example:
-<screen><prompt>$ </prompt>nixos-version
-16.03.1011.6317da4 (Emu)
-</screen>
-   The version consists of the following elements:
-   <variablelist>
-    <varlistentry>
-     <term>
-      <literal>16.03</literal>
-     </term>
-     <listitem>
-      <para>
-       The NixOS release, indicating the year and month in which it was
-       released (e.g. March 2016).
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry>
-     <term>
-      <literal>1011</literal>
-     </term>
-     <listitem>
-      <para>
-       The number of commits in the Nixpkgs Git repository between the start of
-       the release branch and the commit from which this version was built.
-       This ensures that NixOS versions are monotonically increasing. It is
-       <literal>git</literal> when the current NixOS configuration was built
-       from a checkout of the Nixpkgs Git repository rather than from a NixOS
-       channel.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry>
-     <term>
-      <literal>6317da4</literal>
-     </term>
-     <listitem>
-      <para>
-       The first 7 characters of the commit in the Nixpkgs Git repository from
-       which this version was built.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry>
-     <term>
-      <literal>Emu</literal>
-     </term>
-     <listitem>
-      <para>
-       The code name of the NixOS release. The first letter of the code name
-       indicates that this is the N'th stable NixOS release; for example, Emu
-       is the fifth release.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </para>
- </refsection>
-
- <refsection>
-  <title>Options</title>
-
-  <para>
-   This command accepts the following options:
-  </para>
-
-  <variablelist>
-
-   <varlistentry>
-    <term>
-     <option>--hash</option>
-    </term>
-    <term>
-     <option>--revision</option>
-    </term>
-    <listitem>
-     <para>
-      Show the full SHA1 hash of the Git commit from which this configuration
-      was built, e.g.
-<screen><prompt>$ </prompt>nixos-version --hash
-6317da40006f6bc2480c6781999c52d88dde2acf
-</screen>
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term>
-     <option>--json</option>
-    </term>
-    <listitem>
-     <para>
-      Print a JSON representation of the versions of NixOS and the
-      top-level configuration flake.
-     </para>
-    </listitem>
-   </varlistentry>
-
-  </variablelist>
-
- </refsection>
-
-</refentry>
diff --git a/nixpkgs/nixos/doc/manual/man-pages.xml b/nixpkgs/nixos/doc/manual/man-pages.xml
index 58f73521e94f..52183f1f9ee0 100644
--- a/nixpkgs/nixos/doc/manual/man-pages.xml
+++ b/nixpkgs/nixos/doc/manual/man-pages.xml
@@ -14,12 +14,33 @@
   <copyright><year>2007-2022</year><holder>Eelco Dolstra and the Nixpkgs/NixOS contributors</holder>
   </copyright>
  </info>
- <xi:include href="man-configuration.xml" />
- <xi:include href="man-nixos-build-vms.xml" />
- <xi:include href="man-nixos-generate-config.xml" />
- <xi:include href="man-nixos-install.xml" />
- <xi:include href="man-nixos-enter.xml" />
- <xi:include href="man-nixos-option.xml" />
- <xi:include href="man-nixos-rebuild.xml" />
- <xi:include href="man-nixos-version.xml" />
+ <refentry>
+  <refmeta>
+   <refentrytitle><filename>configuration.nix</filename>
+   </refentrytitle><manvolnum>5</manvolnum>
+   <refmiscinfo class="source">NixOS</refmiscinfo>
+ <!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
+  </refmeta>
+  <refnamediv>
+   <refname><filename>configuration.nix</filename></refname>
+   <refpurpose>NixOS system configuration specification</refpurpose>
+  </refnamediv>
+  <refsection>
+   <title>Description</title>
+   <para>
+    The file <filename>/etc/nixos/configuration.nix</filename> contains the
+    declarative specification of your NixOS system configuration. The command
+    <command>nixos-rebuild</command> takes this file and realises the system
+    configuration specified therein.
+   </para>
+  </refsection>
+  <refsection>
+   <title>Options</title>
+   <para>
+    You can use the following options in <filename>configuration.nix</filename>.
+   </para>
+   <xi:include href="./generated/options-db.xml"
+             xpointer="configuration-variable-list" />
+  </refsection>
+ </refentry>
 </reference>
diff --git a/nixpkgs/nixos/doc/manual/manpages/README.md b/nixpkgs/nixos/doc/manual/manpages/README.md
new file mode 100644
index 000000000000..05cb83902c74
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/README.md
@@ -0,0 +1,57 @@
+# NixOS manpages
+
+This is the collection of NixOS manpages, excluding `configuration.nix(5)`.
+
+Man pages are written in [`mdoc(7)` format](https://mandoc.bsd.lv/man/mdoc.7.html) and should be portable between mandoc and groff for rendering (though minor differences may occur, mandoc and groff seem to have slightly different spacing rules.)
+
+For previewing edited files, you can just run `man -l path/to/file.8` and you will see it rendered.
+
+Being written in `mdoc` these manpages use semantic markup. This file provides a guideline on where to apply which of the semantic elements of `mdoc`.
+
+### Command lines and arguments
+
+In any manpage, commands, flags and arguments to the *current* executable should be marked according to their semantics. Commands, flags and arguments passed to *other* executables should not be marked like this and should instead be considered as code examples and marked with `Ql`.
+
+ - Use `Fl` to mark flag arguments, `Ar` for their arguments.
+ - Repeating arguments should be marked by adding ellipses (`...`).
+ - Use `Cm` to mark literal string arguments, e.g. the `boot` command argument passed to `nixos-rebuild`.
+ - Optional flags or arguments should be marked with `Op`. This includes optional repeating arguments.
+ - Required flags or arguments should not be marked.
+ - Mutually exclusive groups of arguments should be enclosed in curly brackets, preferably created with `Bro`/`Brc` blocks.
+
+When an argument is used in an example it should be marked up with `Ar` again to differentiate it from a constant. For example, a command with a `--host name` flag that calls ssh to retrieve the host's local time would signify this thusly:
+```
+This will run
+.Ic ssh Ar name Ic time
+to retrieve the remote time.
+```
+
+### Paths, NixOS options, environment variables
+
+Constant paths should be marked with `Pa`, NixOS options with `Va`, and environment variables with `Ev`.
+
+Generated paths, e.g. `result/bin/run-hostname-vm` (where `hostname` is a variable or arguments) should be marked as `Ql` inline literals with their variable components marked appropriately.
+
+ - Taking `hostname` from an argument become `.Ql result/bin/run- Ns Ar hostname Ns -vm`
+ - Taking `hostname` from a variable otherwise defined becomes `.Ql result/bin/run- Ns Va hostname Ns -vm`
+
+### Code examples and other commands
+
+In free text names and complete invocations of other commands (e.g. `ssh` or `tar -xvf src.tar`) should be marked with `Ic`, fragments of command lines should be marked with `Ql`.
+
+Larger code blocks or those that cannot be shown inline should use indented literal display block markup for their contents, i.e.
+```
+.Bd -literal -offset indent
+...
+.Ed
+```
+Contents of code blocks may be marked up further, e.g. if they refer to arguments that will be substituted into them:
+```
+.Bd -literal -offset indent
+{
+  options.hostname = "\c
+.Ar hostname Ns \c
+";
+}
+.Ed
+```
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-build-vms.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-build-vms.8
new file mode 100644
index 000000000000..6a8f2c42eddf
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-build-vms.8
@@ -0,0 +1,105 @@
+.Dd January 1, 1980
+.Dt nixos-build-vms 8
+.Os
+.Sh NAME
+.Nm nixos-build-vms
+.Nd build a network of virtual machines from a network of NixOS configurations
+.
+.
+.
+.Sh SYNOPSIS
+.Nm nixos-build-vms
+.Op Fl -show-trace
+.Op Fl -no-out-link
+.Op Fl -help
+.Op Fl -option Ar name value
+.Pa network.nix
+.
+.
+.
+.Sh DESCRIPTION
+.
+This command builds a network of QEMU\-KVM virtual machines of a Nix expression
+specifying a network of NixOS machines. The virtual network can be started by
+executing the
+.Pa bin/run-vms
+shell script that is generated by this command. By default, a
+.Pa result
+symlink is produced that points to the generated virtual network.
+.
+.Pp
+A network Nix expression has the following structure:
+.Bd -literal -offset indent
+{
+  test1 = {pkgs, config, ...}:
+    {
+      services.openssh.enable = true;
+      nixpkgs.localSystem.system = "i686-linux";
+      deployment.targetHost = "test1.example.net";
+
+      # Other NixOS options
+    };
+
+  test2 = {pkgs, config, ...}:
+    {
+      services.openssh.enable = true;
+      services.httpd.enable = true;
+      environment.systemPackages = [ pkgs.lynx ];
+      nixpkgs.localSystem.system = "x86_64-linux";
+      deployment.targetHost = "test2.example.net";
+
+      # Other NixOS options
+    };
+}
+.Ed
+.
+.Pp
+Each attribute in the expression represents a machine in the network
+.Ns (e.g.
+.Va test1
+and
+.Va test2 Ns
+) referring to a function defining a NixOS configuration. In each NixOS
+configuration, two attributes have a special meaning. The
+.Va deployment.targetHost
+specifies the address (domain name or IP address) of the system which is used by
+.Ic ssh
+to perform remote deployment operations. The
+.Va nixpkgs.localSystem.system
+attribute can be used to specify an architecture for the target machine, such as
+.Ql i686-linux
+which builds a 32-bit NixOS configuration. Omitting this property will build the
+configuration for the same architecture as the host system.
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl -show-trace
+Shows a trace of the output.
+.
+.It Fl -no-out-link
+Do not create a
+.Pa result
+symlink.
+.
+.It Fl h , -help
+Shows the usage of this command to the user.
+.
+.It Fl -option Ar name Va value
+Set the Nix configuration option
+.Va name
+to
+.Va value Ns
+\&. This overrides settings in the Nix configuration file (see
+.Xr nix.conf 5 Ns
+).
+.El
+.
+.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Eelco Dolstra
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-enter.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-enter.8
new file mode 100644
index 000000000000..646f92199d62
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-enter.8
@@ -0,0 +1,72 @@
+.Dd January 1, 1980
+.Dt nixos-enter 8
+.Os
+.Sh NAME
+.Nm nixos-enter
+.Nd run a command in a NixOS chroot environment
+.
+.
+.
+.Sh SYNOPSIS
+.Nm nixos-enter
+.Op Fl -root Ar root
+.Op Fl -system Ar system
+.Op Fl -command | c Ar shell-command
+.Op Fl -silent
+.Op Fl -help
+.Op Fl - Ar arguments ...
+.
+.
+.
+.Sh DESCRIPTION
+This command runs a command in a NixOS chroot environment, that is, in a filesystem hierarchy previously prepared using
+.Xr nixos-install 8 .
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl -root Ar root
+The path to the NixOS system you want to enter. It defaults to
+.Pa /mnt Ns
+\&.
+.It Fl -system Ar system
+The NixOS system configuration to use. It defaults to
+.Pa /nix/var/nix/profiles/system Ns
+\&. You can enter a previous NixOS configuration by specifying a path such as
+.Pa /nix/var/nix/profiles/system-106-link Ns
+\&.
+.
+.It Fl -command Ar shell-command , Fl c Ar shell-command
+The bash command to execute.
+.
+.It Fl -silent
+Suppresses all output from the activation script of the target system.
+.
+.It Fl -
+Interpret the remaining arguments as the program name and arguments to be invoked.
+The program is not executed in a shell.
+.El
+.
+.
+.
+.Sh EXAMPLES
+.Bl -tag -width indent
+.It Ic nixos-enter --root /mnt
+Start an interactive shell in the NixOS installation in
+.Pa /mnt Ns .
+.
+.It Ic nixos-enter -c 'ls -l /; cat /proc/mounts'
+Run a shell command.
+.
+.It Ic nixos-enter -- cat /proc/mounts
+Run a non-shell command.
+.El
+.
+.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Eelco Dolstra
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-generate-config.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-generate-config.8
new file mode 100644
index 000000000000..1b95599e156a
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-generate-config.8
@@ -0,0 +1,165 @@
+.Dd January 1, 1980
+.Dt nixos-generate-config 8
+.Os
+.Sh NAME
+.Nm nixos-generate-config
+.Nd generate NixOS configuration modules
+.
+.
+.
+.Sh SYNOPSIS
+.Nm nixos-generate-config
+.Op Fl -force
+.Op Fl -root Ar root
+.Op Fl -dir Ar dir
+.
+.
+.
+.Sh DESCRIPTION
+This command writes two NixOS configuration modules:
+.Bl -tag -width indent
+.It Pa /etc/nixos/hardware-configuration.nix
+This module sets NixOS configuration options based on your current hardware
+configuration. In particular, it sets the
+.Va fileSystem
+option to reflect all currently mounted file systems, the
+.Va swapDevices
+option to reflect active swap devices, and the
+.Va boot.initrd.*
+options to ensure that the initial ramdisk contains any kernel modules necessary
+for mounting the root file system.
+.Pp
+If this file already exists, it is overwritten. Thus, you should not modify it
+manually. Rather, you should include it from your
+.Pa /etc/nixos/configuration.nix Ns
+, and re-run
+.Nm
+to update it whenever your hardware configuration changes.
+.
+.It Pa /etc/nixos/configuration.nix
+This is the main NixOS system configuration module. If it already exists, it’s
+left unchanged. Otherwise,
+.Nm
+will write a template for you to customise.
+.El
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl -root Ar root
+If this option is given, treat the directory
+.Ar root
+as the root of the file system. This means that configuration files will be written to
+.Ql Ar root Ns /etc/nixos Ns
+, and that any file systems outside of
+.Ar root
+are ignored for the purpose of generating the
+.Va fileSystems
+option.
+.
+.It Fl -dir Ar dir
+If this option is given, write the configuration files to the directory
+.Ar dir
+instead of
+.Pa /etc/nixos Ns
+\&.
+.
+.It Fl -force
+Overwrite
+.Pa /etc/nixos/configuration.nix
+if it already exists.
+.
+.It Fl -no-filesystems
+Omit everything concerning file systems and swap devices from the hardware configuration.
+.
+.It Fl -show-hardware-config
+Don't generate
+.Pa configuration.nix
+or
+.Pa hardware-configuration.nix
+and print the hardware configuration to stdout only.
+.El
+.
+.
+.
+.Sh EXAMPLES
+This command is typically used during NixOS installation to write initial
+configuration modules. For example, if you created and mounted the target file
+systems on
+.Pa /mnt
+and
+.Pa /mnt/boot Ns
+, you would run:
+.Bd -literal -offset indent
+$ nixos-generate-config --root /mnt
+.Ed
+.
+.Pp
+The resulting file
+.Pa /mnt/etc/nixos/hardware-configuration.nix
+might look like this:
+.Bd -literal -offset indent
+# Do not modify this file!  It was generated by 'nixos-generate-config'
+# and may be overwritten by future invocations.  Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, pkgs, ... }:
+
+{
+  imports =
+    [ <nixos/modules/installer/scan/not-detected.nix>
+    ];
+
+  boot.initrd.availableKernelModules = [ "ehci_hcd" "ahci" ];
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    { device = "/dev/disk/by-label/nixos";
+      fsType = "ext3";
+      options = [ "rw" "data=ordered" "relatime" ];
+    };
+
+  fileSystems."/boot" =
+    { device = "/dev/sda1";
+      fsType = "ext3";
+      options = [ "rw" "errors=continue" "user_xattr" "acl" "barrier=1" "data=writeback" "relatime" ];
+    };
+
+  swapDevices =
+    [ { device = "/dev/sda2"; }
+    ];
+
+  nix.maxJobs = 8;
+}
+.Ed
+.
+.Pp
+It will also create a basic
+.Pa /mnt/etc/nixos/configuration.nix Ns
+, which you should edit to customise the logical configuration of your system. \
+This file includes the result of the hardware scan as follows:
+.Bd -literal -offset indent
+imports = [ ./hardware-configuration.nix ];
+.Ed
+.
+.Pp
+After installation, if your hardware configuration changes, you can run:
+.Bd -literal -offset indent
+$ nixos-generate-config
+.Ed
+.
+.Pp
+to update
+.Pa /etc/nixos/hardware-configuration.nix Ns
+\&. Your
+.Pa /etc/nixos/configuration.nix
+will
+.Em not
+be overwritten.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Eelco Dolstra
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-install.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-install.8
new file mode 100644
index 000000000000..c6c8ed15224d
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-install.8
@@ -0,0 +1,191 @@
+.Dd January 1, 1980
+.Dt nixos-install 8
+.Os
+.Sh NAME
+.Nm nixos-install
+.Nd install bootloader and NixOS
+.
+.
+.
+.Sh SYNOPSIS
+.Nm nixos-install
+.Op Fl -verbose | v
+.Op Fl I Ar path
+.Op Fl -root Ar root
+.Op Fl -system Ar path
+.Op Fl -flake Ar flake-uri
+.Op Fl -impure
+.Op Fl -channel Ar channel
+.Op Fl -no-channel-copy
+.Op Fl -no-root-password | -no-root-passwd
+.Op Fl -no-bootloader
+.Op Fl -max-jobs | j Ar number
+.Op Fl -cores Ar number
+.Op Fl -option Ar name value
+.Op Fl -show-trace
+.Op Fl -keep-going
+.Op Fl -help
+.
+.
+.
+.Sh DESCRIPTION
+This command installs NixOS in the file system mounted on
+.Pa /mnt Ns
+, based on the NixOS configuration specified in
+.Pa /mnt/etc/nixos/configuration.nix Ns
+\&. It performs the following steps:
+.
+.Bl -enum
+.It
+It copies Nix and its dependencies to
+.Pa /mnt/nix/store Ns
+\&.
+.
+.It
+It runs Nix in
+.Pa /mnt
+to build the NixOS configuration specified in
+.Pa /mnt/etc/nixos/configuration.nix Ns
+\&.
+.
+.It
+It installs the current channel
+.Dq nixos
+in the target channel profile (unless
+.Fl -no-channel-copy
+is specified).
+.
+.It
+It installs the GRUB boot loader on the device specified in the option
+.Va boot.loader.grub.device
+(unless
+.Fl -no-bootloader
+is specified), and generates a GRUB configuration file that boots into the NixOS
+configuration just installed.
+.
+.It
+It prompts you for a password for the root account (unless
+.Fl -no-root-password
+is specified).
+.El
+.
+.Pp
+This command is idempotent: if it is interrupted or fails due to a temporary
+problem (e.g. a network issue), you can safely re-run it.
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl -verbose , v
+Increases the level of verbosity of diagnostic messages printed on standard
+error. For each Nix operation, the information printed on standard output is
+well-defined; any diagnostic information is printed on standard error, never on
+standard output.
+.Pp
+Please note that this option may be specified repeatedly.
+.
+.It Fl -root Ar root
+Defaults to
+.Pa /mnt Ns
+\&. If this option is given, treat the directory
+.Ar root
+as the root of the NixOS installation.
+.
+.It Fl -system Ar path
+If this option is provided,
+.Nm
+will install the specified closure rather than attempt to build one from
+.Pa /mnt/etc/nixos/configuration.nix Ns
+\&.
+.Pp
+The closure must be an appropriately configured NixOS system, with boot loader
+and partition configuration that fits the target host. Such a closure is
+typically obtained with a command such as
+.Ic nix-build -I nixos-config=./configuration.nix '<nixpkgs/nixos>' -A system --no-out-link Ns
+\&.
+.
+.It Fl -flake Ar flake-uri Ns # Ns Ar name
+Build the NixOS system from the specified flake. The flake must contain an
+output named
+.Ql nixosConfigurations. Ns Ar name Ns
+\&.
+.
+.It Fl -channel Ar channel
+If this option is provided, do not copy the current
+.Dq nixos
+channel to the target host. Instead, use the specified derivation.
+.
+.It Fl I Ar Path
+Add a path to the Nix expression search path. This option may be given multiple
+times. See the
+.Ev NIX_PATH
+environment variable for information on the semantics of the Nix search path. Paths added through
+.Fl I
+take precedence over
+.Ev NIX_PATH Ns
+\&.
+.
+.It Fl -max-jobs , j Ar number
+Sets the maximum number of build jobs that Nix will perform in parallel to the
+specified number. The default is 1. A higher value is useful on SMP systems or
+to exploit I/O latency.
+.
+.It Fl -cores Ar N
+Sets the value of the
+.Ev NIX_BUILD_CORES
+environment variable in the invocation of builders. Builders can use this
+variable at their discretion to control the maximum amount of parallelism. For
+instance, in Nixpkgs, if the derivation attribute
+.Va enableParallelBuilding
+is set to true, the builder passes the
+.Fl j Ns Va N
+flag to GNU Make. The value 0 means that the builder should use all available CPU cores in the system.
+.
+.It Fl -option Ar name value
+Set the Nix configuration option
+.Ar name
+to
+.Ar value Ns
+\&.
+.
+.It Fl -show-trace
+Causes Nix to print out a stack trace in case of Nix expression evaluation errors.
+.
+.It Fl -keep-going
+Causes Nix to continue building derivations as far as possible in the face of failed builds.
+.
+.It Fl -help
+Synonym for
+.Ic man nixos-install Ns
+\&.
+.El
+.
+.
+.
+.Sh EXAMPLES
+A typical NixOS installation is done by creating and mounting a file system on
+.Pa /mnt Ns
+, generating a NixOS configuration in
+.Pa /mnt/etc/nixos/configuration.nix Ns
+, and running
+.Nm Ns
+\&. For instance, if we want to install NixOS on an ext4 file system created in
+.Pa /dev/sda1 Ns
+:
+.Bd -literal -offset indent
+$ mkfs.ext4 /dev/sda1
+$ mount /dev/sda1 /mnt
+$ nixos-generate-config --root /mnt
+$ # edit /mnt/etc/nixos/configuration.nix
+$ nixos-install
+$ reboot
+.Ed
+.
+.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Eelco Dolstra
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-option.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-option.8
new file mode 100644
index 000000000000..28438b03580b
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-option.8
@@ -0,0 +1,89 @@
+.Dd January 1, 1980
+.Dt nixos-option 8
+.Os
+.Sh NAME
+.Nm nixos-option
+.Nd inspect a NixOS configuration
+.
+.
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl r | -recursive
+.Op Fl I Ar path
+.Ar option.name
+.
+.
+.
+.Sh DESCRIPTION
+This command evaluates the configuration specified in
+.Pa /etc/nixos/configuration.nix
+and returns the properties of the option name given as argument.
+.
+.Pp
+When the option name is not an option, the command prints the list of attributes
+contained in the attribute set.
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl r , -recursive
+Print all the values at or below the specified path recursively.
+.
+.It Fl I Ar path
+This option is passed to the underlying
+.Xr nix-instantiate 1
+invocation.
+.El
+.
+.
+.
+.Sh ENVIRONMENT
+.Bl -tag -width indent
+.It Ev NIXOS_CONFIG
+Path to the main NixOS configuration module. Defaults to
+.Pa /etc/nixos/configuration.nix Ns
+\&.
+.El
+.
+.
+.
+.Sh EXAMPLES
+Investigate option values:
+.Bd -literal -offset indent
+$ nixos-option boot.loader
+This attribute set contains:
+generationsDir
+grub
+initScript
+
+$ nixos-option boot.loader.grub.enable
+Value:
+true
+
+Default:
+true
+
+Description:
+Whether to enable the GNU GRUB boot loader.
+
+Declared by:
+  "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix"
+
+Defined by:
+  "/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix"
+.Ed
+.
+.
+.
+.Sh SEE ALSO
+.Xr configuration.nix 5
+.
+.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Nicolas Pierron
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-rebuild.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-rebuild.8
new file mode 100644
index 000000000000..64bbbee411d7
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-rebuild.8
@@ -0,0 +1,452 @@
+.Dd January 1, 1980
+.Dt nixos-rebuild 8
+.Os
+.Sh NAME
+.Nm nixos-rebuild
+.Nd reconfigure a NixOS machine
+.
+.
+.
+.Sh SYNOPSIS
+.Nm
+.Bro
+.Cm switch | boot | test | build | dry-build | dry-activate | edit | build-vm | build-vm-with-bootloader
+.Brc
+.br
+.Op Fl -upgrade | -upgrade-all
+.Op Fl -install-bootloader
+.Op Fl -no-build-nix
+.Op Fl -fast
+.Op Fl -rollback
+.Op Fl -builders Ar builder-spec
+.br
+.Op Fl -flake Ar flake-uri
+.Op Fl -no-flake
+.Op Fl -override-input Ar input-name flake-uri
+.br
+.Op Fl -profile-name | p Ar name
+.Op Fl -specialisation | c Ar name
+.br
+.Op Fl -build-host Va host
+.Op Fl -target-host Va host
+.Op Fl -use-remote-sudo
+.br
+.Op Fl -show-trace
+.Op Fl I Va NIX_PATH
+.Op Fl -verbose | v
+.Op Fl -impure
+.Op Fl -max-jobs | j Va number
+.Op Fl -keep-failed | K
+.Op Fl -keep-going | k
+.
+.
+.
+.Sh DESCRIPTION
+This command updates the system so that it corresponds to the
+configuration specified in
+.Pa /etc/nixos/configuration.nix
+or
+.Pa /etc/nixos/flake.nix Ns
+\&. Thus, every time you modify the configuration or any other NixOS module, you
+must run
+.Nm
+to make the changes take effect. It builds the new system in
+.Pa /nix/store Ns
+, runs its activation script, and stop and (re)starts any system services if
+needed. Please note that user services need to be started manually as they
+aren't detected by the activation script at the moment.
+.
+.Pp
+This command has one required argument, which specifies the desired
+operation. It must be one of the following:
+.Bl -tag -width indent
+.It Cm switch
+Build and activate the new configuration, and make it the boot default. That
+is, the configuration is added to the GRUB boot menu as the default
+menu entry, so that subsequent reboots will boot the system into the new
+configuration. Previous configurations activated with
+.Ic nixos-rebuild switch
+or
+.Ic nixos-rebuild boot
+remain available in the GRUB menu.
+.Pp
+Note that if you are using specializations, running just
+.Ic nixos-rebuild switch
+will switch you back to the unspecialized, base system \(em in that case, you
+might want to use this instead:
+.Bd -literal -offset indent
+$ nixos-rebuild switch --specialisation your-specialisation-name
+.Ed
+.Pp
+This command will build all specialisations and make them bootable just
+like regular
+.Ic nixos-rebuild switch
+does \(em the only thing different is that it will switch to given
+specialisation instead of the base system; it can be also used to switch from
+the base system into a specialised one, or to switch between specialisations.
+.
+.It Cm boot
+Build the new configuration and make it the boot default (as with
+.Ic nixos-rebuild switch Ns
+), but do not activate it. That is, the system continues to run the previous
+configuration until the next reboot.
+.
+.It Cm test
+Build and activate the new configuration, but do not add it to the GRUB
+boot menu. Thus, if you reboot the system (or if it crashes), you will
+automatically revert to the default configuration (i.e. the
+configuration resulting from the last call to
+.Ic nixos-rebuild switch
+or
+.Ic nixos-rebuild boot Ns
+).
+.Pp
+Note that if you are using specialisations, running just
+.Ic nixos-rebuild test
+will activate the unspecialised, base system \(em in that case, you might want
+to use this instead:
+.Bd -literal -offset indent
+$ nixos-rebuild test --specialisation your-specialisation-name
+.Ed
+.Pp
+This command can be also used to switch from the base system into a
+specialised one, or to switch between specialisations.
+.
+.It Cm build
+Build the new configuration, but neither activate it nor add it to the
+GRUB boot menu. It leaves a symlink named
+.Pa result
+in the current directory, which points to the output of the top-level
+.Dq system
+derivation. This is essentially the same as doing
+.Bd -literal -offset indent
+$ nix-build /path/to/nixpkgs/nixos -A system
+.Ed
+.Pp
+Note that you do not need to be root to run
+.Ic nixos-rebuild build Ns
+\&.
+.
+.It Cm dry-build
+Show what store paths would be built or downloaded by any of the
+operations above, but otherwise do nothing.
+.
+.It Cm dry-activate
+Build the new configuration, but instead of activating it, show what
+changes would be performed by the activation (i.e. by
+.Ic nixos-rebuild test Ns
+). For instance, this command will print which systemd units would be restarted.
+The list of changes is not guaranteed to be complete.
+.
+.It Cm edit
+Opens
+.Pa configuration.nix
+in the default editor.
+.
+.It Cm build-vm
+Build a script that starts a NixOS virtual machine with the desired
+configuration. It leaves a symlink
+.Pa result
+in the current directory that points (under
+.Ql result/bin/run\- Ns Va hostname Ns \-vm Ns
+)
+at the script that starts the VM. Thus, to test a NixOS configuration in
+a virtual machine, you should do the following:
+.Bd -literal -offset indent
+$ nixos-rebuild build-vm
+$ ./result/bin/run-*-vm
+.Ed
+.Pp
+The VM is implemented using the
+.Ql qemu
+package. For best performance, you should load the
+.Ql kvm-intel
+or
+.Ql kvm-amd
+kernel modules to get hardware virtualisation.
+.Pp
+The VM mounts the Nix store of the host through the 9P file system. The
+host Nix store is read-only, so Nix commands that modify the Nix store
+will not work in the VM. This includes commands such as
+.Nm Ns
+; to change the VM’s configuration, you must halt the VM and re-run the commands
+above.
+.Pp
+The VM has its own ext3 root file system, which is automatically created when
+the VM is first started, and is persistent across reboots of the VM. It is
+stored in
+.Ql ./ Ns Va hostname Ns .qcow2 Ns
+\&.
+.\" The entire file system hierarchy of the host is available in
+.\" the VM under
+.\" .Pa /hostfs Ns
+.\" .
+.
+.It Cm build-vm-with-bootloader
+Like
+.Cm build-vm Ns
+, but boots using the regular boot loader of your configuration (e.g. GRUB 1 or
+2), rather than booting directly into the kernel and initial ramdisk of the
+system. This allows you to test whether the boot loader works correctly. \
+However, it does not guarantee that your NixOS configuration will boot
+successfully on the host hardware (i.e., after running
+.Ic nixos-rebuild switch Ns
+), because the hardware and boot loader configuration in the VM are different.
+The boot loader is installed on an automatically generated virtual disk
+containing a
+.Pa /boot
+partition.
+.El
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl -upgrade , -upgrade-all
+Update the root user's channel named
+.Ql nixos
+before rebuilding the system.
+.Pp
+In addition to the
+.Ql nixos
+channel, the root user's channels which have a file named
+.Ql .update-on-nixos-rebuild
+in their base directory will also be updated.
+.Pp
+Passing
+.Fl -upgrade-all
+updates all of the root user's channels.
+.
+.It Fl -install-bootloader
+Causes the boot loader to be (re)installed on the device specified by the
+relevant configuration options.
+.
+.It Fl -no-build-nix
+Normally,
+.Nm
+first builds the
+.Ql nixUnstable
+attribute in Nixpkgs, and uses the resulting instance of the Nix package manager
+to build the new system configuration. This is necessary if the NixOS modules
+use features not provided by the currently installed version of Nix. This option
+disables building a new Nix.
+.
+.It Fl -fast
+Equivalent to
+.Fl -no-build-nix Ns
+\&. This option is useful if you call
+.Nm
+frequently (e.g. if you’re hacking on a NixOS module).
+.
+.It Fl -rollback
+Instead of building a new configuration as specified by
+.Pa /etc/nixos/configuration.nix Ns
+, roll back to the previous configuration. (The previous configuration is
+defined as the one before the “current” generation of the Nix profile
+.Pa /nix/var/nix/profiles/system Ns
+\&.)
+.
+.It Fl -builders Ar builder-spec
+Allow ad-hoc remote builders for building the new system. This requires
+the user executing
+.Nm
+(usually root) to be configured as a trusted user in the Nix daemon. This can be
+achieved by using the
+.Va nix.settings.trusted-users
+NixOS option. Examples values for that option are described in the
+.Dq Remote builds
+chapter in the Nix manual, (i.e.
+.Ql --builders \(dqssh://bigbrother x86_64-linux\(dq Ns
+). By specifying an empty string existing builders specified in
+.Pa /etc/nix/machines
+can be ignored:
+.Ql --builders \(dq\(dq
+for example when they are not reachable due to network connectivity.
+.
+.It Fl -profile-name Ar name , Fl p Ar name
+Instead of using the Nix profile
+.Pa /nix/var/nix/profiles/system
+to keep track of the current and previous system configurations, use
+.Pa /nix/var/nix/profiles/system-profiles/ Ns Va name Ns
+\&. When you use GRUB 2, for every system profile created with this flag, NixOS
+will create a submenu named
+.Dq NixOS - Profile Va name
+in GRUB’s boot menu, containing the current and previous configurations of this profile.
+.Pp
+For instance, if you want to test a configuration file named
+.Pa test.nix
+without affecting the default system profile, you would do:
+.Bd -literal -offset indent
+$ nixos-rebuild switch -p test -I nixos-config=./test.nix
+.Ed
+.Pp
+The new configuration will appear in the GRUB 2 submenu
+.Dq NixOS - Profile 'test' Ns
+\&.
+.
+.It Fl -specialisation Ar name , Fl c Ar name
+Activates given specialisation; when not specified, switching and testing
+will activate the base, unspecialised system.
+.
+.It Fl -build-host Ar host
+Instead of building the new configuration locally, use the specified host
+to perform the build. The host needs to be accessible with
+.Ic ssh Ns ,
+and must be able to perform Nix builds. If the option
+.Fl -target-host
+is not set, the build will be copied back to the local machine when done.
+.Pp
+Note that, if
+.Fl -no-build-nix
+is not specified, Nix will be built both locally and remotely. This is because
+the configuration will always be evaluated locally even though the building
+might be performed remotely.
+.Pp
+You can include a remote user name in the host name
+.Ns ( Va user@host Ns
+). You can also set ssh options by defining the
+.Ev NIX_SSHOPTS
+environment variable.
+.
+.It Fl -target-host Ar host
+Specifies the NixOS target host. By setting this to something other than an
+empty string, the system activation will happen on the remote host instead of
+the local machine. The remote host needs to be accessible over
+.Ic ssh Ns ,
+and for the commands
+.Cm switch Ns
+,
+.Cm boot
+and
+.Cm test
+you need root access.
+.Pp
+If
+.Fl -build-host
+is not explicitly specified or empty, building will take place locally.
+.Pp
+You can include a remote user name in the host name
+.Ns ( Va user@host Ns
+). You can also set ssh options by defining the
+.Ev NIX_SSHOPTS
+environment variable.
+.Pp
+Note that
+.Nm
+honors the
+.Va nixpkgs.crossSystem
+setting of the given configuration but disregards the true architecture of the
+target host. Hence the
+.Va nixpkgs.crossSystem
+setting has to match the target platform or else activation will fail.
+.
+.It Fl -use-substitutes
+When set, nixos-rebuild will add
+.Fl -use-substitutes
+to each invocation of nix-copy-closure. This will only affect the behavior of
+nixos-rebuild if
+.Fl -target-host
+or
+.Fl -build-host
+is also set. This is useful when the target-host connection to cache.nixos.org
+is faster than the connection between hosts.
+.
+.It Fl -use-remote-sudo
+When set, nixos-rebuild prefixes remote commands that run on the
+.Fl -build-host
+and
+.Fl -target-host
+systems with
+.Ic sudo Ns
+\&. Setting this option allows deploying as a non-root user.
+.
+.It Fl -flake Va flake-uri Ns Op Va #name
+Build the NixOS system from the specified flake. It defaults to the directory
+containing the target of the symlink
+.Pa /etc/nixos/flake.nix Ns
+, if it exists. The flake must contain an output named
+.Ql nixosConfigurations. Ns Va name Ns
+\&. If
+.Va name
+is omitted, it default to the current host name.
+.
+.It Fl -no-flake
+Do not imply
+.Fl -flake
+if
+.Pa /etc/nixos/flake.nix
+exists. With this option, it is possible to build non-flake NixOS configurations
+even if the current NixOS systems uses flakes.
+.El
+.Pp
+In addition,
+.Nm
+accepts various Nix-related flags, including
+.Fl -max-jobs Ns ,
+.Fl j Ns ,
+.Fl I Ns ,
+.Fl -show-trace Ns ,
+.Fl -keep-failed Ns ,
+.Fl -keep-going Ns ,
+.Fl -impure Ns ,
+.Fl -verbose Ns , and
+.Fl v Ns
+\&. See the Nix manual for details.
+.
+.
+.
+.Sh ENVIRONMENT
+.Bl -tag -width indent
+.It Ev NIXOS_CONFIG
+Path to the main NixOS configuration module. Defaults to
+.Pa /etc/nixos/configuration.nix Ns
+\&.
+.
+.It Ev NIX_PATH
+A colon-separated list of directories used to look up Nix expressions enclosed
+in angle brackets (e.g. <nixpkgs>). Example:
+.Bd -literal -offset indent
+nixpkgs=./my-nixpkgs
+.Ed
+.
+.It Ev NIX_SSHOPTS
+Additional options to be passed to
+.Ic ssh
+on the command line.
+.El
+.
+.
+.
+.Sh FILES
+.Bl -tag -width indent
+.It Pa /etc/nixos/flake.nix
+If this file exists, then
+.Nm
+will use it as if the
+.Fl -flake
+option was given. This file may be a symlink to a
+.Pa flake.nix
+in an actual flake; thus
+.Pa /etc/nixos
+need not be a flake.
+.
+.It Pa /run/current-system
+A symlink to the currently active system configuration in the Nix store.
+.
+.It Pa /nix/var/nix/profiles/system
+The Nix profile that contains the current and previous system
+configurations. Used to generate the GRUB boot menu.
+.El
+.
+.
+.
+.Sh BUGS
+This command should be renamed to something more descriptive.
+.
+.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Eelco Dolstra
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manpages/nixos-version.8 b/nixpkgs/nixos/doc/manual/manpages/nixos-version.8
new file mode 100644
index 000000000000..f661611599fb
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manpages/nixos-version.8
@@ -0,0 +1,86 @@
+.Dd January 1, 1980
+.Dt nixos-version 8
+.Os
+.Sh NAME
+.Nm nixos-version
+.Nd show the NixOS version
+.
+.
+.
+.Sh SYNOPSIS
+.Nm nixos-version
+.Op Fl -hash
+.Op Fl -revision
+.Op Fl -configuration-revision
+.Op Fl -json
+.
+.
+.
+.Sh DESCRIPTION
+This command shows the version of the currently active NixOS configuration. For example:
+.Bd -literal -offset indent
+$ nixos-version
+16.03.1011.6317da4 (Emu)
+.Ed
+.
+.Pp
+The version consists of the following elements:
+.Bl -tag -width indent
+.It Ql 16.03
+The NixOS release, indicating the year and month in which it was released
+(e.g. March 2016).
+.It Ql 1011
+The number of commits in the Nixpkgs Git repository between the start of the
+release branch and the commit from which this version was built. This ensures
+that NixOS versions are monotonically increasing. It is
+.Ql git
+when the current NixOS configuration was built from a checkout of the Nixpkgs
+Git repository rather than from a NixOS channel.
+.It Ql 6317da4
+The first 7 characters of the commit in the Nixpkgs Git repository from which
+this version was built.
+.It Ql Emu
+The code name of the NixOS release. The first letter of the code name indicates
+that this is the N'th stable NixOS release; for example, Emu is the fifth
+release.
+.El
+.
+.
+.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl -hash , -revision
+Show the full SHA1 hash of the Git commit from which this configuration was
+built, e.g.
+.Bd -literal -offset indent
+$ nixos-version --hash
+6317da40006f6bc2480c6781999c52d88dde2acf
+.Ed
+.
+.It Fl -configuration-revision
+Show the configuration revision if available. This could be the full SHA1 hash
+of the Git commit of the system flake, if you add
+.Bd -literal -offset indent
+{ system.configurationRevision = self.rev or "dirty"; }
+.Ed
+.Pp
+to the
+.Ql modules
+array of your flake.nix system configuration e.g.
+.Bd -literal -offset indent
+$ nixos-version --configuration-revision
+aa314ebd1592f6cdd53cb5bba8bcae97d9323de8
+.Ed
+.
+.It Fl -json
+Print a JSON representation of the versions of NixOS and the top-level
+configuration flake.
+.El
+.
+.
+.
+.Sh AUTHORS
+.An -nosplit
+.An Eelco Dolstra
+and
+.An the Nixpkgs/NixOS contributors
diff --git a/nixpkgs/nixos/doc/manual/manual.md b/nixpkgs/nixos/doc/manual/manual.md
new file mode 100644
index 000000000000..8cb766eeccf6
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/manual.md
@@ -0,0 +1,56 @@
+# NixOS Manual {#book-nixos-manual}
+## Version @NIXOS_VERSION@
+
+<!--
+  this is the top-level structure file for the nixos manual.
+
+  the manual structure extends the nixpkgs commonmark further with include
+  blocks to allow better organization of input text. there are six types of
+  include blocks: preface, parts, chapters, sections, appendix, and options.
+  each type except `options`` corresponds to the docbook elements of (roughly)
+  the same name, and can itself can further include blocks to denote its
+  substructure.
+
+  non-`options`` include blocks are fenced code blocks that list a number of
+  files to include, in the form
+
+     ```{=include=} <type>
+     <file-name-1>
+     <file-name-2>
+     <...>
+     ```
+
+  `options` include blocks do not list file names but contain a list of key-value
+  pairs that describe the options to be included and how to convert them into
+  elements of the manual output type:
+
+      ```{=include=} options
+      id-prefix: <options id prefix>
+      list-id: <variable list element id>
+      source: <path to options.json>
+      ```
+
+-->
+
+```{=include=} preface
+preface.md
+```
+
+```{=include=} parts
+installation/installation.md
+configuration/configuration.md
+administration/running.md
+development/development.md
+```
+
+```{=include=} chapters
+contributing-to-this-manual.chapter.md
+```
+
+```{=include=} appendix html:into-file=//options.html
+nixos-options.md
+```
+
+```{=include=} appendix html:into-file=//release-notes.html
+release-notes/release-notes.md
+```
diff --git a/nixpkgs/nixos/doc/manual/manual.xml b/nixpkgs/nixos/doc/manual/manual.xml
deleted file mode 100644
index 158b3507a58e..000000000000
--- a/nixpkgs/nixos/doc/manual/manual.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<book 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="book-nixos-manual">
- <info>
-  <title>NixOS Manual</title>
-  <subtitle>Version <xi:include href="./generated/version" parse="text" />
-  </subtitle>
- </info>
- <xi:include href="preface.xml" />
- <xi:include href="installation/installation.xml" />
- <xi:include href="configuration/configuration.xml" />
- <xi:include href="administration/running.xml" />
-<!-- <xi:include href="userconfiguration.xml" /> -->
- <xi:include href="development/development.xml" />
- <appendix xml:id="ch-options">
-  <title>Configuration Options</title>
-  <xi:include href="./generated/options-db.xml"
-                xpointer="configuration-variable-list" />
- </appendix>
- <xi:include href="./from_md/contributing-to-this-manual.chapter.xml" />
- <xi:include href="release-notes/release-notes.xml" />
-</book>
diff --git a/nixpkgs/nixos/doc/manual/md-to-db.sh b/nixpkgs/nixos/doc/manual/md-to-db.sh
deleted file mode 100755
index 2091f9b31cd2..000000000000
--- a/nixpkgs/nixos/doc/manual/md-to-db.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#! /usr/bin/env nix-shell
-#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/tarball/21.11 -i bash -p pandoc
-
-# This script is temporarily needed while we transition the manual to
-# CommonMark. It converts the .md files in the regular manual folder
-# into DocBook files in the from_md folder.
-
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
-pushd "$DIR"
-
-# NOTE: Keep in sync with Nixpkgs manual (/doc/Makefile).
-# TODO: Remove raw-attribute when we can get rid of DocBook altogether.
-pandoc_commonmark_enabled_extensions=+attributes+fenced_divs+footnotes+bracketed_spans+definition_lists+pipe_tables+raw_attribute
-pandoc_flags=(
-  # Not needed:
-  # - diagram-generator.lua (we do not support that in NixOS manual to limit dependencies)
-  # - media extraction (was only required for diagram generator)
-  # - docbook-reader/citerefentry-to-rst-role.lua (only relevant for DocBook → MarkDown/rST/MyST)
-  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/myst-reader/roles.lua"
-  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/link-unix-man-references.lua"
-  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/docbook-writer/rst-roles.lua"
-  "--lua-filter=$DIR/../../../doc/build-aux/pandoc-filters/docbook-writer/labelless-link-is-xref.lua"
-  -f "commonmark${pandoc_commonmark_enabled_extensions}+smart"
-  -t docbook
-)
-
-OUT="$DIR/from_md"
-mapfile -t MD_FILES < <(find . -type f -regex '.*\.md$')
-
-for mf in ${MD_FILES[*]}; do
-  if [ "${mf: -11}" == ".section.md" ]; then
-    mkdir -p "$(dirname "$OUT/$mf")"
-    OUTFILE="$OUT/${mf%".section.md"}.section.xml"
-    pandoc "$mf" "${pandoc_flags[@]}" \
-      -o "$OUTFILE"
-    grep -q -m 1 "xi:include" "$OUTFILE" && sed -i 's|xmlns:xlink="http://www.w3.org/1999/xlink"| xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude"|' "$OUTFILE"
-  fi
-
-  if [ "${mf: -11}" == ".chapter.md" ]; then
-    mkdir -p "$(dirname "$OUT/$mf")"
-    OUTFILE="$OUT/${mf%".chapter.md"}.chapter.xml"
-    pandoc "$mf" "${pandoc_flags[@]}" \
-      --top-level-division=chapter \
-      -o "$OUTFILE"
-    grep -q -m 1 "xi:include" "$OUTFILE" && sed -i 's|xmlns:xlink="http://www.w3.org/1999/xlink"| xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude"|' "$OUTFILE"
-  fi
-done
-
-popd
diff --git a/nixpkgs/nixos/doc/manual/nixos-options.md b/nixpkgs/nixos/doc/manual/nixos-options.md
new file mode 100644
index 000000000000..33b487c95a2e
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/nixos-options.md
@@ -0,0 +1,7 @@
+# Configuration Options {#ch-options}
+
+```{=include=} options
+id-prefix: opt-
+list-id: configuration-variable-list
+source: @NIXOS_OPTIONS_JSON@
+```
diff --git a/nixpkgs/nixos/doc/manual/preface.md b/nixpkgs/nixos/doc/manual/preface.md
new file mode 100644
index 000000000000..b33af979c5a9
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/preface.md
@@ -0,0 +1,11 @@
+# Preface {#preface}
+
+This manual describes how to install, use and extend NixOS, a Linux distribution based on the purely functional package management system [Nix](https://nixos.org/nix), that is composed using modules and packages defined in the [Nixpkgs](https://nixos.org/nixpkgs) project.
+
+Additional information regarding the Nix package manager and the Nixpkgs project can be found in respectively the [Nix manual](https://nixos.org/nix/manual) and the [Nixpkgs manual](https://nixos.org/nixpkgs/manual).
+
+If you encounter problems, please report them on the [`Discourse`](https://discourse.nixos.org), the [Matrix room](https://matrix.to/#/%23nix:nixos.org), or on the [`#nixos` channel on Libera.Chat](irc://irc.libera.chat/#nixos). Alternatively, consider [contributing to this manual](#chap-contributing). Bugs should be reported in [NixOS’ GitHub issue tracker](https://github.com/NixOS/nixpkgs/issues).
+
+::: {.note}
+Commands prefixed with `#` have to be run as root, either requiring to login as root user or temporarily switching to it using `sudo` for example.
+:::
diff --git a/nixpkgs/nixos/doc/manual/preface.xml b/nixpkgs/nixos/doc/manual/preface.xml
deleted file mode 100644
index c0d530c3d1b5..000000000000
--- a/nixpkgs/nixos/doc/manual/preface.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<preface xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xml:id="preface">
- <title>Preface</title>
- <para>
-  This manual describes how to install, use and extend NixOS, a Linux
-  distribution based on the purely functional package management system
-  <link xlink:href="https://nixos.org/nix">Nix</link>, that is composed
-  using modules and packages defined in the
-  <link xlink:href="https://nixos.org/nixpkgs">Nixpkgs</link> project.
- </para>
- <para>
-  Additional information regarding the Nix package manager and the Nixpkgs
-  project can be found in respectively the
-  <link xlink:href="https://nixos.org/nix/manual">Nix manual</link> and the
-  <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs manual</link>.
- </para>
- <para>
-  If you encounter problems, please report them on the
-  <literal
-   xlink:href="https://discourse.nixos.org">Discourse</literal>,
-  the <link
-   xlink:href="https://matrix.to/#nix:nixos.org">Matrix room</link>,
-  or on the <link
-   xlink:href="irc://irc.libera.chat/#nixos">
-  <literal>#nixos</literal> channel on Libera.Chat</link>.
-  Alternatively, consider <link
-   xlink:href="#chap-contributing">
-   contributing to this manual</link>. Bugs should be
-  reported in
-  <link
-   xlink:href="https://github.com/NixOS/nixpkgs/issues">NixOS’
-  GitHub issue tracker</link>.
- </para>
- <note>
-  <para>
-   Commands prefixed with <literal>#</literal> have to be run as root, either
-   requiring to login as root user or temporarily switching to it using
-   <literal>sudo</literal> for example.
-  </para>
- </note>
-</preface>
diff --git a/nixpkgs/nixos/doc/manual/release-notes/release-notes.md b/nixpkgs/nixos/doc/manual/release-notes/release-notes.md
new file mode 100644
index 000000000000..3f926fb21a5c
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/release-notes/release-notes.md
@@ -0,0 +1,26 @@
+# Release Notes {#ch-release-notes}
+
+This section lists the release notes for each stable version of NixOS and current unstable revision.
+
+```{=include=} sections
+rl-2311.section.md
+rl-2305.section.md
+rl-2211.section.md
+rl-2205.section.md
+rl-2111.section.md
+rl-2105.section.md
+rl-2009.section.md
+rl-2003.section.md
+rl-1909.section.md
+rl-1903.section.md
+rl-1809.section.md
+rl-1803.section.md
+rl-1709.section.md
+rl-1703.section.md
+rl-1609.section.md
+rl-1603.section.md
+rl-1509.section.md
+rl-1412.section.md
+rl-1404.section.md
+rl-1310.section.md
+```
diff --git a/nixpkgs/nixos/doc/manual/release-notes/release-notes.xml b/nixpkgs/nixos/doc/manual/release-notes/release-notes.xml
deleted file mode 100644
index ee5009faf6f4..000000000000
--- a/nixpkgs/nixos/doc/manual/release-notes/release-notes.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<appendix xmlns="http://docbook.org/ns/docbook"
-          xmlns:xlink="http://www.w3.org/1999/xlink"
-          xmlns:xi="http://www.w3.org/2001/XInclude"
-          version="5.0"
-          xml:id="ch-release-notes">
- <title>Release Notes</title>
- <para>
-  This section lists the release notes for each stable version of NixOS and
-  current unstable revision.
- </para>
- <xi:include href="../from_md/release-notes/rl-2211.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2205.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2111.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2105.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2009.section.xml" />
- <xi:include href="../from_md/release-notes/rl-2003.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1909.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1903.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1809.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1803.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1709.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1703.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1609.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1603.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1509.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1412.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1404.section.xml" />
- <xi:include href="../from_md/release-notes/rl-1310.section.xml" />
-</appendix>
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1509.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1509.section.md
index 55804ddb988a..1422ae4c299c 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1509.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1509.section.md
@@ -2,7 +2,7 @@
 
 In addition to numerous new and upgraded packages, this release has the following highlights:
 
-- The [Haskell](http://haskell.org/) packages infrastructure has been re-designed from the ground up (\"Haskell NG\"). NixOS now distributes the latest version of every single package registered on [Hackage](http://hackage.haskell.org/) \-- well in excess of 8,000 Haskell packages. Detailed instructions on how to use that infrastructure can be found in the [User\'s Guide to the Haskell Infrastructure](https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure). Users migrating from an earlier release may find helpful information below, in the list of backwards-incompatible changes. Furthermore, we distribute 51(!) additional Haskell package sets that provide every single [LTS Haskell](http://www.stackage.org/) release since version 0.0 as well as the most recent [Stackage Nightly](http://www.stackage.org/) snapshot. The announcement [\"Full Stackage Support in Nixpkgs\"](https://nixos.org/nix-dev/2015-September/018138.html) gives additional details.
+- The [Haskell](http://haskell.org/) packages infrastructure has been re-designed from the ground up ("Haskell NG"). NixOS now distributes the latest version of every single package registered on [Hackage](http://hackage.haskell.org/) \-- well in excess of 8,000 Haskell packages. Detailed instructions on how to use that infrastructure can be found in the [User's Guide to the Haskell Infrastructure](https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure). Users migrating from an earlier release may find helpful information below, in the list of backwards-incompatible changes. Furthermore, we distribute 51(!) additional Haskell package sets that provide every single [LTS Haskell](http://www.stackage.org/) release since version 0.0 as well as the most recent [Stackage Nightly](http://www.stackage.org/) snapshot. The announcement ["Full Stackage Support in Nixpkgs"](https://nixos.org/nix-dev/2015-September/018138.html) gives additional details.
 
 - Nix has been updated to version 1.10, which among other improvements enables cryptographic signatures on binary caches for improved security.
 
@@ -178,7 +178,7 @@ The new option `system.stateVersion` ensures that certain configuration changes
 
 - Nix now requires binary caches to be cryptographically signed. If you have unsigned binary caches that you want to continue to use, you should set `nix.requireSignedBinaryCaches = false`.
 
-- Steam now doesn\'t need root rights to work. Instead of using `*-steam-chrootenv`, you should now just run `steam`. `steamChrootEnv` package was renamed to `steam`, and old `steam` package \-- to `steamOriginal`.
+- Steam now doesn't need root rights to work. Instead of using `*-steam-chrootenv`, you should now just run `steam`. `steamChrootEnv` package was renamed to `steam`, and old `steam` package \-- to `steamOriginal`.
 
 - CMPlayer has been renamed to bomi upstream. Package `cmplayer` was accordingly renamed to `bomi`
 
@@ -203,7 +203,7 @@ The new option `system.stateVersion` ensures that certain configuration changes
 }
 ```
 
-- \"`nix-env -qa`\" no longer discovers Haskell packages by name. The only packages visible in the global scope are `ghc`, `cabal-install`, and `stack`, but all other packages are hidden. The reason for this inconvenience is the sheer size of the Haskell package set. Name-based lookups are expensive, and most `nix-env -qa` operations would become much slower if we\'d add the entire Hackage database into the top level attribute set. Instead, the list of Haskell packages can be displayed by running:
+- "`nix-env -qa`" no longer discovers Haskell packages by name. The only packages visible in the global scope are `ghc`, `cabal-install`, and `stack`, but all other packages are hidden. The reason for this inconvenience is the sheer size of the Haskell package set. Name-based lookups are expensive, and most `nix-env -qa` operations would become much slower if we'd add the entire Hackage database into the top level attribute set. Instead, the list of Haskell packages can be displayed by running:
 
 ```ShellSession
 nix-env -f "<nixpkgs>" -qaP -A haskellPackages
@@ -217,11 +217,11 @@ nix-env -f "<nixpkgs>" -iA haskellPackages.pandoc
 
 Installing Haskell _libraries_ this way, however, is no longer supported. See the next item for more details.
 
-- Previous versions of NixOS came with a feature called `ghc-wrapper`, a small script that allowed GHC to transparently pick up on libraries installed in the user\'s profile. This feature has been deprecated; `ghc-wrapper` was removed from the distribution. The proper way to register Haskell libraries with the compiler now is the `haskellPackages.ghcWithPackages` function. The [User\'s Guide to the Haskell Infrastructure](https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure) provides more information about this subject.
+- Previous versions of NixOS came with a feature called `ghc-wrapper`, a small script that allowed GHC to transparently pick up on libraries installed in the user's profile. This feature has been deprecated; `ghc-wrapper` was removed from the distribution. The proper way to register Haskell libraries with the compiler now is the `haskellPackages.ghcWithPackages` function. The [User's Guide to the Haskell Infrastructure](https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure) provides more information about this subject.
 
 - All Haskell builds that have been generated with version 1.x of the `cabal2nix` utility are now invalid and need to be re-generated with a current version of `cabal2nix` to function. The most recent version of this tool can be installed by running `nix-env -i cabal2nix`.
 
-- The `haskellPackages` set in Nixpkgs used to have a function attribute called `extension` that users could override in their `~/.nixpkgs/config.nix` files to configure additional attributes, etc. That function still exists, but it\'s now called `overrides`.
+- The `haskellPackages` set in Nixpkgs used to have a function attribute called `extension` that users could override in their `~/.nixpkgs/config.nix` files to configure additional attributes, etc. That function still exists, but it's now called `overrides`.
 
 - The OpenBLAS library has been updated to version `0.2.14`. Support for the `x86_64-darwin` platform was added. Dynamic architecture detection was enabled; OpenBLAS now selects microarchitecture-optimized routines at runtime, so optimal performance is achieved without the need to rebuild OpenBLAS locally. OpenBLAS has replaced ATLAS in most packages which use an optimized BLAS or LAPACK implementation.
 
@@ -312,7 +312,7 @@ Other notable improvements:
 
 - The nixos and nixpkgs channels were unified, so one _can_ use `nix-env -iA nixos.bash` instead of `nix-env -iA nixos.pkgs.bash`. See [the commit](https://github.com/NixOS/nixpkgs/commit/2cd7c1f198) for details.
 
-- Users running an SSH server who worry about the quality of their `/etc/ssh/moduli` file with respect to the [vulnerabilities discovered in the Diffie-Hellman key exchange](https://stribika.github.io/2015/01/04/secure-secure-shell.html) can now replace OpenSSH\'s default version with one they generated themselves using the new `services.openssh.moduliFile` option.
+- Users running an SSH server who worry about the quality of their `/etc/ssh/moduli` file with respect to the [vulnerabilities discovered in the Diffie-Hellman key exchange](https://stribika.github.io/2015/01/04/secure-secure-shell.html) can now replace OpenSSH's default version with one they generated themselves using the new `services.openssh.moduliFile` option.
 
 - A newly packaged TeX Live 2015 is provided in `pkgs.texlive`, split into 6500 nix packages. For basic user documentation see [the source](https://github.com/NixOS/nixpkgs/blob/release-15.09/pkgs/tools/typesetting/tex/texlive/default.nix#L1). Beware of [an issue](https://github.com/NixOS/nixpkgs/issues/9757) when installing a too large package set. The plan is to deprecate and maybe delete the original TeX packages until the next release.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1603.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1603.section.md
index dce879ec16d0..532a16f937b0 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1603.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1603.section.md
@@ -152,19 +152,19 @@ When upgrading from a previous release, please be aware of the following incompa
   }
   ```
 
-- `s3sync` is removed, as it hasn\'t been developed by upstream for 4 years and only runs with ruby 1.8. For an actively-developer alternative look at `tarsnap` and others.
+- `s3sync` is removed, as it hasn't been developed by upstream for 4 years and only runs with ruby 1.8. For an actively-developer alternative look at `tarsnap` and others.
 
-- `ruby_1_8` has been removed as it\'s not supported from upstream anymore and probably contains security issues.
+- `ruby_1_8` has been removed as it's not supported from upstream anymore and probably contains security issues.
 
 - `tidy-html5` package is removed. Upstream only provided `(lib)tidy5` during development, and now they went back to `(lib)tidy` to work as a drop-in replacement of the original package that has been unmaintained for years. You can (still) use the `html-tidy` package, which got updated to a stable release from this new upstream.
 
 - `extraDeviceOptions` argument is removed from `bumblebee` package. Instead there are now two separate arguments: `extraNvidiaDeviceOptions` and `extraNouveauDeviceOptions` for setting extra X11 options for nvidia and nouveau drivers, respectively.
 
-- The `Ctrl+Alt+Backspace` key combination no longer kills the X server by default. There\'s a new option `services.xserver.enableCtrlAltBackspace` allowing to enable the combination again.
+- The `Ctrl+Alt+Backspace` key combination no longer kills the X server by default. There's a new option `services.xserver.enableCtrlAltBackspace` allowing to enable the combination again.
 
 - `emacsPackagesNg` now contains all packages from the ELPA, MELPA, and MELPA Stable repositories.
 
-- Data directory for Postfix MTA server is moved from `/var/postfix` to `/var/lib/postfix`. Old configurations are migrated automatically. `service.postfix` module has also received many improvements, such as correct directories\' access rights, new `aliasFiles` and `mapFiles` options and more.
+- Data directory for Postfix MTA server is moved from `/var/postfix` to `/var/lib/postfix`. Old configurations are migrated automatically. `service.postfix` module has also received many improvements, such as correct directories' access rights, new `aliasFiles` and `mapFiles` options and more.
 
 - Filesystem options should now be configured as a list of strings, not a comma-separated string. The old style will continue to work, but print a warning, until the 16.09 release. An example of the new style:
 
@@ -180,7 +180,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - CUPS, installed by `services.printing` module, now has its data directory in `/var/lib/cups`. Old configurations from `/etc/cups` are moved there automatically, but there might be problems. Also configuration options `services.printing.cupsdConf` and `services.printing.cupsdFilesConf` were removed because they had been allowing one to override configuration variables required for CUPS to work at all on NixOS. For most use cases, `services.printing.extraConf` and new option `services.printing.extraFilesConf` should be enough; if you encounter a situation when they are not, please file a bug.
 
-  There are also Gutenprint improvements; in particular, a new option `services.printing.gutenprint` is added to enable automatic updating of Gutenprint PPMs; it\'s greatly recommended to enable it instead of adding `gutenprint` to the `drivers` list.
+  There are also Gutenprint improvements; in particular, a new option `services.printing.gutenprint` is added to enable automatic updating of Gutenprint PPMs; it's greatly recommended to enable it instead of adding `gutenprint` to the `drivers` list.
 
 - `services.xserver.vaapiDrivers` has been removed. Use `hardware.opengl.extraPackages{,32}` instead. You can also specify VDPAU drivers there.
 
@@ -202,7 +202,7 @@ When upgrading from a previous release, please be aware of the following incompa
   }
   ```
 
-- `services.udev.extraRules` option now writes rules to `99-local.rules` instead of `10-local.rules`. This makes all the user rules apply after others, so their results wouldn\'t be overriden by anything else.
+- `services.udev.extraRules` option now writes rules to `99-local.rules` instead of `10-local.rules`. This makes all the user rules apply after others, so their results wouldn't be overridden by anything else.
 
 - Large parts of the `services.gitlab` module has been been rewritten. There are new configuration options available. The `stateDir` option was renamned to `statePath` and the `satellitesDir` option was removed. Please review the currently available options.
 
@@ -246,7 +246,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   you should either re-run `nixos-generate-config` or manually replace `"${config.boot.kernelPackages.broadcom_sta}"` by `config.boot.kernelPackages.broadcom_sta` in your `/etc/nixos/hardware-configuration.nix`. More discussion is on [ the github issue](https://github.com/NixOS/nixpkgs/pull/12595).
 
-- The `services.xserver.startGnuPGAgent` option has been removed. GnuPG 2.1.x changed the way the gpg-agent works, and that new approach no longer requires (or even supports) the \"start everything as a child of the agent\" scheme we\'ve implemented in NixOS for older versions. To configure the gpg-agent for your X session, add the following code to `~/.bashrc` or some file that's sourced when your shell is started:
+- The `services.xserver.startGnuPGAgent` option has been removed. GnuPG 2.1.x changed the way the gpg-agent works, and that new approach no longer requires (or even supports) the "start everything as a child of the agent" scheme we've implemented in NixOS for older versions. To configure the gpg-agent for your X session, add the following code to `~/.bashrc` or some file that's sourced when your shell is started:
 
   ```shell
   GPG_TTY=$(tty)
@@ -273,7 +273,7 @@ When upgrading from a previous release, please be aware of the following incompa
       gpg --import ~/.gnupg/secring.gpg
   ```
 
-  The `gpg-agent(1)` man page has more details about this subject, i.e. in the \"EXAMPLES\" section.
+  The `gpg-agent(1)` man page has more details about this subject, i.e. in the "EXAMPLES" section.
 
 Other notable improvements:
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1609.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1609.section.md
index 075f0cf52cd1..ad3478d0ca17 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1609.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1609.section.md
@@ -20,7 +20,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - A large number of packages have been converted to use the multiple outputs feature of Nix to greatly reduce the amount of required disk space, as mentioned above. This may require changes to any custom packages to make them build again; see the relevant chapter in the Nixpkgs manual for more information. (Additional caveat to packagers: some packaging conventions related to multiple-output packages [were changed](https://github.com/NixOS/nixpkgs/pull/14766) late (August 2016) in the release cycle and differ from the initial introduction of multiple outputs.)
 
-- Previous versions of Nixpkgs had support for all versions of the LTS Haskell package set. That support has been dropped. The previously provided `haskell.packages.lts-x_y` package sets still exist in name to aviod breaking user code, but these package sets don\'t actually contain the versions mandated by the corresponding LTS release. Instead, our package set it loosely based on the latest available LTS release, i.e. LTS 7.x at the time of this writing. New releases of NixOS and Nixpkgs will drop those old names entirely. [The motivation for this change](https://nixos.org/nix-dev/2016-June/020585.html) has been discussed at length on the `nix-dev` mailing list and in [Github issue \#14897](https://github.com/NixOS/nixpkgs/issues/14897). Development strategies for Haskell hackers who want to rely on Nix and NixOS have been described in [another nix-dev article](https://nixos.org/nix-dev/2016-June/020642.html).
+- Previous versions of Nixpkgs had support for all versions of the LTS Haskell package set. That support has been dropped. The previously provided `haskell.packages.lts-x_y` package sets still exist in name to avoid breaking user code, but these package sets don't actually contain the versions mandated by the corresponding LTS release. Instead, our package set it loosely based on the latest available LTS release, i.e. LTS 7.x at the time of this writing. New releases of NixOS and Nixpkgs will drop those old names entirely. [The motivation for this change](https://nixos.org/nix-dev/2016-June/020585.html) has been discussed at length on the `nix-dev` mailing list and in [Github issue \#14897](https://github.com/NixOS/nixpkgs/issues/14897). Development strategies for Haskell hackers who want to rely on Nix and NixOS have been described in [another nix-dev article](https://nixos.org/nix-dev/2016-June/020642.html).
 
 - Shell aliases for systemd sub-commands [were dropped](https://github.com/NixOS/nixpkgs/pull/15598): `start`, `stop`, `restart`, `status`.
 
@@ -28,7 +28,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `/var/empty` is now immutable. Activation script runs `chattr +i` to forbid any modifications inside the folder. See [ the pull request](https://github.com/NixOS/nixpkgs/pull/18365) for what bugs this caused.
 
-- Gitlab\'s maintainance script `gitlab-runner` was removed and split up into the more clearer `gitlab-run` and `gitlab-rake` scripts, because `gitlab-runner` is a component of Gitlab CI.
+- Gitlab's maintenance script `gitlab-runner` was removed and split up into the more clearer `gitlab-run` and `gitlab-rake` scripts, because `gitlab-runner` is a component of Gitlab CI.
 
 - `services.xserver.libinput.accelProfile` default changed from `flat` to `adaptive`, as per [ official documentation](https://wayland.freedesktop.org/libinput/doc/latest/group__config.html#gad63796972347f318b180e322e35cee79).
 
@@ -38,7 +38,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `pkgs.linuxPackages.virtualbox` now contains only the kernel modules instead of the VirtualBox user space binaries. If you want to reference the user space binaries, you have to use the new `pkgs.virtualbox` instead.
 
-- `goPackages` was replaced with separated Go applications in appropriate `nixpkgs` categories. Each Go package uses its own dependency set. There\'s also a new `go2nix` tool introduced to generate a Go package definition from its Go source automatically.
+- `goPackages` was replaced with separated Go applications in appropriate `nixpkgs` categories. Each Go package uses its own dependency set. There's also a new `go2nix` tool introduced to generate a Go package definition from its Go source automatically.
 
 - `services.mongodb.extraConfig` configuration format was changed to YAML.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1703.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1703.section.md
index 7f424f2a6ce3..b82c41e28ca3 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1703.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1703.section.md
@@ -8,7 +8,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - This release is based on Glibc 2.25, GCC 5.4.0 and systemd 232. The default Linux kernel is 4.9 and Nix is at 1.11.8.
 
-- The default desktop environment now is KDE\'s Plasma 5. KDE 4 has been removed
+- The default desktop environment now is KDE's Plasma 5. KDE 4 has been removed
 
 - The setuid wrapper functionality now supports setting capabilities.
 
@@ -208,7 +208,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Two lone top-level dict dbs moved into `dictdDBs`. This affects: `dictdWordnet` which is now at `dictdDBs.wordnet` and `dictdWiktionary` which is now at `dictdDBs.wiktionary`
 
-- Parsoid service now uses YAML configuration format. `service.parsoid.interwikis` is now called `service.parsoid.wikis` and is a list of either API URLs or attribute sets as specified in parsoid\'s documentation.
+- Parsoid service now uses YAML configuration format. `service.parsoid.interwikis` is now called `service.parsoid.wikis` and is a list of either API URLs or attribute sets as specified in parsoid's documentation.
 
 - `Ntpd` was replaced by `systemd-timesyncd` as the default service to synchronize system time with a remote NTP server. The old behavior can be restored by setting `services.ntp.enable` to `true`. Upstream time servers for all NTP implementations are now configured using `networking.timeServers`.
 
@@ -260,11 +260,11 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Autoloading connection tracking helpers is now disabled by default. This default was also changed in the Linux kernel and is considered insecure if not configured properly in your firewall. If you need connection tracking helpers (i.e. for active FTP) please enable `networking.firewall.autoLoadConntrackHelpers` and tune `networking.firewall.connectionTrackingModules` to suit your needs.
 
-- `local_recipient_maps` is not set to empty value by Postfix service. It\'s an insecure default as stated by Postfix documentation. Those who want to retain this setting need to set it via `services.postfix.extraConfig`.
+- `local_recipient_maps` is not set to empty value by Postfix service. It's an insecure default as stated by Postfix documentation. Those who want to retain this setting need to set it via `services.postfix.extraConfig`.
 
 - Iputils no longer provide ping6 and traceroute6. The functionality of these tools has been integrated into ping and traceroute respectively. To enforce an address family the new flags `-4` and `-6` have been added. One notable incompatibility is that specifying an interface (for link-local IPv6 for instance) is no longer done with the `-I` flag, but by encoding the interface into the address (`ping fe80::1%eth0`).
 
-- The socket handling of the `services.rmilter` module has been fixed and refactored. As rmilter doesn\'t support binding to more than one socket, the options `bindUnixSockets` and `bindInetSockets` have been replaced by `services.rmilter.bindSocket.*`. The default is still a unix socket in `/run/rmilter/rmilter.sock`. Refer to the options documentation for more information.
+- The socket handling of the `services.rmilter` module has been fixed and refactored. As rmilter doesn't support binding to more than one socket, the options `bindUnixSockets` and `bindInetSockets` have been replaced by `services.rmilter.bindSocket.*`. The default is still a unix socket in `/run/rmilter/rmilter.sock`. Refer to the options documentation for more information.
 
 - The `fetch*` functions no longer support md5, please use sha256 instead.
 
@@ -278,7 +278,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Module type system have a new extensible option types feature that allow to extend certain types, such as enum, through multiple option declarations of the same option across multiple modules.
 
-- `jre` now defaults to GTK UI by default. This improves visual consistency and makes Java follow system font style, improving the situation on HighDPI displays. This has a cost of increased closure size; for server and other headless workloads it\'s recommended to use `jre_headless`.
+- `jre` now defaults to GTK UI by default. This improves visual consistency and makes Java follow system font style, improving the situation on HighDPI displays. This has a cost of increased closure size; for server and other headless workloads it's recommended to use `jre_headless`.
 
 - Python 2.6 interpreter and package set have been removed.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1709.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1709.section.md
index e5af22721b0c..f2ff8b46b83f 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1709.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1709.section.md
@@ -8,7 +8,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The user handling now keeps track of deallocated UIDs/GIDs. When a user or group is revived, this allows it to be allocated the UID/GID it had before. A consequence is that UIDs and GIDs are no longer reused.
 
-- The module option `services.xserver.xrandrHeads` now causes the first head specified in this list to be set as the primary head. Apart from that, it\'s now possible to also set additional options by using an attribute set, for example:
+- The module option `services.xserver.xrandrHeads` now causes the first head specified in this list to be set as the primary head. Apart from that, it's now possible to also set additional options by using an attribute set, for example:
 
   ```nix
   { services.xserver.xrandrHeads = [
@@ -208,7 +208,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   - The `mysql` default `dataDir` has changed from `/var/mysql` to `/var/lib/mysql`.
 
-  - Radicale\'s default package has changed from 1.x to 2.x. Instructions to migrate can be found [ here ](http://radicale.org/1to2/). It is also possible to use the newer version by setting the `package` to `radicale2`, which is done automatically when `stateVersion` is 17.09 or higher. The `extraArgs` option has been added to allow passing the data migration arguments specified in the instructions; see the `radicale.nix` NixOS test for an example migration.
+  - Radicale's default package has changed from 1.x to 2.x. Instructions to migrate can be found [ here ](http://radicale.org/1to2/). It is also possible to use the newer version by setting the `package` to `radicale2`, which is done automatically when `stateVersion` is 17.09 or higher. The `extraArgs` option has been added to allow passing the data migration arguments specified in the instructions; see the `radicale.nix` NixOS test for an example migration.
 
 - The `aiccu` package was removed. This is due to SixXS [ sunsetting](https://www.sixxs.net/main/) its IPv6 tunnel.
 
@@ -216,9 +216,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Top-level `idea` package collection was renamed. All JetBrains IDEs are now at `jetbrains`.
 
-- `flexget`\'s state database cannot be upgraded to its new internal format, requiring removal of any existing `db-config.sqlite` which will be automatically recreated.
+- `flexget`'s state database cannot be upgraded to its new internal format, requiring removal of any existing `db-config.sqlite` which will be automatically recreated.
 
-- The `ipfs` service now doesn\'t ignore the `dataDir` option anymore. If you\'ve ever set this option to anything other than the default you\'ll have to either unset it (so the default gets used) or migrate the old data manually with
+- The `ipfs` service now doesn't ignore the `dataDir` option anymore. If you've ever set this option to anything other than the default you'll have to either unset it (so the default gets used) or migrate the old data manually with
 
   ```ShellSession
   dataDir=<valueOfDataDir>
@@ -236,15 +236,15 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `wvdial` package and module were removed. This is due to the project being dead and not building with openssl 1.1.
 
-- `cc-wrapper`\'s setup-hook now exports a number of environment variables corresponding to binutils binaries, (e.g. `LD`, `STRIP`, `RANLIB`, etc). This is done to prevent packages\' build systems guessing, which is harder to predict, especially when cross-compiling. However, some packages have broken due to this---their build systems either not supporting, or claiming to support without adequate testing, taking such environment variables as parameters.
+- `cc-wrapper`'s setup-hook now exports a number of environment variables corresponding to binutils binaries, (e.g. `LD`, `STRIP`, `RANLIB`, etc). This is done to prevent packages' build systems guessing, which is harder to predict, especially when cross-compiling. However, some packages have broken due to this---their build systems either not supporting, or claiming to support without adequate testing, taking such environment variables as parameters.
 
-- `services.firefox.syncserver` now runs by default as a non-root user. To accomodate this change, the default sqlite database location has also been changed. Migration should work automatically. Refer to the description of the options for more details.
+- `services.firefox.syncserver` now runs by default as a non-root user. To accommodate this change, the default sqlite database location has also been changed. Migration should work automatically. Refer to the description of the options for more details.
 
 - The `compiz` window manager and package was removed. The system support had been broken for several years.
 
 - Touchpad support should now be enabled through `libinput` as `synaptics` is now deprecated. See the option `services.xserver.libinput.enable`.
 
-- grsecurity/PaX support has been dropped, following upstream\'s decision to cease free support. See [ upstream\'s announcement](https://grsecurity.net/passing_the_baton.php) for more information. No complete replacement for grsecurity/PaX is available presently.
+- grsecurity/PaX support has been dropped, following upstream's decision to cease free support. See [ upstream's announcement](https://grsecurity.net/passing_the_baton.php) for more information. No complete replacement for grsecurity/PaX is available presently.
 
 - `services.mysql` now has declarative configuration of databases and users with the `ensureDatabases` and `ensureUsers` options.
 
@@ -275,7 +275,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   You can check that backups still work by running `systemctl start mysql-backup` then `systemctl status mysql-backup`.
 
-- Templated systemd services e.g `container@name` are now handled currectly when switching to a new configuration, resulting in them being reloaded.
+- Templated systemd services e.g `container@name` are now handled correctly when switching to a new configuration, resulting in them being reloaded.
 
 - Steam: the `newStdcpp` parameter was removed and should not be needed anymore.
 
@@ -283,9 +283,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 ## Other Notable Changes {#sec-release-17.09-notable-changes}
 
-- Modules can now be disabled by using [ disabledModules](https://nixos.org/nixpkgs/manual/#sec-replace-modules), allowing another to take it\'s place. This can be used to import a set of modules from another channel while keeping the rest of the system on a stable release.
+- Modules can now be disabled by using [ disabledModules](https://nixos.org/nixpkgs/manual/#sec-replace-modules), allowing another to take it's place. This can be used to import a set of modules from another channel while keeping the rest of the system on a stable release.
 
-- Updated to FreeType 2.7.1, including a new TrueType engine. The new engine replaces the Infinality engine which was the default in NixOS. The default font rendering settings are now provided by fontconfig-penultimate, replacing fontconfig-ultimate; the new defaults are less invasive and provide rendering that is more consistent with other systems and hopefully with each font designer\'s intent. Some system-wide configuration has been removed from the Fontconfig NixOS module where user Fontconfig settings are available.
+- Updated to FreeType 2.7.1, including a new TrueType engine. The new engine replaces the Infinality engine which was the default in NixOS. The default font rendering settings are now provided by fontconfig-penultimate, replacing fontconfig-ultimate; the new defaults are less invasive and provide rendering that is more consistent with other systems and hopefully with each font designer's intent. Some system-wide configuration has been removed from the Fontconfig NixOS module where user Fontconfig settings are available.
 
 - ZFS/SPL have been updated to 0.7.0, `zfsUnstable, splUnstable` have therefore been removed.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1803.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1803.section.md
index c5146015d449..ecf5757bae6c 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1803.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1803.section.md
@@ -6,7 +6,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - End of support is planned for end of October 2018, handing over to 18.09.
 
-- Platform support: x86_64-linux and x86_64-darwin since release time (the latter isn\'t NixOS, really). Binaries for aarch64-linux are available, but no channel exists yet, as it\'s waiting for some test fixes, etc.
+- Platform support: x86_64-linux and x86_64-darwin since release time (the latter isn't NixOS, really). Binaries for aarch64-linux are available, but no channel exists yet, as it's waiting for some test fixes, etc.
 
 - Nix now defaults to 2.0; see its [release notes](https://nixos.org/nix/manual/#ssec-relnotes-2.0).
 
@@ -174,9 +174,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `openssh` package now includes Kerberos support by default; the `openssh_with_kerberos` package is now a deprecated alias. If you do not want Kerberos support, you can do `openssh.override { withKerberos = false; }`. Note, this also applies to the `openssh_hpn` package.
 
-- `cc-wrapper` has been split in two; there is now also a `bintools-wrapper`. The most commonly used files in `nix-support` are now split between the two wrappers. Some commonly used ones, like `nix-support/dynamic-linker`, are duplicated for backwards compatability, even though they rightly belong only in `bintools-wrapper`. Other more obscure ones are just moved.
+- `cc-wrapper` has been split in two; there is now also a `bintools-wrapper`. The most commonly used files in `nix-support` are now split between the two wrappers. Some commonly used ones, like `nix-support/dynamic-linker`, are duplicated for backwards compatibility, even though they rightly belong only in `bintools-wrapper`. Other more obscure ones are just moved.
 
-- The propagation logic has been changed. The new logic, along with new types of dependencies that go with, is thoroughly documented in the \"Specifying dependencies\" section of the \"Standard Environment\" chapter of the nixpkgs manual. The old logic isn\'t but is easy to describe: dependencies were propagated as the same type of dependency no matter what. In practice, that means that many `propagatedNativeBuildInputs` should instead be `propagatedBuildInputs`. Thankfully, that was and is the least used type of dependency. Also, it means that some `propagatedBuildInputs` should instead be `depsTargetTargetPropagated`. Other types dependencies should be unaffected.
+- The propagation logic has been changed. The new logic, along with new types of dependencies that go with, is thoroughly documented in the "Specifying dependencies" section of the "Standard Environment" chapter of the nixpkgs manual. The old logic isn't but is easy to describe: dependencies were propagated as the same type of dependency no matter what. In practice, that means that many `propagatedNativeBuildInputs` should instead be `propagatedBuildInputs`. Thankfully, that was and is the least used type of dependency. Also, it means that some `propagatedBuildInputs` should instead be `depsTargetTargetPropagated`. Other types dependencies should be unaffected.
 
 - `lib.addPassthru drv passthru` is removed. Use `lib.extendDerivation true passthru drv` instead.
 
@@ -184,7 +184,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `hardware.amdHybridGraphics.disable` option was removed for lack of a maintainer. If you still need this module, you may wish to include a copy of it from an older version of nixos in your imports.
 
-- The merging of config options for `services.postfix.config` was buggy. Previously, if other options in the Postfix module like `services.postfix.useSrs` were set and the user set config options that were also set by such options, the resulting config wouldn\'t include all options that were needed. They are now merged correctly. If config options need to be overridden, `lib.mkForce` or `lib.mkOverride` can be used.
+- The merging of config options for `services.postfix.config` was buggy. Previously, if other options in the Postfix module like `services.postfix.useSrs` were set and the user set config options that were also set by such options, the resulting config wouldn't include all options that were needed. They are now merged correctly. If config options need to be overridden, `lib.mkForce` or `lib.mkOverride` can be used.
 
 - The following changes apply if the `stateVersion` is changed to 18.03 or higher. For `stateVersion = "17.09"` or lower the old behavior is preserved.
 
@@ -204,7 +204,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   - The data directory `/var/lib/piwik` was renamed to `/var/lib/matomo`. All files will be moved automatically on first startup, but you might need to adjust your backup scripts.
 
-  - The default `serverName` for the nginx configuration changed from `piwik.${config.networking.hostName}` to `matomo.${config.networking.hostName}.${config.networking.domain}` if `config.networking.domain` is set, `matomo.${config.networking.hostName}` if it is not set. If you change your `serverName`, remember you\'ll need to update the `trustedHosts[]` array in `/var/lib/matomo/config/config.ini.php` as well.
+  - The default `serverName` for the nginx configuration changed from `piwik.${config.networking.hostName}` to `matomo.${config.networking.hostName}.${config.networking.domain}` if `config.networking.domain` is set, `matomo.${config.networking.hostName}` if it is not set. If you change your `serverName`, remember you'll need to update the `trustedHosts[]` array in `/var/lib/matomo/config/config.ini.php` as well.
 
   - The `piwik` user was renamed to `matomo`. The service will adjust ownership automatically for files in the data directory. If you use unix socket authentication, remember to give the new `matomo` user access to the database and to change the `username` to `matomo` in the `[database]` section of `/var/lib/matomo/config/config.ini.php`.
 
@@ -250,7 +250,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The option `services.logstash.listenAddress` is now `127.0.0.1` by default. Previously the default behaviour was to listen on all interfaces.
 
-- `services.btrfs.autoScrub` has been added, to periodically check btrfs filesystems for data corruption. If there\'s a correct copy available, it will automatically repair corrupted blocks.
+- `services.btrfs.autoScrub` has been added, to periodically check btrfs filesystems for data corruption. If there's a correct copy available, it will automatically repair corrupted blocks.
 
 - `displayManager.lightdm.greeters.gtk.clock-format.` has been added, the clock format string (as expected by strftime, e.g. `%H:%M`) to use with the lightdm gtk greeter panel.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1809.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1809.section.md
index 3443db37c97e..71afc71d5a89 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1809.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1809.section.md
@@ -204,11 +204,11 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `clementine` package points now to the free derivation. `clementineFree` is removed now and `clementineUnfree` points to the package which is bundled with the unfree `libspotify` package.
 
-- The `netcat` package is now taken directly from OpenBSD\'s `libressl`, instead of relying on Debian\'s fork. The new version should be very close to the old version, but there are some minor differences. Importantly, flags like -b, -q, -C, and -Z are no longer accepted by the nc command.
+- The `netcat` package is now taken directly from OpenBSD's `libressl`, instead of relying on Debian's fork. The new version should be very close to the old version, but there are some minor differences. Importantly, flags like -b, -q, -C, and -Z are no longer accepted by the nc command.
 
-- The `services.docker-registry.extraConfig` object doesn\'t contain environment variables anymore. Instead it needs to provide an object structure that can be mapped onto the YAML configuration defined in [the `docker/distribution` docs](https://github.com/docker/distribution/blob/v2.6.2/docs/configuration.md).
+- The `services.docker-registry.extraConfig` object doesn't contain environment variables anymore. Instead it needs to provide an object structure that can be mapped onto the YAML configuration defined in [the `docker/distribution` docs](https://github.com/docker/distribution/blob/v2.6.2/docs/configuration.md).
 
-- `gnucash` has changed from version 2.4 to 3.x. If you\'ve been using `gnucash` (version 2.4) instead of `gnucash26` (version 2.6) you must open your Gnucash data file(s) with `gnucash26` and then save them to upgrade the file format. Then you may use your data file(s) with Gnucash 3.x. See the upgrade [documentation](https://wiki.gnucash.org/wiki/FAQ#Using_Different_Versions.2C_Up_And_Downgrade). Gnucash 2.4 is still available under the attribute `gnucash24`.
+- `gnucash` has changed from version 2.4 to 3.x. If you've been using `gnucash` (version 2.4) instead of `gnucash26` (version 2.6) you must open your Gnucash data file(s) with `gnucash26` and then save them to upgrade the file format. Then you may use your data file(s) with Gnucash 3.x. See the upgrade [documentation](https://wiki.gnucash.org/wiki/FAQ#Using_Different_Versions.2C_Up_And_Downgrade). Gnucash 2.4 is still available under the attribute `gnucash24`.
 
 - `services.munge` now runs as user (and group) `munge` instead of root. Make sure the key file is accessible to the daemon.
 
@@ -315,7 +315,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The Kubernetes Dashboard now has only minimal RBAC permissions by default. If dashboard cluster-admin rights are desired, set `services.kubernetes.addons.dashboard.rbac.clusterAdmin` to true. On existing clusters, in order for the revocation of privileges to take effect, the current ClusterRoleBinding for kubernetes-dashboard must be manually removed: `kubectl delete clusterrolebinding kubernetes-dashboard`
 
-- The `programs.screen` module provides allows to configure `/etc/screenrc`, however the module behaved fairly counterintuitive as the config exists, but the package wasn\'t available. Since 18.09 `pkgs.screen` will be added to `environment.systemPackages`.
+- The `programs.screen` module provides allows to configure `/etc/screenrc`, however the module behaved fairly counterintuitive as the config exists, but the package wasn't available. Since 18.09 `pkgs.screen` will be added to `environment.systemPackages`.
 
 - The module `services.networking.hostapd` now uses WPA2 by default.
 
@@ -327,6 +327,6 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The default display manager is now LightDM. To use SLiM set `services.xserver.displayManager.slim.enable` to `true`.
 
-- NixOS option descriptions are now automatically broken up into individual paragraphs if the text contains two consecutive newlines, so it\'s no longer necessary to use `</para><para>` to start a new paragraph.
+- NixOS option descriptions are now automatically broken up into individual paragraphs if the text contains two consecutive newlines, so it's no longer necessary to use `</para><para>` to start a new paragraph.
 
 - Top-level `buildPlatform`, `hostPlatform`, and `targetPlatform` in Nixpkgs are deprecated. Please use their equivalents in `stdenv` instead: `stdenv.buildPlatform`, `stdenv.hostPlatform`, and `stdenv.targetPlatform`.
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1903.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1903.section.md
index 7637a70c1bf8..e83a3911a5cf 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1903.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1903.section.md
@@ -11,11 +11,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 - Added the Pantheon desktop environment. It can be enabled through `services.xserver.desktopManager.pantheon.enable`.
 
   ::: {.note}
-  By default, `services.xserver.desktopManager.pantheon` enables LightDM as a display manager, as pantheon\'s screen locking implementation relies on it.
-  Because of that it is recommended to leave LightDM enabled. If you\'d like to disable it anyway, set `services.xserver.displayManager.lightdm.enable` to `false` and enable your preferred display manager.
+  By default, `services.xserver.desktopManager.pantheon` enables LightDM as a display manager, as pantheon's screen locking implementation relies on it.
+  Because of that it is recommended to leave LightDM enabled. If you'd like to disable it anyway, set `services.xserver.displayManager.lightdm.enable` to `false` and enable your preferred display manager.
   :::
 
-  Also note that Pantheon\'s LightDM greeter is not enabled by default, because it has numerous issues in NixOS and isn\'t optimal for use here yet.
+  Also note that Pantheon's LightDM greeter is not enabled by default, because it has numerous issues in NixOS and isn't optimal for use here yet.
 
 - A major refactoring of the Kubernetes module has been completed. Refactorings primarily focus on decoupling components and enhancing security. Two-way TLS and RBAC has been enabled by default for all components, which slightly changes the way the module is configured. See: [](#sec-kubernetes) for details.
 
@@ -57,7 +57,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The Syncthing state and configuration data has been moved from `services.syncthing.dataDir` to the newly defined `services.syncthing.configDir`, which default to `/var/lib/syncthing/.config/syncthing`. This change makes possible to share synced directories using ACLs without Syncthing resetting the permission on every start.
 
-- The `ntp` module now has sane default restrictions. If you\'re relying on the previous defaults, which permitted all queries and commands from all firewall-permitted sources, you can set `services.ntp.restrictDefault` and `services.ntp.restrictSource` to `[]`.
+- The `ntp` module now has sane default restrictions. If you're relying on the previous defaults, which permitted all queries and commands from all firewall-permitted sources, you can set `services.ntp.restrictDefault` and `services.ntp.restrictSource` to `[]`.
 
 - Package `rabbitmq_server` is renamed to `rabbitmq-server`.
 
@@ -73,7 +73,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - OpenSMTPD has been upgraded to version 6.4.0p1. This release makes backwards-incompatible changes to the configuration file format. See `man smtpd.conf` for more information on the new file format.
 
-- The versioned `postgresql` have been renamed to use underscore number seperators. For example, `postgresql96` has been renamed to `postgresql_9_6`.
+- The versioned `postgresql` have been renamed to use underscore number separators. For example, `postgresql96` has been renamed to `postgresql_9_6`.
 
 - Package `consul-ui` and passthrough `consul.ui` have been removed. The package `consul` now uses upstream releases that vendor the UI into the binary. See [\#48714](https://github.com/NixOS/nixpkgs/pull/48714#issuecomment-433454834) for details.
 
@@ -81,7 +81,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   The slurmctld now runs as user `slurm` instead of `root`. If you want to keep slurmctld running as `root`, set `services.slurm.user = root`.
 
-  The options `services.slurm.nodeName` and `services.slurm.partitionName` are now sets of strings to correctly reflect that fact that each of these options can occour more than once in the configuration.
+  The options `services.slurm.nodeName` and `services.slurm.partitionName` are now sets of strings to correctly reflect that fact that each of these options can occur more than once in the configuration.
 
 - The `solr` package has been upgraded from 4.10.3 to 7.5.0 and has undergone some major changes. The `services.solr` module has been updated to reflect these changes. Please review http://lucene.apache.org/solr/ carefully before upgrading.
 
@@ -89,9 +89,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The option `services.xserver.displayManager.job.logToFile` which was previously set to `true` when using the display managers `lightdm`, `sddm` or `xpra` has been reset to the default value (`false`).
 
-- Network interface indiscriminate NixOS firewall options (`networking.firewall.allow*`) are now preserved when also setting interface specific rules such as `networking.firewall.interfaces.en0.allow*`. These rules continue to use the pseudo device \"default\" (`networking.firewall.interfaces.default.*`), and assigning to this pseudo device will override the (`networking.firewall.allow*`) options.
+- Network interface indiscriminate NixOS firewall options (`networking.firewall.allow*`) are now preserved when also setting interface specific rules such as `networking.firewall.interfaces.en0.allow*`. These rules continue to use the pseudo device "default" (`networking.firewall.interfaces.default.*`), and assigning to this pseudo device will override the (`networking.firewall.allow*`) options.
 
-- The `nscd` service now disables all caching of `passwd` and `group` databases by default. This was interferring with the correct functioning of the `libnss_systemd.so` module which is used by `systemd` to manage uids and usernames in the presence of `DynamicUser=` in systemd services. This was already the default behaviour in presence of `services.sssd.enable = true` because nscd caching would interfere with `sssd` in unpredictable ways as well. Because we\'re using nscd not for caching, but for convincing glibc to find NSS modules in the nix store instead of an absolute path, we have decided to disable caching globally now, as it\'s usually not the behaviour the user wants and can lead to surprising behaviour. Furthermore, negative caching of host lookups is also disabled now by default. This should fix the issue of dns lookups failing in the presence of an unreliable network.
+- The `nscd` service now disables all caching of `passwd` and `group` databases by default. This was interfering with the correct functioning of the `libnss_systemd.so` module which is used by `systemd` to manage uids and usernames in the presence of `DynamicUser=` in systemd services. This was already the default behaviour in presence of `services.sssd.enable = true` because nscd caching would interfere with `sssd` in unpredictable ways as well. Because we're using nscd not for caching, but for convincing glibc to find NSS modules in the nix store instead of an absolute path, we have decided to disable caching globally now, as it's usually not the behaviour the user wants and can lead to surprising behaviour. Furthermore, negative caching of host lookups is also disabled now by default. This should fix the issue of dns lookups failing in the presence of an unreliable network.
 
   If the old behaviour is desired, this can be restored by setting the `services.nscd.config` option with the desired caching parameters.
 
@@ -135,9 +135,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - GitLab Shell previously used the nix store paths for the `gitlab-shell` command in its `authorized_keys` file, which might stop working after garbage collection. To circumvent that, we regenerated that file on each startup. As `gitlab-shell` has now been changed to use `/var/run/current-system/sw/bin/gitlab-shell`, this is not necessary anymore, but there might be leftover lines with a nix store path. Regenerate the `authorized_keys` file via `sudo -u git -H gitlab-rake gitlab:shell:setup` in that case.
 
-- The `pam_unix` account module is now loaded with its control field set to `required` instead of `sufficient`, so that later PAM account modules that might do more extensive checks are being executed. Previously, the whole account module verification was exited prematurely in case a nss module provided the account name to `pam_unix`. The LDAP and SSSD NixOS modules already add their NSS modules when enabled. In case your setup breaks due to some later PAM account module previosuly shadowed, or failing NSS lookups, please file a bug. You can get back the old behaviour by manually setting `security.pam.services.<name?>.text`.
+- The `pam_unix` account module is now loaded with its control field set to `required` instead of `sufficient`, so that later PAM account modules that might do more extensive checks are being executed. Previously, the whole account module verification was exited prematurely in case a nss module provided the account name to `pam_unix`. The LDAP and SSSD NixOS modules already add their NSS modules when enabled. In case your setup breaks due to some later PAM account module previously shadowed, or failing NSS lookups, please file a bug. You can get back the old behaviour by manually setting `security.pam.services.<name?>.text`.
 
-- The `pam_unix` password module is now loaded with its control field set to `sufficient` instead of `required`, so that password managed only by later PAM password modules are being executed. Previously, for example, changing an LDAP account\'s password through PAM was not possible: the whole password module verification was exited prematurely by `pam_unix`, preventing `pam_ldap` to manage the password as it should.
+- The `pam_unix` password module is now loaded with its control field set to `sufficient` instead of `required`, so that password managed only by later PAM password modules are being executed. Previously, for example, changing an LDAP account's password through PAM was not possible: the whole password module verification was exited prematurely by `pam_unix`, preventing `pam_ldap` to manage the password as it should.
 
 - `fish` has been upgraded to 3.0. It comes with a number of improvements and backwards incompatible changes. See the `fish` [release notes](https://github.com/fish-shell/fish-shell/releases/tag/3.0.0) for more information.
 
@@ -145,7 +145,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - NixOS module system type `types.optionSet` and `lib.mkOption` argument `options` are deprecated. Use `types.submodule` instead. ([\#54637](https://github.com/NixOS/nixpkgs/pull/54637))
 
-- `matrix-synapse` has been updated to version 0.99. It will [no longer generate a self-signed certificate on first launch](https://github.com/matrix-org/synapse/pull/4509) and will be [the last version to accept self-signed certificates](https://matrix.org/blog/2019/02/05/synapse-0-99-0/). As such, it is now recommended to use a proper certificate verified by a root CA (for example Let\'s Encrypt). The new [manual chapter on Matrix](#module-services-matrix) contains a working example of using nginx as a reverse proxy in front of `matrix-synapse`, using Let\'s Encrypt certificates.
+- `matrix-synapse` has been updated to version 0.99. It will [no longer generate a self-signed certificate on first launch](https://github.com/matrix-org/synapse/pull/4509) and will be [the last version to accept self-signed certificates](https://matrix.org/blog/2019/02/05/synapse-0-99-0/). As such, it is now recommended to use a proper certificate verified by a root CA (for example Let's Encrypt). The new [manual chapter on Matrix](#module-services-matrix) contains a working example of using nginx as a reverse proxy in front of `matrix-synapse`, using Let's Encrypt certificates.
 
 - `mailutils` now works by default when `sendmail` is not in a setuid wrapper. As a consequence, the `sendmailPath` argument, having lost its main use, has been removed.
 
@@ -191,7 +191,7 @@ When upgrading from a previous release, please be aware of the following incompa
   With this change application specific volumes are relative to the master volume which can be adjusted independently, whereas before they were absolute; meaning that in effect, it scaled the device-volume with the volume of the loudest application.
   :::
 
-- The [`ndppd`](https://github.com/DanielAdolfsson/ndppd) module now supports [all config options](options.html#opt-services.ndppd.enable) provided by the current upstream version as service options. Additionally the `ndppd` package doesn\'t contain the systemd unit configuration from upstream anymore, the unit is completely configured by the NixOS module now.
+- The [`ndppd`](https://github.com/DanielAdolfsson/ndppd) module now supports [all config options](options.html#opt-services.ndppd.enable) provided by the current upstream version as service options. Additionally the `ndppd` package doesn't contain the systemd unit configuration from upstream anymore, the unit is completely configured by the NixOS module now.
 
 - New installs of NixOS will default to the Redmine 4.x series unless otherwise specified in `services.redmine.package` while existing installs of NixOS will default to the Redmine 3.x series.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-1909.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-1909.section.md
index 572f1bf5a255..22cef05d4fa7 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-1909.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-1909.section.md
@@ -34,7 +34,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The installer now uses a less privileged `nixos` user whereas before we logged in as root. To gain root privileges use `sudo -i` without a password.
 
-- We\'ve updated to Xfce 4.14, which brings a new module `services.xserver.desktopManager.xfce4-14`. If you\'d like to upgrade, please switch from the `services.xserver.desktopManager.xfce` module as it will be deprecated in a future release. They\'re incompatibilities with the current Xfce module; it doesn\'t support `thunarPlugins` and it isn\'t recommended to use `services.xserver.desktopManager.xfce` and `services.xserver.desktopManager.xfce4-14` simultaneously or to downgrade from Xfce 4.14 after upgrading.
+- We've updated to Xfce 4.14, which brings a new module `services.xserver.desktopManager.xfce4-14`. If you'd like to upgrade, please switch from the `services.xserver.desktopManager.xfce` module as it will be deprecated in a future release. They're incompatibilities with the current Xfce module; it doesn't support `thunarPlugins` and it isn't recommended to use `services.xserver.desktopManager.xfce` and `services.xserver.desktopManager.xfce4-14` simultaneously or to downgrade from Xfce 4.14 after upgrading.
 
 - The GNOME 3 desktop manager module sports an interface to enable/disable core services, applications, and optional GNOME packages like games.
 
@@ -46,9 +46,9 @@ In addition to numerous new and upgraded packages, this release has the followin
 
   - `services.gnome3.games.enable`
 
-  With these options we hope to give users finer grained control over their systems. Prior to this change you\'d either have to manually disable options or use `environment.gnome3.excludePackages` which only excluded the optional applications. `environment.gnome3.excludePackages` is now unguarded, it can exclude any package installed with `environment.systemPackages` in the GNOME 3 module.
+  With these options we hope to give users finer grained control over their systems. Prior to this change you'd either have to manually disable options or use `environment.gnome3.excludePackages` which only excluded the optional applications. `environment.gnome3.excludePackages` is now unguarded, it can exclude any package installed with `environment.systemPackages` in the GNOME 3 module.
 
-- Orthogonal to the previous changes to the GNOME 3 desktop manager module, we\'ve updated all default services and applications to match as close as possible to a default reference GNOME 3 experience.
+- Orthogonal to the previous changes to the GNOME 3 desktop manager module, we've updated all default services and applications to match as close as possible to a default reference GNOME 3 experience.
 
   **The following changes were enacted in `services.gnome3.core-utilities.enable`**
 
@@ -104,7 +104,7 @@ The following new services were added since the last release:
 
   - `services.xserver.desktopManager.pantheon`
 
-  - `services.xserver.desktopManager.mate` Note Mate uses `programs.system-config-printer` as it doesn\'t use it as a service, but its graphical interface directly.
+  - `services.xserver.desktopManager.mate` Note Mate uses `programs.system-config-printer` as it doesn't use it as a service, but its graphical interface directly.
 
 - [services.blueman.enable](options.html#opt-services.blueman.enable) has been added. If you previously had blueman installed via `environment.systemPackages` please migrate to using the NixOS module, as this would result in an insufficiently configured blueman.
 
@@ -118,11 +118,11 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - PostgreSQL 9.4 is scheduled EOL during the 19.09 life cycle and has been removed.
 
-- The options `services.prometheus.alertmanager.user` and `services.prometheus.alertmanager.group` have been removed because the alertmanager service is now using systemd\'s [ DynamicUser mechanism](http://0pointer.net/blog/dynamic-users-with-systemd.html) which obviates these options.
+- The options `services.prometheus.alertmanager.user` and `services.prometheus.alertmanager.group` have been removed because the alertmanager service is now using systemd's [ DynamicUser mechanism](http://0pointer.net/blog/dynamic-users-with-systemd.html) which obviates these options.
 
 - The NetworkManager systemd unit was renamed back from network-manager.service to NetworkManager.service for better compatibility with other applications expecting this name. The same applies to ModemManager where modem-manager.service is now called ModemManager.service again.
 
-- The `services.nzbget.configFile` and `services.nzbget.openFirewall` options were removed as they are managed internally by the nzbget. The `services.nzbget.dataDir` option hadn\'t actually been used by the module for some time and so was removed as cleanup.
+- The `services.nzbget.configFile` and `services.nzbget.openFirewall` options were removed as they are managed internally by the nzbget. The `services.nzbget.dataDir` option hadn't actually been used by the module for some time and so was removed as cleanup.
 
 - The `services.mysql.pidDir` option was removed, as it was only used by the wordpress apache-httpd service to wait for mysql to have started up. This can be accomplished by either describing a dependency on mysql.service (preferred) or waiting for the (hardcoded) `/run/mysqld/mysql.sock` file to appear.
 
@@ -148,19 +148,19 @@ When upgrading from a previous release, please be aware of the following incompa
 
   A new knob named `nixops.enableDeprecatedAutoLuks` has been introduced to disable the eval failure and to acknowledge the notice was received and read. If you plan on using the feature please note that it might break with subsequent updates.
 
-  Make sure you set the `_netdev` option for each of the file systems referring to block devices provided by the autoLuks module. Not doing this might render the system in a state where it doesn\'t boot anymore.
+  Make sure you set the `_netdev` option for each of the file systems referring to block devices provided by the autoLuks module. Not doing this might render the system in a state where it doesn't boot anymore.
 
   If you are actively using the `autoLuks` module please let us know in [issue \#62211](https://github.com/NixOS/nixpkgs/issues/62211).
 
 - The setopt declarations will be evaluated at the end of `/etc/zshrc`, so any code in [programs.zsh.interactiveShellInit](options.html#opt-programs.zsh.interactiveShellInit), [programs.zsh.loginShellInit](options.html#opt-programs.zsh.loginShellInit) and [programs.zsh.promptInit](options.html#opt-programs.zsh.promptInit) may break if it relies on those options being set.
 
-- The `prometheus-nginx-exporter` package now uses the offical exporter provided by NGINX Inc. Its metrics are differently structured and are incompatible to the old ones. For information about the metrics, have a look at the [official repo](https://github.com/nginxinc/nginx-prometheus-exporter).
+- The `prometheus-nginx-exporter` package now uses the official exporter provided by NGINX Inc. Its metrics are differently structured and are incompatible to the old ones. For information about the metrics, have a look at the [official repo](https://github.com/nginxinc/nginx-prometheus-exporter).
 
 - The `shibboleth-sp` package has been updated to version 3. It is largely backward compatible, for further information refer to the [release notes](https://wiki.shibboleth.net/confluence/display/SP3/ReleaseNotes) and [upgrade guide](https://wiki.shibboleth.net/confluence/display/SP3/UpgradingFromV2).
 
   Nodejs 8 is scheduled EOL under the lifetime of 19.09 and has been dropped.
 
-- By default, prometheus exporters are now run with `DynamicUser` enabled. Exporters that need a real user, now run under a seperate user and group which follow the pattern `<exporter-name>-exporter`, instead of the previous default `nobody` and `nogroup`. Only some exporters are affected by the latter, namely the exporters `dovecot`, `node`, `postfix` and `varnish`.
+- By default, prometheus exporters are now run with `DynamicUser` enabled. Exporters that need a real user, now run under a separate user and group which follow the pattern `<exporter-name>-exporter`, instead of the previous default `nobody` and `nogroup`. Only some exporters are affected by the latter, namely the exporters `dovecot`, `node`, `postfix` and `varnish`.
 
 - The `ibus-qt` package is not installed by default anymore when [i18n.inputMethod.enabled](options.html#opt-i18n.inputMethod.enabled) is set to `ibus`. If IBus support in Qt 4.x applications is required, add the `ibus-qt` package to your [environment.systemPackages](options.html#opt-environment.systemPackages) manually.
 
@@ -194,15 +194,15 @@ When upgrading from a previous release, please be aware of the following incompa
 
   `security.acme.preDelay` and `security.acme.activationDelay` options have been removed. To execute a service before certificates are provisioned or renewed add a `RequiredBy=acme-${cert}.service` to any service.
 
-  Furthermore, the acme module will not automatically add a dependency on `lighttpd.service` anymore. If you are using certficates provided by letsencrypt for lighttpd, then you should depend on the certificate service `acme-${cert}.service>` manually.
+  Furthermore, the acme module will not automatically add a dependency on `lighttpd.service` anymore. If you are using certificates provided by letsencrypt for lighttpd, then you should depend on the certificate service `acme-${cert}.service>` manually.
 
-  For nginx, the dependencies are still automatically managed when `services.nginx.virtualhosts.<name>.enableACME` is enabled just like before. What changed is that nginx now directly depends on the specific certificates that it needs, instead of depending on the catch-all `acme-certificates.target`. This target unit was also removed from the codebase. This will mean nginx will no longer depend on certificates it isn\'t explicitly managing and fixes a bug with certificate renewal ordering racing with nginx restarting which could lead to nginx getting in a broken state as described at [NixOS/nixpkgs\#60180](https://github.com/NixOS/nixpkgs/issues/60180).
+  For nginx, the dependencies are still automatically managed when `services.nginx.virtualhosts.<name>.enableACME` is enabled just like before. What changed is that nginx now directly depends on the specific certificates that it needs, instead of depending on the catch-all `acme-certificates.target`. This target unit was also removed from the codebase. This will mean nginx will no longer depend on certificates it isn't explicitly managing and fixes a bug with certificate renewal ordering racing with nginx restarting which could lead to nginx getting in a broken state as described at [NixOS/nixpkgs\#60180](https://github.com/NixOS/nixpkgs/issues/60180).
 
 - The old deprecated `emacs` package sets have been dropped. What used to be called `emacsPackagesNg` is now simply called `emacsPackages`.
 
-- `services.xserver.desktopManager.xterm` is now disabled by default if `stateVersion` is 19.09 or higher. Previously the xterm desktopManager was enabled when xserver was enabled, but it isn\'t useful for all people so it didn\'t make sense to have any desktopManager enabled default.
+- `services.xserver.desktopManager.xterm` is now disabled by default if `stateVersion` is 19.09 or higher. Previously the xterm desktopManager was enabled when xserver was enabled, but it isn't useful for all people so it didn't make sense to have any desktopManager enabled default.
 
-- The WeeChat plugin `pkgs.weechatScripts.weechat-xmpp` has been removed as it doesn\'t receive any updates from upstream and depends on outdated Python2-based modules.
+- The WeeChat plugin `pkgs.weechatScripts.weechat-xmpp` has been removed as it doesn't receive any updates from upstream and depends on outdated Python2-based modules.
 
 - Old unsupported versions (`logstash5`, `kibana5`, `filebeat5`, `heartbeat5`, `metricbeat5`, `packetbeat5`) of the ELK-stack and Elastic beats have been removed.
 
@@ -210,7 +210,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Citrix Receiver (`citrix_receiver`) has been dropped in favor of Citrix Workspace (`citrix_workspace`).
 
-- The `services.gitlab` module has had its literal secret options (`services.gitlab.smtp.password`, `services.gitlab.databasePassword`, `services.gitlab.initialRootPassword`, `services.gitlab.secrets.secret`, `services.gitlab.secrets.db`, `services.gitlab.secrets.otp` and `services.gitlab.secrets.jws`) replaced by file-based versions (`services.gitlab.smtp.passwordFile`, `services.gitlab.databasePasswordFile`, `services.gitlab.initialRootPasswordFile`, `services.gitlab.secrets.secretFile`, `services.gitlab.secrets.dbFile`, `services.gitlab.secrets.otpFile` and `services.gitlab.secrets.jwsFile`). This was done so that secrets aren\'t stored in the world-readable nix store, but means that for each option you\'ll have to create a file with the same exact string, add \"File\" to the end of the option name, and change the definition to a string pointing to the corresponding file; e.g. `services.gitlab.databasePassword = "supersecurepassword"` becomes `services.gitlab.databasePasswordFile = "/path/to/secret_file"` where the file `secret_file` contains the string `supersecurepassword`.
+- The `services.gitlab` module has had its literal secret options (`services.gitlab.smtp.password`, `services.gitlab.databasePassword`, `services.gitlab.initialRootPassword`, `services.gitlab.secrets.secret`, `services.gitlab.secrets.db`, `services.gitlab.secrets.otp` and `services.gitlab.secrets.jws`) replaced by file-based versions (`services.gitlab.smtp.passwordFile`, `services.gitlab.databasePasswordFile`, `services.gitlab.initialRootPasswordFile`, `services.gitlab.secrets.secretFile`, `services.gitlab.secrets.dbFile`, `services.gitlab.secrets.otpFile` and `services.gitlab.secrets.jwsFile`). This was done so that secrets aren't stored in the world-readable nix store, but means that for each option you'll have to create a file with the same exact string, add "File" to the end of the option name, and change the definition to a string pointing to the corresponding file; e.g. `services.gitlab.databasePassword = "supersecurepassword"` becomes `services.gitlab.databasePasswordFile = "/path/to/secret_file"` where the file `secret_file` contains the string `supersecurepassword`.
 
   The state path (`services.gitlab.statePath`) now has the following restriction: no parent directory can be owned by any other user than `root` or the user specified in `services.gitlab.user`; i.e. if `services.gitlab.statePath` is set to `/var/lib/gitlab/state`, `gitlab` and all parent directories must be owned by either `root` or the user specified in `services.gitlab.user`.
 
@@ -218,7 +218,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The Twitter client `corebird` has been dropped as [it is discontinued and does not work against the new Twitter API](https://www.patreon.com/posts/corebirds-future-18921328). Please use the fork `cawbird` instead which has been adapted to the API changes and is still maintained.
 
-- The `nodejs-11_x` package has been removed as it\'s EOLed by upstream.
+- The `nodejs-11_x` package has been removed as it's EOLed by upstream.
 
 - Because of the systemd upgrade, systemd-timesyncd will no longer work if `system.stateVersion` is not set correctly. When upgrading from NixOS 19.03, please make sure that `system.stateVersion` is set to `"19.03"`, or lower if the installation dates back to an earlier version of NixOS.
 
@@ -252,7 +252,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `consul` package was upgraded past version `1.5`, so its deprecated legacy UI is no longer available.
 
-- The default resample-method for PulseAudio has been changed from the upstream default `speex-float-1` to `speex-float-5`. Be aware that low-powered ARM-based and MIPS-based boards will struggle with this so you\'ll need to set `hardware.pulseaudio.daemon.config.resample-method` back to `speex-float-1`.
+- The default resample-method for PulseAudio has been changed from the upstream default `speex-float-1` to `speex-float-5`. Be aware that low-powered ARM-based and MIPS-based boards will struggle with this so you'll need to set `hardware.pulseaudio.daemon.config.resample-method` back to `speex-float-1`.
 
 - The `phabricator` package and associated `httpd.extraSubservice`, as well as the `phd` service have been removed from nixpkgs due to lack of maintainer.
 
@@ -264,7 +264,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `tomcat-connector` `httpd.extraSubservice` has been removed from nixpkgs.
 
-- It\'s now possible to change configuration in [services.nextcloud](options.html#opt-services.nextcloud.enable) after the initial deploy since all config parameters are persisted in an additional config file generated by the module. Previously core configuration like database parameters were set using their imperative installer after creating `/var/lib/nextcloud`.
+- It's now possible to change configuration in [services.nextcloud](options.html#opt-services.nextcloud.enable) after the initial deploy since all config parameters are persisted in an additional config file generated by the module. Previously core configuration like database parameters were set using their imperative installer after creating `/var/lib/nextcloud`.
 
 - There exists now `lib.forEach`, which is like `map`, but with arguments flipped. When mapping function body spans many lines (or has nested `map`s), it is often hard to follow which list is modified.
 
@@ -308,6 +308,6 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `altcoins` categorization of packages has been removed. You now access these packages at the top level, ie. `nix-shell -p dogecoin` instead of `nix-shell -p altcoins.dogecoin`, etc.
 
-- Ceph has been upgraded to v14.2.1. See the [release notes](https://ceph.com/releases/v14-2-0-nautilus-released/) for details. The mgr dashboard as well as osds backed by loop-devices is no longer explicitly supported by the package and module. Note: There\'s been some issues with python-cherrypy, which is used by the dashboard and prometheus mgr modules (and possibly others), hence 0000-dont-check-cherrypy-version.patch.
+- Ceph has been upgraded to v14.2.1. See the [release notes](https://ceph.com/releases/v14-2-0-nautilus-released/) for details. The mgr dashboard as well as osds backed by loop-devices is no longer explicitly supported by the package and module. Note: There's been some issues with python-cherrypy, which is used by the dashboard and prometheus mgr modules (and possibly others), hence 0000-dont-check-cherrypy-version.patch.
 
 - `pkgs.weechat` is now compiled against `pkgs.python3`. Weechat also recommends [to use Python3 in their docs.](https://weechat.org/scripts/python3/)
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2003.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2003.section.md
index b92c7f6634c7..76cee8858e80 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2003.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2003.section.md
@@ -34,11 +34,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - Postgresql for NixOS service now defaults to v11.
 
-- The graphical installer image starts the graphical session automatically. Before you\'d be greeted by a tty and asked to enter `systemctl start display-manager`. It is now possible to disable the display-manager from running by selecting the `Disable display-manager` quirk in the boot menu.
+- The graphical installer image starts the graphical session automatically. Before you'd be greeted by a tty and asked to enter `systemctl start display-manager`. It is now possible to disable the display-manager from running by selecting the `Disable display-manager` quirk in the boot menu.
 
 - GNOME 3 has been upgraded to 3.34. Please take a look at their [Release Notes](https://help.gnome.org/misc/release-notes/3.34) for details.
 
-- If you enable the Pantheon Desktop Manager via [services.xserver.desktopManager.pantheon.enable](options.html#opt-services.xserver.desktopManager.pantheon.enable), we now default to also use [ Pantheon\'s newly designed greeter ](https://blog.elementary.io/say-hello-to-the-new-greeter/). Contrary to NixOS\'s usual update policy, Pantheon will receive updates during the cycle of NixOS 20.03 when backwards compatible.
+- If you enable the Pantheon Desktop Manager via [services.xserver.desktopManager.pantheon.enable](options.html#opt-services.xserver.desktopManager.pantheon.enable), we now default to also use [ Pantheon's newly designed greeter ](https://blog.elementary.io/say-hello-to-the-new-greeter/). Contrary to NixOS's usual update policy, Pantheon will receive updates during the cycle of NixOS 20.03 when backwards compatible.
 
 - By default zfs pools will now be trimmed on a weekly basis. Trimming is only done on supported devices (i.e. NVME or SSDs) and should improve throughput and lifetime of these devices. It is controlled by the `services.zfs.trim.enable` varname. The zfs scrub service (`services.zfs.autoScrub.enable`) and the zfs autosnapshot service (`services.zfs.autoSnapshot.enable`) are now only enabled if zfs is set in `config.boot.initrd.supportedFilesystems` or `config.boot.supportedFilesystems`. These lists will automatically contain zfs as soon as any zfs mountpoint is configured in `fileSystems`.
 
@@ -77,7 +77,7 @@ The following new services were added since the last release:
 
 - The kubernetes kube-proxy now supports a new hostname configuration `services.kubernetes.proxy.hostname` which has to be set if the hostname of the node should be non default.
 
-- UPower\'s configuration is now managed by NixOS and can be customized via `services.upower`.
+- UPower's configuration is now managed by NixOS and can be customized via `services.upower`.
 
 - To use Geary you should enable [programs.geary.enable](options.html#opt-programs.geary.enable) instead of just adding it to [environment.systemPackages](options.html#opt-environment.systemPackages). It was created so Geary could function properly outside of GNOME.
 
@@ -187,9 +187,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `99-main.network` file was removed. Matching all network interfaces caused many breakages, see [\#18962](https://github.com/NixOS/nixpkgs/pull/18962) and [\#71106](https://github.com/NixOS/nixpkgs/pull/71106).
 
-  We already don\'t support the global [networking.useDHCP](options.html#opt-networking.useDHCP), [networking.defaultGateway](options.html#opt-networking.defaultGateway) and [networking.defaultGateway6](options.html#opt-networking.defaultGateway6) options if [networking.useNetworkd](options.html#opt-networking.useNetworkd) is enabled, but direct users to configure the per-device [networking.interfaces.\<name\>....](options.html#opt-networking.interfaces) options.
+  We already don't support the global [networking.useDHCP](options.html#opt-networking.useDHCP), [networking.defaultGateway](options.html#opt-networking.defaultGateway) and [networking.defaultGateway6](options.html#opt-networking.defaultGateway6) options if [networking.useNetworkd](options.html#opt-networking.useNetworkd) is enabled, but direct users to configure the per-device [networking.interfaces.\<name\>....](options.html#opt-networking.interfaces) options.
 
-- The stdenv now runs all bash with `set -u`, to catch the use of undefined variables. Before, it itself used `set -u` but was careful to unset it so other packages\' code ran as before. Now, all bash code is held to the same high standard, and the rather complex stateful manipulation of the options can be discarded.
+- The stdenv now runs all bash with `set -u`, to catch the use of undefined variables. Before, it itself used `set -u` but was careful to unset it so other packages' code ran as before. Now, all bash code is held to the same high standard, and the rather complex stateful manipulation of the options can be discarded.
 
 - The SLIM Display Manager has been removed, as it has been unmaintained since 2013. Consider migrating to a different display manager such as LightDM (current default in NixOS), SDDM, GDM, or using the startx module which uses Xinitrc.
 
@@ -197,7 +197,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The BEAM package set has been deleted. You will only find there the different interpreters. You should now use the different build tools coming with the languages with sandbox mode disabled.
 
-- There is now only one Xfce package-set and module. This means that attributes `xfce4-14` and `xfceUnstable` all now point to the latest Xfce 4.14 packages. And in the future NixOS releases will be the latest released version of Xfce available at the time of the release\'s development (if viable).
+- There is now only one Xfce package-set and module. This means that attributes `xfce4-14` and `xfceUnstable` all now point to the latest Xfce 4.14 packages. And in the future NixOS releases will be the latest released version of Xfce available at the time of the release's development (if viable).
 
 - The [phpfpm](options.html#opt-services.phpfpm.pools) module now sets `PrivateTmp=true` in its systemd units for better process isolation. If you rely on `/tmp` being shared with other services, explicitly override this by setting `serviceConfig.PrivateTmp` to `false` for each phpfpm unit.
 
@@ -221,7 +221,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The packages `openobex` and `obexftp` are no longer installed when enabling Bluetooth via `hardware.bluetooth.enable`.
 
-- The `dump1090` derivation has been changed to use FlightAware\'s dump1090 as its upstream. However, this version does not have an internal webserver anymore. The assets in the `share/dump1090` directory of the derivation can be used in conjunction with an external webserver to replace this functionality.
+- The `dump1090` derivation has been changed to use FlightAware's dump1090 as its upstream. However, this version does not have an internal webserver anymore. The assets in the `share/dump1090` directory of the derivation can be used in conjunction with an external webserver to replace this functionality.
 
 - The fourStore and fourStoreEndpoint modules have been removed.
 
@@ -291,7 +291,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   - `services.buildkite-agent.meta-data` has been renamed to [services.buildkite-agents.\<name\>.tags](options.html#opt-services.buildkite-agents), to match upstreams naming for 3.x. Its type has also changed - it now accepts an attrset of strings.
 
-  - The`services.buildkite-agent.openssh.publicKeyPath` option has been removed, as it\'s not necessary to deploy public keys to clone private repositories.
+  - The`services.buildkite-agent.openssh.publicKeyPath` option has been removed, as it's not necessary to deploy public keys to clone private repositories.
 
   - `services.buildkite-agent.openssh.privateKeyPath` has been renamed to [buildkite-agents.\<name\>.privateSshKeyPath](options.html#opt-services.buildkite-agents), as the whole `openssh` now only contained that single option.
 
@@ -301,7 +301,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `gcc5` and `gfortran5` packages have been removed.
 
-- The `services.xserver.displayManager.auto` module has been removed. It was only intended for use in internal NixOS tests, and gave the false impression of it being a special display manager when it\'s actually LightDM. Please use the `services.xserver.displayManager.lightdm.autoLogin` options instead, or any other display manager in NixOS as they all support auto-login. If you used this module specifically because it permitted root auto-login you can override the lightdm-autologin pam module like:
+- The `services.xserver.displayManager.auto` module has been removed. It was only intended for use in internal NixOS tests, and gave the false impression of it being a special display manager when it's actually LightDM. Please use the `services.xserver.displayManager.lightdm.autoLogin` options instead, or any other display manager in NixOS as they all support auto-login. If you used this module specifically because it permitted root auto-login you can override the lightdm-autologin pam module like:
 
   ```nix
   {
@@ -325,13 +325,13 @@ When upgrading from a previous release, please be aware of the following incompa
   auth required pam_succeed_if.so quiet
   ```
 
-  line, where default it\'s:
+  line, where default it's:
 
   ```
    auth required pam_succeed_if.so uid >= 1000 quiet
   ```
 
-  not permitting users with uid\'s below 1000 (like root). All other display managers in NixOS are configured like this.
+  not permitting users with uid's below 1000 (like root). All other display managers in NixOS are configured like this.
 
 - There have been lots of improvements to the Mailman module. As a result,
 
@@ -357,9 +357,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Rspamd was updated to version 2.2. Read [ the upstream migration notes](https://rspamd.com/doc/migration.html#migration-to-rspamd-20) carefully. Please be especially aware that some modules were removed and the default Bayes backend is now Redis.
 
-- The `*psu` versions of oraclejdk8 have been removed as they aren\'t provided by upstream anymore.
+- The `*psu` versions of oraclejdk8 have been removed as they aren't provided by upstream anymore.
 
-- The `services.dnscrypt-proxy` module has been removed as it used the deprecated version of dnscrypt-proxy. We\'ve added [services.dnscrypt-proxy2.enable](options.html#opt-services.dnscrypt-proxy2.enable) to use the supported version. This module supports configuration via the Nix attribute set [services.dnscrypt-proxy2.settings](options.html#opt-services.dnscrypt-proxy2.settings), or by passing a TOML configuration file via [services.dnscrypt-proxy2.configFile](options.html#opt-services.dnscrypt-proxy2.configFile).
+- The `services.dnscrypt-proxy` module has been removed as it used the deprecated version of dnscrypt-proxy. We've added [services.dnscrypt-proxy2.enable](options.html#opt-services.dnscrypt-proxy2.enable) to use the supported version. This module supports configuration via the Nix attribute set [services.dnscrypt-proxy2.settings](options.html#opt-services.dnscrypt-proxy2.settings), or by passing a TOML configuration file via [services.dnscrypt-proxy2.configFile](options.html#opt-services.dnscrypt-proxy2.configFile).
 
   ```nix
   {
@@ -382,13 +382,13 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `qesteidutil` has been deprecated in favor of `qdigidoc`.
 
-- sqldeveloper_18 has been removed as it\'s not maintained anymore, sqldeveloper has been updated to version `19.4`. Please note that this means that this means that the oraclejdk is now required. For further information please read the [release notes](https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/sqldev-relnotes-194-5908846.html).
+- sqldeveloper_18 has been removed as it's not maintained anymore, sqldeveloper has been updated to version `19.4`. Please note that this means that this means that the oraclejdk is now required. For further information please read the [release notes](https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/sqldev-relnotes-194-5908846.html).
 
-- Haskell `env` and `shellFor` dev shell environments now organize dependencies the same way as regular builds. In particular, rather than receiving all the different lists of dependencies mashed together as one big list, and then partitioning into Haskell and non-Hakell dependencies, they work from the original many different dependency parameters and don\'t need to algorithmically partition anything.
+- Haskell `env` and `shellFor` dev shell environments now organize dependencies the same way as regular builds. In particular, rather than receiving all the different lists of dependencies mashed together as one big list, and then partitioning into Haskell and non-Hakell dependencies, they work from the original many different dependency parameters and don't need to algorithmically partition anything.
 
   This means that if you incorrectly categorize a dependency, e.g. non-Haskell library dependency as a `buildDepends` or run-time Haskell dependency as a `setupDepends`, whereas things would have worked before they may not work now.
 
-- The gcc-snapshot-package has been removed. It\'s marked as broken for \>2 years and used to point to a fairly old snapshot from the gcc7-branch.
+- The gcc-snapshot-package has been removed. It's marked as broken for \>2 years and used to point to a fairly old snapshot from the gcc7-branch.
 
 - The nixos-build-vms8 -script now uses the python test-driver.
 
@@ -398,21 +398,21 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Stand-alone usage of `Upower` now requires `services.upower.enable` instead of just installing into [environment.systemPackages](options.html#opt-environment.systemPackages).
 
-- nextcloud has been updated to `v18.0.2`. This means that users from NixOS 19.09 can\'t upgrade directly since you can only move one version forward and 19.09 uses `v16.0.8`.
+- nextcloud has been updated to `v18.0.2`. This means that users from NixOS 19.09 can't upgrade directly since you can only move one version forward and 19.09 uses `v16.0.8`.
 
   To provide a safe upgrade-path and to circumvent similar issues in the future, the following measures were taken:
 
   - The pkgs.nextcloud-attribute has been removed and replaced with versioned attributes (currently pkgs.nextcloud17 and pkgs.nextcloud18). With this change major-releases can be backported without breaking stuff and to make upgrade-paths easier.
 
-  - Existing setups will be detected using [system.stateVersion](options.html#opt-system.stateVersion): by default, nextcloud17 will be used, but will raise a warning which notes that after that deploy it\'s recommended to update to the latest stable version (nextcloud18) by declaring the newly introduced setting [services.nextcloud.package](options.html#opt-services.nextcloud.package).
+  - Existing setups will be detected using [system.stateVersion](options.html#opt-system.stateVersion): by default, nextcloud17 will be used, but will raise a warning which notes that after that deploy it's recommended to update to the latest stable version (nextcloud18) by declaring the newly introduced setting [services.nextcloud.package](options.html#opt-services.nextcloud.package).
 
-  - Users with an overlay (e.g. to use nextcloud at version `v18` on `19.09`) will get an evaluation error by default. This is done to ensure that our [package](options.html#opt-services.nextcloud.package)-option doesn\'t select an older version by accident. It\'s recommended to use pkgs.nextcloud18 or to set [package](options.html#opt-services.nextcloud.package) to pkgs.nextcloud explicitly.
+  - Users with an overlay (e.g. to use nextcloud at version `v18` on `19.09`) will get an evaluation error by default. This is done to ensure that our [package](options.html#opt-services.nextcloud.package)-option doesn't select an older version by accident. It's recommended to use pkgs.nextcloud18 or to set [package](options.html#opt-services.nextcloud.package) to pkgs.nextcloud explicitly.
 
   ::: {.warning}
-  Please note that if you\'re coming from `19.03` or older, you have to manually upgrade to `19.09` first to upgrade your server to Nextcloud v16.
+  Please note that if you're coming from `19.03` or older, you have to manually upgrade to `19.09` first to upgrade your server to Nextcloud v16.
   :::
 
-- Hydra has gained a massive performance improvement due to [some database schema changes](https://github.com/NixOS/hydra/pull/710) by adding several IDs and better indexing. However, it\'s necessary to upgrade Hydra in multiple steps:
+- Hydra has gained a massive performance improvement due to [some database schema changes](https://github.com/NixOS/hydra/pull/710) by adding several IDs and better indexing. However, it's necessary to upgrade Hydra in multiple steps:
 
   - At first, an older version of Hydra needs to be deployed which adds those (nullable) columns. When having set [stateVersion ](options.html#opt-system.stateVersion) to a value older than `20.03`, this package will be selected by default from the module when upgrading. Otherwise, the package can be deployed using the following config:
 
@@ -434,13 +434,13 @@ When upgrading from a previous release, please be aware of the following incompa
 - Deploy a newer version of Hydra to activate the DB optimizations. This can be done by using hydra-unstable. This package already includes [flake-support](https://github.com/nixos/rfcs/pull/49) and is therefore compiled against pkgs.nixFlakes.
 
   ::: {.warning}
-  If your [stateVersion](options.html#opt-system.stateVersion) is set to `20.03` or greater, hydra-unstable will be used automatically! This will break your setup if you didn\'t run the migration.
+  If your [stateVersion](options.html#opt-system.stateVersion) is set to `20.03` or greater, hydra-unstable will be used automatically! This will break your setup if you didn't run the migration.
   :::
 
-  Please note that Hydra is currently not available with nixStable as this doesn\'t compile anymore.
+  Please note that Hydra is currently not available with nixStable as this doesn't compile anymore.
 
   ::: {.warning}
-  pkgs.hydra has been removed to ensure a graceful database-migration using the dedicated package-attributes. If you still have pkgs.hydra defined in e.g. an overlay, an assertion error will be thrown. To circumvent this, you need to set [services.hydra.package](options.html#opt-services.hydra.package) to pkgs.hydra explicitly and make sure you know what you\'re doing!
+  pkgs.hydra has been removed to ensure a graceful database-migration using the dedicated package-attributes. If you still have pkgs.hydra defined in e.g. an overlay, an assertion error will be thrown. To circumvent this, you need to set [services.hydra.package](options.html#opt-services.hydra.package) to pkgs.hydra explicitly and make sure you know what you're doing!
   :::
 
 - The TokuDB storage engine will be disabled in mariadb 10.5. It is recommended to switch to RocksDB. See also [TokuDB](https://mariadb.com/kb/en/tokudb/).
@@ -478,9 +478,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
   Depending on your setup, you need to incorporate one of the following changes in your setup to upgrade to 20.03:
 
-  - If you use `sqlite3` you don\'t need to do anything.
+  - If you use `sqlite3` you don't need to do anything.
 
-  - If you use `postgresql` on a different server, you don\'t need to change anything as well since this module was never designed to configure remote databases.
+  - If you use `postgresql` on a different server, you don't need to change anything as well since this module was never designed to configure remote databases.
 
   - If you use `postgresql` and configured your synapse initially on `19.09` or older, you simply need to enable postgresql-support explicitly:
 
@@ -496,12 +496,12 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - If you deploy a fresh matrix-synapse, you need to configure the database yourself (e.g. by using the [services.postgresql.initialScript](options.html#opt-services.postgresql.initialScript) option). An example for this can be found in the [documentation of the Matrix module](#module-services-matrix).
 
-- If you initially deployed your matrix-synapse on `nixos-unstable` _after_ the `19.09`-release, your database is misconfigured due to a regression in NixOS. For now, matrix-synapse will startup with a warning, but it\'s recommended to reconfigure the database to set the values `LC_COLLATE` and `LC_CTYPE` to [`'C'`](https://www.postgresql.org/docs/12/locale.html).
+- If you initially deployed your matrix-synapse on `nixos-unstable` _after_ the `19.09`-release, your database is misconfigured due to a regression in NixOS. For now, matrix-synapse will startup with a warning, but it's recommended to reconfigure the database to set the values `LC_COLLATE` and `LC_CTYPE` to [`'C'`](https://www.postgresql.org/docs/12/locale.html).
 
-- The [systemd.network.links](options.html#opt-systemd.network.links) option is now respected even when [systemd-networkd](options.html#opt-systemd.network.enable) is disabled. This mirrors the behaviour of systemd - It\'s udev that parses `.link` files, not `systemd-networkd`.
+- The [systemd.network.links](options.html#opt-systemd.network.links) option is now respected even when [systemd-networkd](options.html#opt-systemd.network.enable) is disabled. This mirrors the behaviour of systemd - It's udev that parses `.link` files, not `systemd-networkd`.
 
 - mongodb has been updated to version `3.4.24`.
 
   ::: {.warning}
-  Please note that mongodb has been relicensed under their own [` sspl`](https://www.mongodb.com/licensing/server-side-public-license/faq)-license. Since it\'s not entirely free and not OSI-approved, it\'s listed as non-free. This means that Hydra doesn\'t provide prebuilt mongodb-packages and needs to be built locally.
+  Please note that mongodb has been relicensed under their own [` sspl`](https://www.mongodb.com/licensing/server-side-public-license/faq)-license. Since it's not entirely free and not OSI-approved, it's listed as non-free. This means that Hydra doesn't provide prebuilt mongodb-packages and needs to be built locally.
   :::
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2009.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2009.section.md
index 79be2a56a54e..6bb75a04b3e8 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2009.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2009.section.md
@@ -130,7 +130,7 @@ In addition to 1119 new, 118 updated, and 476 removed options; 61 new modules we
 
   - [services.cage.enable](options.html#opt-services.cage.enable) Wayland cage service
 
-  - [services.convos.enable](options.html#opt-services.convos.enable) IRC daemon, which can be accessed throught the browser
+  - [services.convos.enable](options.html#opt-services.convos.enable) IRC daemon, which can be accessed through the browser
 
   - [services.engelsystem.enable](options.html#opt-services.engelsystem.enable) Tool for coordinating volunteers and shifts on large events
 
@@ -218,7 +218,7 @@ In addition to 1119 new, 118 updated, and 476 removed options; 61 new modules we
 
 When upgrading from a previous release, please be aware of the following incompatible changes:
 
-- MariaDB has been updated to 10.4, MariaDB Galera to 26.4. Before you upgrade, it would be best to take a backup of your database. For MariaDB Galera Cluster, see [Upgrading from MariaDB 10.3 to MariaDB 10.4 with Galera Cluster](https://mariadb.com/kb/en/upgrading-from-mariadb-103-to-mariadb-104-with-galera-cluster/) instead. Before doing the upgrade read [Incompatible Changes Between 10.3 and 10.4](https://mariadb.com/kb/en/upgrading-from-mariadb-103-to-mariadb-104/#incompatible-changes-between-103-and-104). After the upgrade you will need to run `mysql_upgrade`. MariaDB 10.4 introduces a number of changes to the authentication process, intended to make things easier and more intuitive. See [Authentication from MariaDB 10.4](https://mariadb.com/kb/en/authentication-from-mariadb-104/). unix_socket auth plugin does not use a password, and uses the connecting user\'s UID instead. When a new MariaDB data directory is initialized, two MariaDB users are created and can be used with new unix_socket auth plugin, as well as traditional mysql_native_password plugin: root\@localhost and mysql\@localhost. To actually use the traditional mysql_native_password plugin method, one must run the following:
+- MariaDB has been updated to 10.4, MariaDB Galera to 26.4. Before you upgrade, it would be best to take a backup of your database. For MariaDB Galera Cluster, see [Upgrading from MariaDB 10.3 to MariaDB 10.4 with Galera Cluster](https://mariadb.com/kb/en/upgrading-from-mariadb-103-to-mariadb-104-with-galera-cluster/) instead. Before doing the upgrade read [Incompatible Changes Between 10.3 and 10.4](https://mariadb.com/kb/en/upgrading-from-mariadb-103-to-mariadb-104/#incompatible-changes-between-103-and-104). After the upgrade you will need to run `mysql_upgrade`. MariaDB 10.4 introduces a number of changes to the authentication process, intended to make things easier and more intuitive. See [Authentication from MariaDB 10.4](https://mariadb.com/kb/en/authentication-from-mariadb-104/). unix_socket auth plugin does not use a password, and uses the connecting user's UID instead. When a new MariaDB data directory is initialized, two MariaDB users are created and can be used with new unix_socket auth plugin, as well as traditional mysql_native_password plugin: root\@localhost and mysql\@localhost. To actually use the traditional mysql_native_password plugin method, one must run the following:
 
   ```nix
   {
@@ -284,7 +284,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The [matrix-synapse](options.html#opt-services.matrix-synapse.enable) module no longer includes optional dependencies by default, they have to be added through the [plugins](options.html#opt-services.matrix-synapse.plugins) option.
 
-- `buildGoModule` now internally creates a vendor directory in the source tree for downloaded modules instead of using go\'s [module proxy protocol](https://golang.org/cmd/go/#hdr-Module_proxy_protocol). This storage format is simpler and therefore less likely to break with future versions of go. As a result `buildGoModule` switched from `modSha256` to the `vendorSha256` attribute to pin fetched version data.
+- `buildGoModule` now internally creates a vendor directory in the source tree for downloaded modules instead of using go's [module proxy protocol](https://golang.org/cmd/go/#hdr-Module_proxy_protocol). This storage format is simpler and therefore less likely to break with future versions of go. As a result `buildGoModule` switched from `modSha256` to the `vendorSha256` attribute to pin fetched version data.
 
 - Grafana is now built without support for phantomjs by default. Phantomjs support has been [deprecated in Grafana](https://grafana.com/docs/grafana/latest/guides/whats-new-in-v6-4/) and the phantomjs project is [currently unmaintained](https://github.com/ariya/phantomjs/issues/15344#issue-302015362). It can still be enabled by providing `phantomJsSupport = true` to the package instantiation:
 
@@ -306,9 +306,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The initrd SSH support now uses OpenSSH rather than Dropbear to allow the use of Ed25519 keys and other OpenSSH-specific functionality. Host keys must now be in the OpenSSH format, and at least one pre-generated key must be specified.
 
-  If you used the `boot.initrd.network.ssh.host*Key` options, you\'ll get an error explaining how to convert your host keys and migrate to the new `boot.initrd.network.ssh.hostKeys` option. Otherwise, if you don\'t have any host keys set, you\'ll need to generate some; see the `hostKeys` option documentation for instructions.
+  If you used the `boot.initrd.network.ssh.host*Key` options, you'll get an error explaining how to convert your host keys and migrate to the new `boot.initrd.network.ssh.hostKeys` option. Otherwise, if you don't have any host keys set, you'll need to generate some; see the `hostKeys` option documentation for instructions.
 
-- Since this release there\'s an easy way to customize your PHP install to get a much smaller base PHP with only wanted extensions enabled. See the following snippet installing a smaller PHP with the extensions `imagick`, `opcache`, `pdo` and `pdo_mysql` loaded:
+- Since this release there's an easy way to customize your PHP install to get a much smaller base PHP with only wanted extensions enabled. See the following snippet installing a smaller PHP with the extensions `imagick`, `opcache`, `pdo` and `pdo_mysql` loaded:
 
   ```nix
   {
@@ -325,7 +325,7 @@ When upgrading from a previous release, please be aware of the following incompa
   }
   ```
 
-  The default `php` attribute hasn\'t lost any extensions. The `opcache` extension has been added. All upstream PHP extensions are available under php.extensions.\<name?\>.
+  The default `php` attribute hasn't lost any extensions. The `opcache` extension has been added. All upstream PHP extensions are available under php.extensions.\<name?\>.
 
   All PHP `config` flags have been removed for the following reasons:
 
@@ -418,9 +418,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
   The default value for [services.httpd.mpm](options.html#opt-services.httpd.mpm) has been changed from `prefork` to `event`. Along with this change the default value for [services.httpd.virtualHosts.\<name\>.http2](options.html#opt-services.httpd.virtualHosts) has been set to `true`.
 
-- The `systemd-networkd` option `systemd.network.networks.<name>.dhcp.CriticalConnection` has been removed following upstream systemd\'s deprecation of the same. It is recommended to use `systemd.network.networks.<name>.networkConfig.KeepConfiguration` instead. See systemd.network 5 for details.
+- The `systemd-networkd` option `systemd.network.networks.<name>.dhcp.CriticalConnection` has been removed following upstream systemd's deprecation of the same. It is recommended to use `systemd.network.networks.<name>.networkConfig.KeepConfiguration` instead. See systemd.network 5 for details.
 
-- The `systemd-networkd` option `systemd.network.networks._name_.dhcpConfig` has been renamed to [systemd.network.networks._name_.dhcpV4Config](options.html#opt-systemd.network.networks._name_.dhcpV4Config) following upstream systemd\'s documentation change. See systemd.network 5 for details.
+- The `systemd-networkd` option `systemd.network.networks._name_.dhcpConfig` has been renamed to [systemd.network.networks._name_.dhcpV4Config](options.html#opt-systemd.network.networks._name_.dhcpV4Config) following upstream systemd's documentation change. See systemd.network 5 for details.
 
 - In the `picom` module, several options that accepted floating point numbers encoded as strings (for example [services.picom.activeOpacity](options.html#opt-services.picom.activeOpacity)) have been changed to the (relatively) new native `float` type. To migrate your configuration simply remove the quotes around the numbers.
 
@@ -440,7 +440,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The GRUB specific option `boot.loader.grub.extraInitrd` has been replaced with the generic option `boot.initrd.secrets`. This option creates a secondary initrd from the specified files, rather than using a manually created initrd file. Due to an existing bug with `boot.loader.grub.extraInitrd`, it is not possible to directly boot an older generation that used that option. It is still possible to rollback to that generation if the required initrd file has not been deleted.
 
-- The [DNSChain](https://github.com/okTurtles/dnschain) package and NixOS module have been removed from Nixpkgs as the software is unmaintained and can\'t be built. For more information see issue [\#89205](https://github.com/NixOS/nixpkgs/issues/89205).
+- The [DNSChain](https://github.com/okTurtles/dnschain) package and NixOS module have been removed from Nixpkgs as the software is unmaintained and can't be built. For more information see issue [\#89205](https://github.com/NixOS/nixpkgs/issues/89205).
 
 - In the `resilio` module, [services.resilio.httpListenAddr](options.html#opt-services.resilio.httpListenAddr) has been changed to listen to `[::1]` instead of `0.0.0.0`.
 
@@ -456,7 +456,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   - Update servers first, then clients.
 
-- Radicale\'s default package has changed from 2.x to 3.x. An upgrade checklist can be found [here](https://github.com/Kozea/Radicale/blob/3.0.x/NEWS.md#upgrade-checklist). You can use the newer version in the NixOS service by setting the `package` to `radicale3`, which is done automatically if `stateVersion` is 20.09 or higher.
+- Radicale's default package has changed from 2.x to 3.x. An upgrade checklist can be found [here](https://github.com/Kozea/Radicale/blob/3.0.x/NEWS.md#upgrade-checklist). You can use the newer version in the NixOS service by setting the `package` to `radicale3`, which is done automatically if `stateVersion` is 20.09 or higher.
 
 - `udpt` experienced a complete rewrite from C++ to rust. The configuration format changed from ini to toml. The new configuration documentation can be found at [the official website](https://naim94a.github.io/udpt/config.html) and example configuration is packaged in `${udpt}/share/udpt/udpt.toml`.
 
@@ -522,7 +522,7 @@ When upgrading from a previous release, please be aware of the following incompa
   }
   ```
 
-  The base package has also been upgraded to the 2020-07-29 \"Hogfather\" release. Plugins might be incompatible or require upgrading.
+  The base package has also been upgraded to the 2020-07-29 "Hogfather" release. Plugins might be incompatible or require upgrading.
 
 - The [services.postgresql.dataDir](options.html#opt-services.postgresql.dataDir) option is now set to `"/var/lib/postgresql/${cfg.package.psqlSchema}"` regardless of your [system.stateVersion](options.html#opt-system.stateVersion). Users with an existing postgresql install that have a [system.stateVersion](options.html#opt-system.stateVersion) of `17.03` or below should double check what the value of their [services.postgresql.dataDir](options.html#opt-services.postgresql.dataDir) option is (`/var/db/postgresql`) and then explicitly set this value to maintain compatibility:
 
@@ -552,17 +552,17 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The [jellyfin](options.html#opt-services.jellyfin.enable) module will use and stay on the Jellyfin version `10.5.5` if `stateVersion` is lower than `20.09`. This is because significant changes were made to the database schema, and it is highly recommended to backup your instance before upgrading. After making your backup, you can upgrade to the latest version either by setting your `stateVersion` to `20.09` or higher, or set the `services.jellyfin.package` to `pkgs.jellyfin`. If you do not wish to upgrade Jellyfin, but want to change your `stateVersion`, you can set the value of `services.jellyfin.package` to `pkgs.jellyfin_10_5`.
 
-- The `security.rngd` service is now disabled by default. This choice was made because there\'s krngd in the linux kernel space making it (for most usecases) functionally redundent.
+- The `security.rngd` service is now disabled by default. This choice was made because there's krngd in the linux kernel space making it (for most usecases) functionally redundant.
 
 - The `hardware.nvidia.optimus_prime.enable` service has been renamed to `hardware.nvidia.prime.sync.enable` and has many new enhancements. Related nvidia prime settings may have also changed.
 
 - The package nextcloud17 has been removed and nextcloud18 was marked as insecure since both of them will [ will be EOL (end of life) within the lifetime of 20.09](https://docs.nextcloud.com/server/19/admin_manual/release_schedule.html).
 
-  It\'s necessary to upgrade to nextcloud19:
+  It's necessary to upgrade to nextcloud19:
 
-  - From nextcloud17, you have to upgrade to nextcloud18 first as Nextcloud doesn\'t allow going multiple major revisions forward in a single upgrade. This is possible by setting [services.nextcloud.package](options.html#opt-services.nextcloud.package) to nextcloud18.
+  - From nextcloud17, you have to upgrade to nextcloud18 first as Nextcloud doesn't allow going multiple major revisions forward in a single upgrade. This is possible by setting [services.nextcloud.package](options.html#opt-services.nextcloud.package) to nextcloud18.
 
-  - From nextcloud18, it\'s possible to directly upgrade to nextcloud19 by setting [services.nextcloud.package](options.html#opt-services.nextcloud.package) to nextcloud19.
+  - From nextcloud18, it's possible to directly upgrade to nextcloud19 by setting [services.nextcloud.package](options.html#opt-services.nextcloud.package) to nextcloud19.
 
 - The GNOME desktop manager no longer default installs gnome3.epiphany. It was chosen to do this as it has a usability breaking issue (see issue [\#98819](https://github.com/NixOS/nixpkgs/issues/98819)) that makes it unsuitable to be a default app.
 
@@ -578,7 +578,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `services.journald.rateLimitBurst` was updated from `1000` to `10000` to follow the new upstream systemd default.
 
-- The notmuch package moves its emacs-related binaries and emacs lisp files to a separate output. They\'re not part of the default `out` output anymore - if you relied on the `notmuch-emacs-mua` binary or the emacs lisp files, access them via the `notmuch.emacs` output.
+- The notmuch package moves its emacs-related binaries and emacs lisp files to a separate output. They're not part of the default `out` output anymore - if you relied on the `notmuch-emacs-mua` binary or the emacs lisp files, access them via the `notmuch.emacs` output.
 
 - Device tree overlay support was improved in [\#79370](https://github.com/NixOS/nixpkgs/pull/79370) and now uses [hardware.deviceTree.kernelPackage](options.html#opt-hardware.deviceTree.kernelPackage) instead of `hardware.deviceTree.base`. [hardware.deviceTree.overlays](options.html#opt-hardware.deviceTree.overlays) configuration was extended to support `.dts` files with symbols. Device trees can now be filtered by setting [hardware.deviceTree.filter](options.html#opt-hardware.deviceTree.filter) option.
 
@@ -590,7 +590,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   Please note that Rust packages utilizing a custom build/install procedure (e.g. by using a `Makefile`) or test suites that rely on the structure of the `target/` directory may break due to those assumptions. For further information, please read the Rust section in the Nixpkgs manual.
 
-- The cc- and binutils-wrapper\'s \"infix salt\" and `_BUILD_` and `_TARGET_` user infixes have been replaced with with a \"suffix salt\" and suffixes and `_FOR_BUILD` and `_FOR_TARGET`. This matches the autotools convention for env vars which standard for these things, making interfacing with other tools easier.
+- The cc- and binutils-wrapper's "infix salt" and `_BUILD_` and `_TARGET_` user infixes have been replaced with with a "suffix salt" and suffixes and `_FOR_BUILD` and `_FOR_TARGET`. This matches the autotools convention for env vars which standard for these things, making interfacing with other tools easier.
 
 - Additional Git documentation (HTML and text files) is now available via the `git-doc` package.
 
@@ -598,7 +598,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The installer now enables sshd by default. This improves installation on headless machines especially ARM single-board-computer. To login through ssh, either a password or an ssh key must be set for the root user or the nixos user.
 
-- The scripted networking system now uses `.link` files in `/etc/systemd/network` to configure mac address and link MTU, instead of the sometimes buggy `network-link-*` units, which have been removed. Bringing the interface up has been moved to the beginning of the `network-addresses-*` unit. Note this doesn\'t require `systemd-networkd` - it\'s udev that parses `.link` files. Extra care needs to be taken in the presence of [legacy udev rules](https://wiki.debian.org/NetworkInterfaceNames#THE_.22PERSISTENT_NAMES.22_SCHEME) to rename interfaces, as MAC Address and MTU defined in these options can only match on the original link name. In such cases, you most likely want to create a `10-*.link` file through [systemd.network.links](options.html#opt-systemd.network.links) and set both name and MAC Address / MTU there.
+- The scripted networking system now uses `.link` files in `/etc/systemd/network` to configure mac address and link MTU, instead of the sometimes buggy `network-link-*` units, which have been removed. Bringing the interface up has been moved to the beginning of the `network-addresses-*` unit. Note this doesn't require `systemd-networkd` - it's udev that parses `.link` files. Extra care needs to be taken in the presence of [legacy udev rules](https://wiki.debian.org/NetworkInterfaceNames#THE_.22PERSISTENT_NAMES.22_SCHEME) to rename interfaces, as MAC Address and MTU defined in these options can only match on the original link name. In such cases, you most likely want to create a `10-*.link` file through [systemd.network.links](options.html#opt-systemd.network.links) and set both name and MAC Address / MTU there.
 
 - Grafana received a major update to version 7.x. A plugin is now needed for image rendering support, and plugins must now be signed by default. More information can be found [in the Grafana documentation](https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v7-0).
 
@@ -624,15 +624,15 @@ When upgrading from a previous release, please be aware of the following incompa
 
   to get the previous behavior of listening on all network interfaces.
 
-- With this release `systemd-networkd` (when enabled through [networking.useNetworkd](options.html#opt-networking.useNetworkd)) has it\'s netlink socket created through a `systemd.socket` unit. This gives us control over socket buffer sizes and other parameters. For larger setups where networkd has to create a lot of (virtual) devices the default buffer size (currently 128MB) is not enough.
+- With this release `systemd-networkd` (when enabled through [networking.useNetworkd](options.html#opt-networking.useNetworkd)) has it's netlink socket created through a `systemd.socket` unit. This gives us control over socket buffer sizes and other parameters. For larger setups where networkd has to create a lot of (virtual) devices the default buffer size (currently 128MB) is not enough.
 
   On a machine with \>100 virtual interfaces (e.g., wireguard tunnels, VLANs, ...), that all have to be brought up during system startup, the receive buffer size will spike for a brief period. Eventually some of the message will be dropped since there is not enough (permitted) buffer space available.
 
   By having `systemd-networkd` start with a netlink socket created by `systemd` we can configure the `ReceiveBufferSize=` parameter in the socket options (i.e. `systemd.sockets.systemd-networkd.socketOptions.ReceiveBufferSize`) without recompiling `systemd-networkd`.
 
-  Since the actual memory requirements depend on hardware, timing, exact configurations etc. it isn\'t currently possible to infer a good default from within the NixOS module system. Administrators are advised to monitor the logs of `systemd-networkd` for `rtnl: kernel receive buffer overrun` spam and increase the memory limit as they see fit.
+  Since the actual memory requirements depend on hardware, timing, exact configurations etc. it isn't currently possible to infer a good default from within the NixOS module system. Administrators are advised to monitor the logs of `systemd-networkd` for `rtnl: kernel receive buffer overrun` spam and increase the memory limit as they see fit.
 
-  Note: Increasing the `ReceiveBufferSize=` doesn\'t allocate any memory. It just increases the upper bound on the kernel side. The memory allocation depends on the amount of messages that are queued on the kernel side of the netlink socket.
+  Note: Increasing the `ReceiveBufferSize=` doesn't allocate any memory. It just increases the upper bound on the kernel side. The memory allocation depends on the amount of messages that are queued on the kernel side of the netlink socket.
 
 - Specifying [mailboxes](options.html#opt-services.dovecot2.mailboxes) in the dovecot2 module as a list is deprecated and will break eval in 21.05. Instead, an attribute-set should be specified where the `name` should be the key of the attribute.
 
@@ -662,7 +662,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - nextcloud has been updated to [v19](https://nextcloud.com/blog/nextcloud-hub-brings-productivity-to-home-office/).
 
-  If you have an existing installation, please make sure that you\'re on nextcloud18 before upgrading to nextcloud19 since Nextcloud doesn\'t support upgrades across multiple major versions.
+  If you have an existing installation, please make sure that you're on nextcloud18 before upgrading to nextcloud19 since Nextcloud doesn't support upgrades across multiple major versions.
 
 - The `nixos-run-vms` script now deletes the previous run machines states on test startup. You can use the `--keep-vm-state` flag to match the previous behaviour and keep the same VM state between different test runs.
 
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2105.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2105.section.md
index 359f2e5b2e58..080ca68d9258 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2105.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2105.section.md
@@ -68,9 +68,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - If the `services.dbus` module is enabled, then the user D-Bus session is now always socket activated. The associated options `services.dbus.socketActivated` and `services.xserver.startDbusSession` have therefore been removed and you will receive a warning if they are present in your configuration. This change makes the user D-Bus session available also for non-graphical logins.
 
-- The `networking.wireless.iwd` module now installs the upstream-provided 80-iwd.link file, which sets the NamePolicy= for all wlan devices to \"keep kernel\", to avoid race conditions between iwd and networkd. If you don\'t want this, you can set `systemd.network.links."80-iwd" = lib.mkForce {}`.
+- The `networking.wireless.iwd` module now installs the upstream-provided 80-iwd.link file, which sets the NamePolicy= for all wlan devices to "keep kernel", to avoid race conditions between iwd and networkd. If you don't want this, you can set `systemd.network.links."80-iwd" = lib.mkForce {}`.
 
-- `rubyMinimal` was removed due to being unused and unusable. The default ruby interpreter includes JIT support, which makes it reference it\'s compiler. Since JIT support is probably needed by some Gems, it was decided to enable this feature with all cc references by default, and allow to build a Ruby derivation without references to cc, by setting `jitSupport = false;` in an overlay. See [\#90151](https://github.com/NixOS/nixpkgs/pull/90151) for more info.
+- `rubyMinimal` was removed due to being unused and unusable. The default ruby interpreter includes JIT support, which makes it reference it's compiler. Since JIT support is probably needed by some Gems, it was decided to enable this feature with all cc references by default, and allow to build a Ruby derivation without references to cc, by setting `jitSupport = false;` in an overlay. See [\#90151](https://github.com/NixOS/nixpkgs/pull/90151) for more info.
 
 - Setting `services.openssh.authorizedKeysFiles` now also affects which keys `security.pam.enableSSHAgentAuth` will use. WARNING: If you are using these options in combination do make sure that any key paths you use are present in `services.openssh.authorizedKeysFiles`!
 
@@ -130,7 +130,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `vim` and `neovim` switched to Python 3, dropping all Python 2 support.
 
-- [networking.wireguard.interfaces.\<name\>.generatePrivateKeyFile](options.html#opt-networking.wireguard.interfaces), which is off by default, had a `chmod` race condition fixed. As an aside, the parent directory\'s permissions were widened, and the key files were made owner-writable. This only affects newly created keys. However, if the exact permissions are important for your setup, read [\#121294](https://github.com/NixOS/nixpkgs/pull/121294).
+- [networking.wireguard.interfaces.\<name\>.generatePrivateKeyFile](options.html#opt-networking.wireguard.interfaces), which is off by default, had a `chmod` race condition fixed. As an aside, the parent directory's permissions were widened, and the key files were made owner-writable. This only affects newly created keys. However, if the exact permissions are important for your setup, read [\#121294](https://github.com/NixOS/nixpkgs/pull/121294).
 
 - [boot.zfs.forceImportAll](options.html#opt-boot.zfs.forceImportAll) previously did nothing, but has been fixed. However its default has been changed to `false` to preserve the existing default behaviour. If you have this explicitly set to `true`, please note that your non-root pools will now be forcibly imported.
 
@@ -157,12 +157,12 @@ When upgrading from a previous release, please be aware of the following incompa
 - Amazon EC2 and OpenStack Compute (nova) images now re-fetch instance meta data and user data from the instance metadata service (IMDS) on each boot. For example: stopping an EC2 instance, changing its user data, and restarting the instance will now cause it to fetch and apply the new user data.
 
   ::: {.warning}
-  Specifically, `/etc/ec2-metadata` is re-populated on each boot. Some NixOS scripts that read from this directory are guarded to only run if the files they want to manipulate do not already exist, and so will not re-apply their changes if the IMDS response changes. Examples: `root`\'s SSH key is only added if `/root/.ssh/authorized_keys` does not exist, and SSH host keys are only set from user data if they do not exist in `/etc/ssh`.
+  Specifically, `/etc/ec2-metadata` is re-populated on each boot. Some NixOS scripts that read from this directory are guarded to only run if the files they want to manipulate do not already exist, and so will not re-apply their changes if the IMDS response changes. Examples: `root`'s SSH key is only added if `/root/.ssh/authorized_keys` does not exist, and SSH host keys are only set from user data if they do not exist in `/etc/ssh`.
   :::
 
 - The `rspamd` services is now sandboxed. It is run as a dynamic user instead of root, so secrets and other files may have to be moved or their permissions may have to be fixed. The sockets are now located in `/run/rspamd` instead of `/run`.
 
-- Enabling the Tor client no longer silently also enables and configures Privoxy, and the `services.tor.client.privoxy.enable` option has been removed. To enable Privoxy, and to configure it to use Tor\'s faster port, use the following configuration:
+- Enabling the Tor client no longer silently also enables and configures Privoxy, and the `services.tor.client.privoxy.enable` option has been removed. To enable Privoxy, and to configure it to use Tor's faster port, use the following configuration:
 
   ```nix
   {
@@ -181,7 +181,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The fish-foreign-env package has been replaced with fishPlugins.foreign-env, in which the fish functions have been relocated to the `vendor_functions.d` directory to be loaded automatically.
 
-- The prometheus json exporter is now managed by the prometheus community. Together with additional features some backwards incompatibilities were introduced. Most importantly the exporter no longer accepts a fixed command-line parameter to specify the URL of the endpoint serving JSON. It now expects this URL to be passed as an URL parameter, when scraping the exporter\'s `/probe` endpoint. In the prometheus scrape configuration the scrape target might look like this:
+- The prometheus json exporter is now managed by the prometheus community. Together with additional features some backwards incompatibilities were introduced. Most importantly the exporter no longer accepts a fixed command-line parameter to specify the URL of the endpoint serving JSON. It now expects this URL to be passed as an URL parameter, when scraping the exporter's `/probe` endpoint. In the prometheus scrape configuration the scrape target might look like this:
 
   ```
   http://some.json-exporter.host:7979/probe?target=https://example.com/some/json/endpoint
@@ -197,7 +197,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   Android packages are now loaded from a repo.json file created by parsing Android repo XML files. The arguments `repoJson` and `repoXmls` have been added to allow overriding the built-in androidenv repo.json with your own. Additionally, license files are now written to allow compatibility with Gradle-based tools, and the `extraLicenses` argument has been added to accept more SDK licenses if your project requires it. See the androidenv documentation for more details.
 
-- The attribute `mpi` is now consistently used to provide a default, system-wide MPI implementation. The default implementation is openmpi, which has been used before by all derivations affects by this change. Note that all packages that have used `mpi ? null` in the input for optional MPI builds, have been changed to the boolean input paramater `useMpi` to enable building with MPI. Building all packages with `mpich` instead of the default `openmpi` can now be achived like this:
+- The attribute `mpi` is now consistently used to provide a default, system-wide MPI implementation. The default implementation is openmpi, which has been used before by all derivations affects by this change. Note that all packages that have used `mpi ? null` in the input for optional MPI builds, have been changed to the boolean input parameter `useMpi` to enable building with MPI. Building all packages with `mpich` instead of the default `openmpi` can now be achieved like this:
 
   ```nix
   self: super:
@@ -230,7 +230,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   Additionally, packages flashplayer and hal-flash were removed along with the `services.flashpolicyd` module.
 
-- The `security.rngd` module has been removed. It was disabled by default in 20.09 as it was functionally redundant with krngd in the linux kernel. It is not necessary for any device that the kernel recognises as an hardware RNG, as it will automatically run the krngd task to periodically collect random data from the device and mix it into the kernel\'s RNG.
+- The `security.rngd` module has been removed. It was disabled by default in 20.09 as it was functionally redundant with krngd in the linux kernel. It is not necessary for any device that the kernel recognises as an hardware RNG, as it will automatically run the krngd task to periodically collect random data from the device and mix it into the kernel's RNG.
 
   The default SMTP port for GitLab has been changed to `25` from its previous default of `465`. If you depended on this default, you should now set the [services.gitlab.smtp.port](options.html#opt-services.gitlab.smtp.port) option.
 
@@ -272,11 +272,11 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `environment.defaultPackages` now includes the nano package. If pkgs.nano is not added to the list, make sure another editor is installed and the `EDITOR` environment variable is set to it. Environment variables can be set using `environment.variables`.
 
-- `services.minio.dataDir` changed type to a list of paths, required for specifiyng multiple data directories for using with erasure coding. Currently, the service doesn\'t enforce nor checks the correct number of paths to correspond to minio requirements.
+- `services.minio.dataDir` changed type to a list of paths, required for specifying multiple data directories for using with erasure coding. Currently, the service doesn't enforce nor checks the correct number of paths to correspond to minio requirements.
 
 - All CUDA toolkit versions prior to CUDA 10 have been removed.
 
-- The kbdKeymaps package was removed since dvp and neo are now included in kbd. If you want to use the Programmer Dvorak Keyboard Layout, you have to use `dvorak-programmer` in `console.keyMap` now instead of `dvp`. In `services.xserver.xkbVariant` it\'s still `dvp`.
+- The kbdKeymaps package was removed since dvp and neo are now included in kbd. If you want to use the Programmer Dvorak Keyboard Layout, you have to use `dvorak-programmer` in `console.keyMap` now instead of `dvp`. In `services.xserver.xkbVariant` it's still `dvp`.
 
 - The babeld service is now being run as an unprivileged user. To achieve that the module configures `skip-kernel-setup true` and takes care of setting forwarding and rp_filter sysctls by itself as well as for each interface in `services.babeld.interfaces`.
 
@@ -286,7 +286,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - Instead of determining `services.radicale.package` automatically based on `system.stateVersion`, the latest version is always used because old versions are not officially supported.
 
-  Furthermore, Radicale\'s systemd unit was hardened which might break some deployments. In particular, a non-default `filesystem_folder` has to be added to `systemd.services.radicale.serviceConfig.ReadWritePaths` if the deprecated `services.radicale.config` is used.
+  Furthermore, Radicale's systemd unit was hardened which might break some deployments. In particular, a non-default `filesystem_folder` has to be added to `systemd.services.radicale.serviceConfig.ReadWritePaths` if the deprecated `services.radicale.config` is used.
 
 - In the `security.acme` module, use of `--reuse-key` parameter for Lego has been removed. It was introduced for HKPK, but this security feature is now deprecated. It is a better security practice to rotate key pairs instead of always keeping the same. If you need to keep this parameter, you can add it back using `extraLegoRenewFlags` as an option for the appropriate certificate.
 
@@ -294,13 +294,13 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `stdenv.lib` has been deprecated and will break eval in 21.11. Please use `pkgs.lib` instead. See [\#108938](https://github.com/NixOS/nixpkgs/issues/108938) for details.
 
-- [GNURadio](https://www.gnuradio.org/) has a `pkgs` attribute set, and there\'s a `gnuradio.callPackage` function that extends `pkgs` with a `mkDerivation`, and a `mkDerivationWith`, like Qt5. Now all `gnuradio.pkgs` are defined with `gnuradio.callPackage` and some packages that depend on gnuradio are defined with this as well.
+- [GNURadio](https://www.gnuradio.org/) has a `pkgs` attribute set, and there's a `gnuradio.callPackage` function that extends `pkgs` with a `mkDerivation`, and a `mkDerivationWith`, like Qt5. Now all `gnuradio.pkgs` are defined with `gnuradio.callPackage` and some packages that depend on gnuradio are defined with this as well.
 
 - [Privoxy](https://www.privoxy.org/) has been updated to version 3.0.32 (See [announcement](https://lists.privoxy.org/pipermail/privoxy-announce/2021-February/000007.html)). Compared to the previous release, Privoxy has gained support for HTTPS inspection (still experimental), Brotli decompression, several new filters and lots of bug fixes, including security ones. In addition, the package is now built with compression and external filters support, which were previously disabled.
 
   Regarding the NixOS module, new options for HTTPS inspection have been added and `services.privoxy.extraConfig` has been replaced by the new [services.privoxy.settings](options.html#opt-services.privoxy.settings) (See [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) for the motivation).
 
-- [Kodi](https://kodi.tv/) has been updated to version 19.1 \"Matrix\". See the [announcement](https://kodi.tv/article/kodi-19-0-matrix-release) for further details.
+- [Kodi](https://kodi.tv/) has been updated to version 19.1 "Matrix". See the [announcement](https://kodi.tv/article/kodi-19-0-matrix-release) for further details.
 
 - The `services.packagekit.backend` option has been removed as it only supported a single setting which would always be the default. Instead new [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) compliant [services.packagekit.settings](options.html#opt-services.packagekit.settings) and [services.packagekit.vendorSettings](options.html#opt-services.packagekit.vendorSettings) options have been introduced.
 
@@ -316,13 +316,13 @@ When upgrading from a previous release, please be aware of the following incompa
 
   If this option is disabled, default MTA config becomes not set and you should set the options in `services.mailman.settings.mta` according to the desired configuration as described in [Mailman documentation](https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html).
 
-- The default-version of `nextcloud` is nextcloud21. Please note that it\'s _not_ possible to upgrade `nextcloud` across multiple major versions! This means that it\'s e.g. not possible to upgrade from nextcloud18 to nextcloud20 in a single deploy and most `20.09` users will have to upgrade to nextcloud20 first.
+- The default-version of `nextcloud` is nextcloud21. Please note that it's _not_ possible to upgrade `nextcloud` across multiple major versions! This means that it's e.g. not possible to upgrade from nextcloud18 to nextcloud20 in a single deploy and most `20.09` users will have to upgrade to nextcloud20 first.
 
   The package can be manually upgraded by setting [services.nextcloud.package](options.html#opt-services.nextcloud.package) to nextcloud21.
 
 - The setting [services.redis.bind](options.html#opt-services.redis.bind) defaults to `127.0.0.1` now, making Redis listen on the loopback interface only, and not all public network interfaces.
 
-- NixOS now emits a deprecation warning if systemd\'s `StartLimitInterval` setting is used in a `serviceConfig` section instead of in a `unitConfig`; that setting is deprecated and now undocumented for the service section by systemd upstream, but still effective and somewhat buggy there, which can be confusing. See [\#45785](https://github.com/NixOS/nixpkgs/issues/45785) for details.
+- NixOS now emits a deprecation warning if systemd's `StartLimitInterval` setting is used in a `serviceConfig` section instead of in a `unitConfig`; that setting is deprecated and now undocumented for the service section by systemd upstream, but still effective and somewhat buggy there, which can be confusing. See [\#45785](https://github.com/NixOS/nixpkgs/issues/45785) for details.
 
   All services should use [systemd.services._name_.startLimitIntervalSec](options.html#opt-systemd.services._name_.startLimitIntervalSec) or `StartLimitIntervalSec` in [systemd.services._name_.unitConfig](options.html#opt-systemd.services._name_.unitConfig) instead.
 
@@ -357,7 +357,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   `services.unbound.forwardAddresses` and `services.unbound.allowedAccess` have also been changed to use the new settings interface. You can follow the instructions when executing `nixos-rebuild` to upgrade your configuration to use the new interface.
 
-- The `services.dnscrypt-proxy2` module now takes the upstream\'s example configuration and updates it with the user\'s settings. An option has been added to restore the old behaviour if you prefer to declare the configuration from scratch.
+- The `services.dnscrypt-proxy2` module now takes the upstream's example configuration and updates it with the user's settings. An option has been added to restore the old behaviour if you prefer to declare the configuration from scratch.
 
 - NixOS now defaults to the unified cgroup hierarchy (cgroupsv2). See the [Fedora Article for 31](https://www.redhat.com/sysadmin/fedora-31-control-group-v2) for details on why this is desirable, and how it impacts containers.
 
@@ -367,15 +367,15 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - GNOME users may wish to delete their `~/.config/pulse` due to the changes to stream routing logic. See [PulseAudio bug 832](https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832) for more information.
 
-- The zookeeper package does not provide `zooInspector.sh` anymore, as that \"contrib\" has been dropped from upstream releases.
+- The zookeeper package does not provide `zooInspector.sh` anymore, as that "contrib" has been dropped from upstream releases.
 
-- In the ACME module, the data used to build the hash for the account directory has changed to accomodate new features to reduce account rate limit issues. This will trigger new account creation on the first rebuild following this update. No issues are expected to arise from this, thanks to the new account creation handling.
+- In the ACME module, the data used to build the hash for the account directory has changed to accommodate new features to reduce account rate limit issues. This will trigger new account creation on the first rebuild following this update. No issues are expected to arise from this, thanks to the new account creation handling.
 
-- [users.users._name_.createHome](options.html#opt-users.users._name_.createHome) now always ensures home directory permissions to be `0700`. Permissions had previously been ignored for already existing home directories, possibly leaving them readable by others. The option\'s description was incorrect regarding ownership management and has been simplified greatly.
+- [users.users._name_.createHome](options.html#opt-users.users._name_.createHome) now always ensures home directory permissions to be `0700`. Permissions had previously been ignored for already existing home directories, possibly leaving them readable by others. The option's description was incorrect regarding ownership management and has been simplified greatly.
 
 - When defining a new user, one of [users.users._name_.isNormalUser](options.html#opt-users.users._name_.isNormalUser) and [users.users._name_.isSystemUser](options.html#opt-users.users._name_.isSystemUser) is now required. This is to prevent accidentally giving a UID above 1000 to system users, which could have unexpected consequences, like running user activation scripts for system users. Note that users defined with an explicit UID below 500 are exempted from this check, as [users.users._name_.isSystemUser](options.html#opt-users.users._name_.isSystemUser) has no effect for those.
 
-- The `security.apparmor` module, for the [AppArmor](https://gitlab.com/apparmor/apparmor/-/wikis/Documentation) Mandatory Access Control system, has been substantialy improved along with related tools, so that module maintainers can now more easily write AppArmor profiles for NixOS. The most notable change on the user-side is the new option [security.apparmor.policies](options.html#opt-security.apparmor.policies), replacing the previous `profiles` option to provide a way to disable a profile and to select whether to confine in enforce mode (default) or in complain mode (see `journalctl -b --grep apparmor`). Security-minded users may also want to enable [security.apparmor.killUnconfinedConfinables](options.html#opt-security.apparmor.killUnconfinedConfinables), at the cost of having some of their processes killed when updating to a NixOS version introducing new AppArmor profiles.
+- The `security.apparmor` module, for the [AppArmor](https://gitlab.com/apparmor/apparmor/-/wikis/Documentation) Mandatory Access Control system, has been substantially improved along with related tools, so that module maintainers can now more easily write AppArmor profiles for NixOS. The most notable change on the user-side is the new option [security.apparmor.policies](options.html#opt-security.apparmor.policies), replacing the previous `profiles` option to provide a way to disable a profile and to select whether to confine in enforce mode (default) or in complain mode (see `journalctl -b --grep apparmor`). Security-minded users may also want to enable [security.apparmor.killUnconfinedConfinables](options.html#opt-security.apparmor.killUnconfinedConfinables), at the cost of having some of their processes killed when updating to a NixOS version introducing new AppArmor profiles.
 
 - The GNOME desktop manager once again installs gnome.epiphany by default.
 
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 e673d6721a38..159881a0ac4c 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -164,6 +164,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [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).
 
+- [twingate](https://docs.twingate.com/docs/linux), a high performance, easy to use zero trust solution that enables access to private resources from any device with better security than a VPN.
+
 ## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
 
 - The NixOS VM test framework, `pkgs.nixosTest`/`make-test-python.nix` (`pkgs.testers.nixosTest` since 22.05), now requires detaching commands such as `succeed("foo &")` and `succeed("foo | xclip -i")` to close stdout.
@@ -233,7 +235,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `erigon` ethereum node has moved to a new database format in `2021-05-04`, and requires a full resync
 
-- The `erigon` ethereum node has moved it's database location in `2021-08-03`, users upgrading must manually move their chaindata (see [release notes](https://github.com/ledgerwatch/erigon/releases/tag/v2021.08.03)).
+- The `erigon` ethereum node has moved its database location in `2021-08-03`, users upgrading must manually move their chaindata (see [release notes](https://github.com/ledgerwatch/erigon/releases/tag/v2021.08.03)).
 
 - [users.users.&lt;name&gt;.group](options.html#opt-users.users._name_.group) no longer defaults to `nogroup`, which was insecure. Out-of-tree modules are likely to require adaptation: instead of
   ```nix
@@ -373,7 +375,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `programs.neovim.runtime` switched to a `linkFarm` internally, making it impossible to use wildcards in the `source` argument.
 
-- The `openrazer` and `openrazer-daemon` packages as well as the `hardware.openrazer` module now require users to be members of the `openrazer` group instead of `plugdev`. With this change, users no longer need be granted the entire set of `plugdev` group permissions, which can include permissions other than those required by `openrazer`. This is desirable from a security point of view. The setting [`harware.openrazer.users`](options.html#opt-services.hardware.openrazer.users) can be used to add users to the `openrazer` group.
+- The `openrazer` and `openrazer-daemon` packages as well as the `hardware.openrazer` module now require users to be members of the `openrazer` group instead of `plugdev`. With this change, users no longer need be granted the entire set of `plugdev` group permissions, which can include permissions other than those required by `openrazer`. This is desirable from a security point of view. The setting [`hardware.openrazer.users`](options.html#opt-services.hardware.openrazer.users) can be used to add users to the `openrazer` group.
 
 - The fontconfig service's dpi option has been removed.
   Fontconfig should use Xft settings by default so there's no need to override one value in multiple places.
@@ -427,7 +429,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - 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`
+- The default GNAT version has been changed: The `gnat` attribute now points to `gnat12`
   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.
@@ -437,6 +439,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 - `/usr` will always be included in the initial ramdisk. See the `fileSystems.<name>.neededForBoot` option.
   If any files exist under `/usr` (which is not typical for NixOS), they will be included in the initial ramdisk, increasing its size to a possibly problematic extent.
 
+- `pkgs.haskell-language-server` will now by default be linked dynamically to improve TemplateHaskell compatibility. To mitigate the increased closure size it will now by default only support our current default ghc (at the moment 9.0.2). Add other ghc versions via e.g. `pkgs.haskell-language-server.override { supportedGhcVersions = [ "90" "92" ]; }`.
+
 ## Other Notable Changes {#sec-release-21.11-notable-changes}
 
 
@@ -573,3 +577,5 @@ In addition to numerous new and upgraded packages, this release has the followin
 - 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`.
+
+- The sets `haskell.packages` and `haskell.compiler` now contain for every ghc version an attribute with the minor version dropped. E.g. for `ghc8107` there also now exists `ghc810`. Those attributes point to the same compilers and packagesets but have the advantage that e.g. `ghc92` stays stable when we update from `ghc925` to `ghc926`.
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md
index 2d2140d92d59..d4581fe9441c 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -9,8 +9,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 - Nix has been updated from 2.3 to 2.8. This mainly brings experimental support
   for Flakes, but also marks the `nix` command as experimental which now has to
   be enabled via the configuration explicitly. For more information and
-  instructions for upgrades, see the 
-  relase notes for [nix-2.4](https://nixos.org/manual/nix/stable/release-notes/rl-2.4.html),  
+  instructions for upgrades, see the
+  release notes for [nix-2.4](https://nixos.org/manual/nix/stable/release-notes/rl-2.4.html),
   [nix-2.5](https://nixos.org/manual/nix/stable/release-notes/rl-2.5.html),
   [nix-2.6](https://nixos.org/manual/nix/stable/release-notes/rl-2.6.html),
   [nix-2.7](https://nixos.org/manual/nix/stable/release-notes/rl-2.7.html) and
@@ -30,7 +30,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - Systemd has been upgraded to the version 250.
 
-- Pulseaudio has been updated to version 15.0 and now optionally 
+- Pulseaudio has been updated to version 15.0 and now optionally
   [supports additional Bluetooth audio codecs](https://www.freedesktop.org/wiki/Software/PulseAudio/Notes/15.0/#supportforldacandaptxbluetoothcodecsplussbcxqsbcwithhigher-qualityparameters)
   such as aptX or LDAC, with codec switching available in `pavucontrol`. This
   feature is disabled by default, but can be enabled with the option
@@ -50,7 +50,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   settings for many certificates at once. This also opens up the option to use
   DNS-01 validation when using `enableACME` web server virtual hosts (e.g.
   `services.nginx.virtualHosts.*.enableACME`).
-  
+
 ## New Services {#sec-release-22.05-new-services}
 
 - [1password](https://1password.com/), command-lines and graphic interface for 1Password. Available as [programs._1password](#opt-programs._1password.enable) and [programs._1password-gui](#opt-programs._1password.enable).
@@ -65,7 +65,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm), a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Available as [services.archisteamfarm](#opt-services.archisteamfarm.enable).
 
-- [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at [services.baget](#opt-services.baget.enable).
+- [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at services.baget.
 
 - [bird-lg](https://github.com/xddxdd/bird-lg-go), a BGP looking glass for Bird Routing. Available as [services.bird-lg](#opt-services.bird-lg.package).
 
@@ -107,7 +107,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [kanidm](https://kanidm.github.io/kanidm/stable/), an identity management server written in Rust. Available as [services.kanidm](#opt-services.kanidm.enableServer)
 
-- [Maddy](https://maddy.email/), a free an open source mail server. Availabe as [services.maddy](#opt-services.maddy.enable).
+- [Maddy](https://maddy.email/), a free an open source mail server. Available as [services.maddy](#opt-services.maddy.enable).
 
 - [matrix-conduit](https://conduit.rs/), a simple, fast and reliable chat server powered by matrix. Available as [services.matrix-conduit](option.html#opt-services.matrix-conduit.enable).
 
@@ -147,7 +147,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [rstudio-server](https://www.rstudio.com/products/rstudio/#rstudio-server), a browser-based version of the RStudio IDE for the R programming language. Available as [services.rstudio-server](#opt-services.rstudio-server.enable).
 
-- [rtsp-simple-server](https://github.com/aler9/rtsp-simple-server), ready-to-use RTSP / RTMP / HLS server and proxy that allows to read, publish and proxy video and audio streams. Available as [services.rtsp-simple-server](#opt-services.rtsp-simple-server.enable).
+- [mediamtx](https://github.com/aler9/mediamtx), ready-to-use RTSP / RTMP / HLS server and proxy that allows to read, publish and proxy video and audio streams. Available as [services.mediamtx](#opt-services.mediamtx.enable).
 
 - [Snipe-IT](https://snipeitapp.com), a free open source IT asset/license management system. Available as [services.snipe-it](#opt-services.snipe-it.enable).
 
@@ -278,11 +278,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `openldap` (and therefore the slapd LDAP server) were updated to version 2.6.2. The project introduced backwards-incompatible changes, namely the removal of the bdb, hdb, ndb, and shell backends in slapd. Therefore before updating, dump your database `slapcat -n 1` in LDIF format, and reimport it after updating your `services.openldap.settings`, which represents your `cn=config`.
 
-  Additionally with 2.5 the argon2 module was included in the standard distrubtion and renamed from `pw-argon2` to `argon2`. Remember to update your `olcModuleLoad` entry in `cn=config`.
+  Additionally with 2.5 the argon2 module was included in the standard distribution and renamed from `pw-argon2` to `argon2`. Remember to update your `olcModuleLoad` entry in `cn=config`.
 
 - `openssh` has been update to 8.9p1, changing the FIDO security key middleware interface.
 
-- `git` no longer hardcodes the path to openssh' ssh binary to reduce the amount of rebuilds. If you are using git with ssh remotes and do not have a ssh binary in your enviroment consider adding `openssh` to it or switching to `gitFull`.
+- `git` no longer hardcodes the path to openssh' ssh binary to reduce the amount of rebuilds. If you are using git with ssh remotes and do not have a ssh binary in your environment consider adding `openssh` to it or switching to `gitFull`.
 
 - `services.k3s.enable` no longer implies `systemd.enableUnifiedCgroupHierarchy = false`, and will default to the 'systemd' cgroup driver when using `services.k3s.docker = true`.
   This change may require a reboot to take effect, and k3s may not be able to run if the boot cgroup hierarchy does not match its configuration.
@@ -562,7 +562,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 - `pkgs._7zz` is now correctly licensed as LGPL3+ and BSD3 with optional unfree unRAR licensed code
 
 - The `vim.customize` function produced by `vimUtils.makeCustomizable` now has a slightly different interface:
-  * The wrapper now includes everything in the given Vim derivation if `name` is `"vim"` (the default). This makes the `wrapManual` argument obsolete, but this behavior can be overriden by setting the `standalone` argument.
+  * The wrapper now includes everything in the given Vim derivation if `name` is `"vim"` (the default). This makes the `wrapManual` argument obsolete, but this behavior can be overridden by setting the `standalone` argument.
   * All the executables present in the given derivation (or, in `standalone` mode, only the `*vim` ones) are wrapped. This makes the `wrapGui` argument obsolete.
   * The `vimExecutableName` and `gvimExecutableName` arguments were replaced by a single `executableName` argument in which the shell variable `$exe` can be used to refer to the wrapped executable's name.
 
@@ -581,7 +581,15 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `miller` package has been upgraded from 5.10.3 to [6.2.0](https://github.com/johnkerl/miller/releases/tag/v6.2.0). See [What's new in Miller 6](https://miller.readthedocs.io/en/latest/new-in-miller-6).
 
-- MultiMC has been replaced with the fork PolyMC due to upstream developers being hostile to 3rd party package maintainers. PolyMC removes all MultiMC branding and is aimed at providing proper 3rd party packages like the one contained in Nixpkgs. This change affects the data folder where game instances and other save and configuration files are stored. Users with existing installations should rename `~/.local/share/multimc` to `~/.local/share/polymc`. The main config file's path has also moved from `~/.local/share/multimc/multimc.cfg` to `~/.local/share/polymc/polymc.cfg`.
+- MultiMC has been replaced with the fork PrismLauncher due to upstream
+  developers being hostile to 3rd party package maintainers. PrismLauncher
+  removes all MultiMC branding and is aimed at providing proper 3rd party
+  packages like the one contained in Nixpkgs. This change affects the data
+  folder where game instances and other save and configuration files are stored.
+  Users with existing installations should rename `~/.local/share/multimc` to
+  `~/.local/share/PrismLauncher`. The main config file's path has also moved
+  from `~/.local/share/multimc/multimc.cfg` to
+  `~/.local/share/PrismLauncher/prismlauncher.cfg`.
 
 - `systemd-nspawn@.service` settings have been reverted to the default systemd behaviour. User namespaces are now activated by default. If you want to keep running nspawn containers without user namespaces you need to set `systemd.nspawn.<name>.execConfig.PrivateUsers = false`
 
@@ -631,7 +639,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   changes in the database scheme and configuration format.
 
 - Some top-level settings under [services.epgstation](#opt-services.epgstation.enable)
-  is now deprecated because it was redudant due to the same options being
+  is now deprecated because it was redundant due to the same options being
   present in [services.epgstation.settings](#opt-services.epgstation.settings).
 
 - The option `services.epgstation.basicAuth` was removed because basic
@@ -645,7 +653,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   option now expects options for `config.yml` in EPGStation v2.
 
 - Existing data for the [services.epgstation](#opt-services.epgstation.enable)
-  module would have to be backed up prior to the upgrade. To back up exising
+  module would have to be backed up prior to the upgrade. To back up existing
   data to `/tmp/epgstation.bak`, run
   `sudo -u epgstation epgstation run backup /tmp/epgstation.bak`.
   To import that data after to the upgrade, run
@@ -735,11 +743,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 - The configuration portion of the `nix-daemon` module has been reworked and exposed as [nix.settings](options.html#opt-nix-settings):
   * Legacy options have been mapped to the corresponding options under under [nix.settings](options.html#opt-nix.settings) and will be deprecated when NixOS 21.11 reaches end of life.
   * [nix.buildMachines.publicHostKey](options.html#opt-nix.buildMachines.publicHostKey) has been added.
-  
+
 - [`kops`](https://kops.sigs.k8s.io) defaults to 1.23.2, which will enable [Instance Metadata Service Version 2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) and require tokens on new clusters with Kubernetes >= 1.22. This will increase security by default, but may break some types of workloads. The default behaviour for `spec.kubeDNS.nodeLocalDNS.forwardToKubeDNS` has changed from `true` to `false`. Cilium now has `disable-cnp-status-updates: true` by default. Set this to false if you rely on the CiliumNetworkPolicy status fields. Support for Kubernetes 1.17, the Lyft CNI, Weave CNI on Kubernetes >= 1.23, CentOS 7 and 8, Debian 9, RHEL 7, and Ubuntu 16.05 (Xenial) has been removed. See the [1.22 release notes](https://kops.sigs.k8s.io/releases/1.22-notes/) and [1.23 release notes](https://kops.sigs.k8s.io/releases/1.23-notes/) for more details, including other significant changes.
 
 - Mattermost has been upgraded to extended support version 6.3 as the previously
-  packaged extended support version 5.37 is [reaching end of life](https://docs.mattermost.com/upgrade/extended-support-release.html). 
+  packaged extended support version 5.37 is [reaching end of life](https://docs.mattermost.com/upgrade/extended-support-release.html).
   Migration may take some time, see the [changelog](https://docs.mattermost.com/install/self-managed-changelog.html#release-v6-3-extended-support-release)
   and [important upgrade notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html).
 
@@ -754,14 +762,14 @@ In addition to numerous new and upgraded packages, this release has the followin
   By default auto-upgrade will now run immediately if it would have been triggered at least
   once during the time when the timer was inactive.
 
-- Mastodon now uses `services.redis.servers` to start a new redis server, instead of using a global redis server. 
+- Mastodon now uses `services.redis.servers` to start a new redis server, instead of using a global redis server.
   This improves compatibility with other services that use redis.
-  
-  Note that this will recreate the redis database, although according to the [Mastodon docs](https://docs.joinmastodon.org/admin/backups/), 
+
+  Note that this will recreate the redis database, although according to the [Mastodon docs](https://docs.joinmastodon.org/admin/backups/),
   this is almost harmless:
-  > Losing the Redis database is almost harmless: The only irrecoverable data will be the contents of the Sidekiq queues and scheduled retries of previously failed jobs. 
+  > Losing the Redis database is almost harmless: The only irrecoverable data will be the contents of the Sidekiq queues and scheduled retries of previously failed jobs.
   >  The home and list feeds are stored in Redis, but can be regenerated with tootctl.
-  
+
   If you do want to save the redis database, you can use the following commands:
   ```bash
   redis-cli save
@@ -796,7 +804,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 - 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.
+  backwards compatibility, but will be removed at a later date.
 
 - The `unifi` package was switched from `unifi6` to `unifi7`.
   Direct downgrades from Unifi 7 to Unifi 6 are not possible and require restoring from a backup made by Unifi 6.
@@ -898,8 +906,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [services.logrotate.enable](#opt-services.logrotate.enable) now defaults to true if any rotate path has
   been defined, and some paths have been added by default.
-- The logrotate module also has been updated to freeform syntax: [services.logrotate.paths](#opt-services.logrotate.paths)
-  and [services.logrotate.extraConfig](#opt-services.logrotate.extraConfig) will work, but issue deprecation
+- The logrotate module also has been updated to freeform syntax: `services.logrotate.paths`
+  and `services.logrotate.extraConfig` will work, but issue deprecation
   warnings and [services.logrotate.settings](#opt-services.logrotate.settings) should now be used instead.
 
 - `security.pam.ussh` has been added, which allows authorizing PAM sessions based on SSH _certificates_ held within an SSH agent, using [pam-ussh](https://github.com/uber/pam-ussh).
@@ -972,7 +980,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   or `wl*` with priority 99 (which means that it doesn't have any effect if such an interface is matched
   by a `.network-`unit with a lower priority). In case of scripted networking, no behavior
   was changed.
-  
+
 - The new [`postgresqlTestHook`](https://nixos.org/manual/nixpkgs/stable/#sec-postgresqlTestHook) runs a PostgreSQL server for the duration of package checks.
 
 - `zfs` was updated from 2.1.4 to 2.1.5, enabling it to be used with Linux kernel 5.18.
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2211.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2211.section.md
index 8d9d2679c8f4..97a305573501 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -1,24 +1,53 @@
-# Release 22.11 (“Raccoon”, 2022.11/??) {#sec-release-22.11}
+# Release 22.11 (“Raccoon”, 2022.11/30) {#sec-release-22.11}
 
-Support is planned until the end of June 2023, handing over to 23.05.
+The NixOS release team is happy to announce a new version of NixOS 22.11. NixOS is a Linux distribution, whose set of packages can also be used on other Linux systems and macOS.
+
+This release is supported until the end of June 2023, handing over to NixOS 23.05.
+
+To upgrade to the latest release follow the [upgrade chapter](#sec-upgrading).
 
 ## Highlights {#sec-release-22.11-highlights}
 
-In addition to numerous new and upgraded packages, this release has the following highlights:
+In addition to numerous new and upgraded packages, this release includes the following highlights:
+
+- Software that uses the `crypt` password hashing API is now using the implementation provided by [`libxcrypt`](https://github.com/besser82/libxcrypt) instead of glibc's, which enables support for more secure algorithms.
+  - Support for algorithms that `libxcrypt` [does not consider strong](https://github.com/besser82/libxcrypt/blob/v4.4.28/lib/hashes.conf#L41) are **deprecated** as of this release, and will be removed in NixOS 23.05.
+  - This includes system login passwords. Given this, we **strongly encourage** all users to update their system passwords, as you will be unable to login if password hashes are not migrated by the time their support is removed.
+    - When using `users.users.<name>.hashedPassword` to configure user passwords, run `mkpasswd`, and use the yescrypt hash that is provided as the new value.
+    - On the other hand, for interactively configured user passwords, simply re-set the passwords for all users with `passwd`.
+    - This release introduces warnings for the use of deprecated hash algorithms for both methods of configuring passwords. To make sure you migrated correctly, run `nixos-rebuild switch`.
+
+- The NixOS documentation is now generated from markdown. While docbook is still part of the documentation build process, it's a big step towards the full migration.
+
+- `aarch64-linux` is now included in the `nixos-22.11` and `nixos-22.11-small` channels. This means that when those channel update, both `x86_64-linux` and `aarch64-linux` will be available in the binary cache.
+
+- `aarch64-linux` ISOs are now available on the [downloads page](https://nixos.org/download.html).
+
+- `nsncd` is now available as a replacement of `nscd`.
+
+  `nscd` is responsible for resolving hostnames, users and more in NixOS and has been a long standing source of bugs, such as sporadic network freezes.
+
+  More context in this [issue](https://github.com/NixOS/nixpkgs/issues/135888).
+
+  Help us test the new implementation by setting `services.nscd.enableNsncd` to `true`.
+
+  We plan to use `nsncd` by default in NixOS 23.05.
+
+- Linode cloud images are now supported by importing `${modulesPath}/virtualisation/linode-image.nix` and accessing `system.build.linodeImage` on the output.
 
-- During cross-compilation, tests are now executed if the test suite can be executed
-  by the build platform. This is the case when doing “native” cross-compilation
-  where the build and host platforms are largely the same, but the nixpkgs' cross
-  compilation infrastructure is used, e.g. `pkgsStatic` and `pkgsLLVM`. Another
-  possibility is that the build platform is a superset of the host platform, e.g. when
-  cross-compiling from `x86_64-unknown-linux` to `i686-unknown-linux`.
-  The predicate gating test suite execution is the newly added `canExecute`
-  predicate: You can e.g. check if `stdenv.buildPlatform` can execute binaries
-  built for `stdenv.hostPlatform` (i.e. produced by `stdenv.cc`) by evaluating
-  `stdenv.buildPlatform.canExecute stdenv.hostPlatform`.
+- `hardware.nvidia` has a new option, `hardware.nvidia.open`, that can be used to enable the usage of NVIDIA's open-source kernel driver. Note that the driver's support for GeForce and Workstation GPUs is still alpha quality, see [the release announcement](https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/) for more information.
 
-- The `nixpkgs.hostPlatform` and `nixpkgs.buildPlatform` options have been added.
-  These cover and override the `nixpkgs.{system,localSystem,crossSystem}` options.
+- The `emacs` package now makes use of native compilation which means:
+  - Emacs packages from Nixpkgs, builtin or not, will do native compilation ahead of time so you can enjoy the benefit of native compilation without compiling them on you machine;
+  - Emacs packages from somewhere else, e.g. `package-install`, will perform asynchronously deferred native compilation. If you do not want this, maybe to avoid CPU consumption for compilation, you can use `(setq native-comp-deferred-compilation nil)` to disable it while still benefiting from native compilation for packages from Nixpkgs.
+
+## Internal changes {#sec-release-22.11-internal}
+
+- Haskell `ghcWithPackages` is now up to 15 times faster to evaluate, thanks to changing `lib.closePropagation` from a quadratic to linear complexity. Please see backward incompatibilities notes below. <https://github.com/NixOS/nixpkgs/pull/194391>
+
+- For cross-compilation targets that can also run on the building machine, we now run tests. This, for example, is the case for the `pkgsStatic` and `pkgsLLVM` package sets or i686 packages on `x86_64` machines.
+
+- To simplify cross-compilation in NixOS, this release introduces the `nixpkgs.hostPlatform` and `nixpkgs.buildPlatform` options. These cover and override the `nixpkgs.{system,localSystem,crossSystem}` options.
 
    - `hostPlatform` is the platform or "`system`" string of the NixOS system
      described by the configuration.
@@ -35,75 +64,56 @@ In addition to numerous new and upgraded packages, this release has the followin
   for a transition period so that in time the ecosystem can switch without
   breaking compatibility with any supported NixOS release.
 
-- `nixos-generate-config` now generates configurations that can be built in pure
-  mode. This is achieved by setting the new `nixpkgs.hostPlatform` option.
-
-  You may have to unset the `system` parameter in `lib.nixosSystem`, or similarly
-  remove definitions of the `nixpkgs.{system,localSystem,crossSystem}` options.
-
-  Alternatively, you can remove the `hostPlatform` line and use NixOS like you
-  would in NixOS 22.05 and earlier.
-
-- PHP now defaults to PHP 8.1, updated from 8.0.
-
-- Cinnamon has been updated to 5.4.
-
-- `hardware.nvidia` has a new option `open` that can be used to opt in the opensource version of NVIDIA kernel driver. Note that the driver's support for GeForce and Workstation GPUs is still alpha quality, see [NVIDIA Releases Open-Source GPU Kernel Modules](https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/) for the official announcement.
-
-<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
-
-## New Services {#sec-release-22.11-new-services}
-
-- [appvm](https://github.com/jollheef/appvm), Nix based app VMs. Available as [virtualisation.appvm](options.html#opt-virtualisation.appvm.enable).
-- [syncstorage-rs](https://github.com/mozilla-services/syncstorage-rs), a self-hostable sync server for Firefox. Available as [services.firefox-syncserver](options.html#opt-services.firefox-syncserver.enable).
+## Notable version updates {#sec-release-22.11-version-updates}
 
-- [dragonflydb](https://dragonflydb.io/), a modern replacement for Redis and Memcached. Available as [services.dragonflydb](#opt-services.dragonflydb.enable).
-
-- [Komga](https://komga.org/), a free and open source comics/mangas media server. Available as [services.komga](#opt-services.komga.enable).
+- Nix has been upgraded from v2.8.1 to v2.11.0. For more information, please see the release notes for [2.9](https://nixos.org/manual/nix/stable/release-notes/rl-2.9.html), [2.10](https://nixos.org/manual/nix/stable/release-notes/rl-2.10.html) and [2.11](https://nixos.org/manual/nix/stable/release-notes/rl-2.11.html).
 
-- [HBase cluster](https://hbase.apache.org/), a distributed, scalable, big data store. Available as [services.hadoop.hbase](options.html#opt-services.hadoop.hbase.enable).
+- OpenSSL now defaults to OpenSSL 3, updated from 1.1.1.
 
-- [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle.
-  Available as [services.infnoise](options.html#opt-services.infnoise.enable).
+- GNOME has been upgraded to version 43. Please see the [release notes](https://release.gnome.org/43/) for details.
 
-- [kanata](https://github.com/jtroo/kanata), a tool to improve keyboard comfort and usability with advanced customization.
-  Available as [services.kanata](options.html#opt-services.kanata.enable).
+- KDE Plasma has been upgraded from v5.24 to v5.26. Please see the release notes for [v5.25](https://kde.org/announcements/plasma/5/5.25.0/) and [v5.26](https://kde.org/announcements/plasma/5/5.26.0/) for more details on the included changes.
 
-- [persistent-evdev](https://github.com/aiberia/persistent-evdev), a daemon to add virtual proxy devices that mirror a physical input device but persist even if the underlying hardware is hot-plugged. Available as [services.persistent-evdev](#opt-services.persistent-evdev.enable).
+- Cinnamon has been updated to 5.4, and the Cinnamon module now defaults to
+  Blueman as the Bluetooth manager and slick-greeter as the LightDM greeter, to match upstream.
 
-- [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable).
-
-- [expressvpn](https://www.expressvpn.com), the CLI client for ExpressVPN. Available as [services.expressvpn](#opt-services.expressvpn.enable).
-
-- [Grafana Tempo](https://www.grafana.com/oss/tempo/), a distributed tracing store. Available as [services.tempo](#opt-services.tempo.enable).
+- PHP now defaults to PHP 8.1, updated from 8.0.
 
-- [Patroni](https://github.com/zalando/patroni), a template for PostgreSQL HA with ZooKeeper, etcd or Consul.
-Available as [services.patroni](options.html#opt-services.patroni.enable).
+- Perl has been updated to 5.36, and its core module `HTTP::Tiny` was patched to verify SSL/TLS certificates by default.
 
-<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+- Python now defaults to 3.10, updated from 3.9.
 
 ## Backward Incompatibilities {#sec-release-22.11-incompatibilities}
 
+- Nixpkgs now requires Nix 2.3 or newer.
+
 - The `isCompatible` predicate checking CPU compatibility is no longer exposed
   by the platform sets generated using `lib.systems.elaborate`. In most cases
   you will want to use the new `canExecute` predicate instead which also
-  considers the kernel / syscall interface. It is briefly described in the
-  release's [highlights section](#sec-release-22.11-highlights).
+  takes the kernel / syscall interface into account.
   `lib.systems.parse.isCompatible` still exists, but has changed semantically:
   Architectures with differing endianness modes are *no longer considered compatible*.
 
 - `ngrok` has been upgraded from 2.3.40 to 3.0.4. Please see [the upgrade guide](https://ngrok.com/docs/guides/upgrade-v2-v3)
   and [changelog](https://ngrok.com/docs/ngrok-agent/changelog). Notably, breaking changes are that the config file format has
-  changed and support for single hypen arguments was dropped.
+  changed and support for single hyphen arguments was dropped.
+
+- `i18n.supportedLocales` is now only generated with the locales set in `i18n.defaultLocale` and `i18n.extraLocaleSettings`.
+  - This reduces the final system closure size by up to 200MB.
+  - If you require all locales installed, set the option to ``[ "all" ]``.
 
-- `i18n.supportedLocales` is now by default only generated with the locales set in `i18n.defaultLocale` and `i18n.extraLocaleSettings`.
-  This got partially copied over from the minimal profile and reduces the final system size by up to 200MB.
-  If you require all locales installed set the option to ``[ "all" ]``.
+- Deprecated settings `logrotate.paths` and `logrotate.extraConfig` have
+  been removed. Please convert any uses to
+  [services.logrotate.settings](#opt-services.logrotate.settings) instead.
 
 - The `isPowerPC` predicate, found on `platform` attrsets (`hostPlatform`, `buildPlatform`, `targetPlatform`, etc) has been removed in order to reduce confusion.  The predicate was was defined such that it matches only the 32-bit big-endian members of the POWER/PowerPC family, despite having a name which would imply a broader set of systems.  If you were using this predicate, you can replace `foo.isPowerPC` with `(with foo; isPower && is32bit && isBigEndian)`.
 
 - The `fetchgit` fetcher now uses [cone mode](https://www.git-scm.com/docs/git-sparse-checkout/2.37.0#_internalscone_mode_handling) by default for sparse checkouts. [Non-cone mode](https://www.git-scm.com/docs/git-sparse-checkout/2.37.0#_internalsnon_cone_problems) can be enabled by passing `nonConeMode = true`, but note that non-cone mode is deprecated and this option may be removed alongside a future Git update without notice.
 
+- The `fetchgit` fetcher supports sparse checkouts via the `sparseCheckout` option. This used to accept a multi-line string with directories/patterns to check out, but now requires a list of strings.
+
+- `openssh` was updated to version 9.1, disabling the generation of DSA keys when using `ssh-keygen -A` as they are insecure. Also, `SetEnv` directives in `ssh_config` and `sshd_config` are now first-match-wins.
+
 - `bsp-layout` no longer uses the command `cycle` to switch to other window layouts, as it got replaced by the commands `previous` and `next`.
 
 - The Barco ClickShare driver/client package `pkgs.clickshare-csc1` and the option `programs.clickshare-csc1.enable` have been removed,
@@ -113,63 +123,414 @@ Available as [services.patroni](options.html#opt-services.patroni.enable).
 - `services.hbase` has been renamed to `services.hbase-standalone`.
   For production HBase clusters, use `services.hadoop.hbase` instead.
 
+- The `p4` package now only includes the open-source Perforce Helix Core command-line client and APIs. It no longer installs the unfree Helix Core Server binaries `p4d`, `p4broker`, and `p4p`. To install the Helix Core Server binaries, use the `p4d` package instead.
+
+- The OpenSSL extension for the PHP interpreter used by Nextcloud is built against OpenSSL 1.1 if
+  [](#opt-system.stateVersion) is below `22.11`. This is to make sure that people using [server-side encryption](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html)
+  don't lose access to their files.
+
+  In any other case, it's safe to use OpenSSL 3 for PHP's OpenSSL extension. This can be done by setting
+  [](#opt-services.nextcloud.enableBrokenCiphersForSSE) to `false`.
+
+- The `coq` package and versioned variants starting at `coq_8_14` no
+  longer include CoqIDE, which is now available through
+  `coqPackages.coqide`. It is still possible to get CoqIDE as part of
+  the `coq` package by overriding the `buildIde` argument of the
+  derivation.
+
 - PHP 7.4 is no longer supported due to upstream not supporting this
   version for the entire lifecycle of the 22.11 release.
 
-- `pkgs.cosign` does not provide the `cosigned` binary anymore.
+- The ipfs package and module were renamed to kubo. The kubo module now uses an RFC42-style `settings` option instead of `extraConfig` and the `gatewayAddress`, `apiAddress` and `swarmAddress` options were renamed. Using the old names will print a warning but still work.
+
+- `pkgs.cosign` does not provide the `cosigned` binary anymore. The `sget` binary has been moved into its own package.
+
+- Emacs now uses the Lucid toolkit by default instead of GTK because of stability and compatibility issues.
+  Users who still wish to remain using GTK can do so by using `emacs-gtk`.
+
+- `kanidm` has been updated to 1.1.0-alpha.10 and now requires a TLS certificate and key. It will always start `https` and-–-if enabled-–-an LDAPS server and no HTTP and LDAP server anymore.
 
 - riak package removed along with `services.riak` module, due to lack of maintainer to update the package.
 
+- ppd files in `pkgs.cups-drv-rastertosag-gdi` are now gzipped.  If you refer to such a ppd file with its path (e.g. via [hardware.printers.ensurePrinters](options.html#opt-hardware.printers.ensurePrinters)) you will need to append `.gz` to the path.
+
 - xow package removed along with the `hardware.xow` module, due to the project being deprecated in favor of `xone`,  which is available via the `hardware.xone` module.
 
+- dd-agent package removed along with the `services.dd-agent` module, due to the project being deprecated in favor of `datadog-agent`,  which is available via the `services.datadog-agent` module.
+
+- `teleport` has been upgraded to major version 10. Please see upstream [upgrade instructions](https://goteleport.com/docs/ver/10.0/management/operations/upgrading/) and [release notes](https://goteleport.com/docs/ver/10.0/changelog/#1000).
+
+- `lib.closePropagation` now needs that all gathered sets have an `outPath` attribute.
+
+- lemmy module option `services.lemmy.settings.database.createLocally`
+  moved to `services.lemmy.database.createLocally`.
+
 - virtlyst package and `services.virtlyst` module removed, due to lack of maintainers.
 
+- The `nix.checkConfig` option now fully disables the config check. The new `nix.checkAllErrors` option behaves like `nix.checkConfig`  previously did.
+
+- `generateOptparseApplicativeCompletions` and `generateOptparseApplicativeCompletion` from `haskell.lib.compose`
+  (and `haskell.lib`) have been deprecated in favor of `generateOptparseApplicativeCompletions` (plural!) as
+  provided by the haskell package sets (so `haskellPackages.generateOptparseApplicativeCompletions` etc.).
+  The latter allows for cross-compilation (by automatically disabling generation of completion in the cross case).
+  For it to work properly you need to make sure that the function comes from the same context as the package
+  you are trying to override, i.e. always use the same package set as your package is coming from or – even
+  better – use `self.generateOptparseApplicativeCompletions` if you are overriding a haskell package set.
+  The old functions are retained for backwards compatibility, but yield are warning.
+
 - The `services.graphite.api` and `services.graphite.beacon` NixOS options, and
   the `python3.pkgs.graphite_api`, `python3.pkgs.graphite_beacon` and
   `python3.pkgs.influxgraph` packages, have been removed due to lack of upstream
   maintenance.
 
+- The `trace` binary from `perf-linux` package has been removed, due to being a duplicate of the `perf` binary.
+
+- The `aws` package has been removed due to being abandoned by the upstream. It is recommended to use `awscli` or `awscli2` instead.
+
+- The [CEmu TI-84 Plus CE emulator](https://ce-programming.github.io/CEmu) package has been renamed to `cemu-ti`. The [Cemu Wii U emulator](https://cemu.info) is now packaged as `cemu`.
+
+- `systemd-networkd` v250 deprecated, renamed, and moved some sections and settings which leads to the following breaking module changes:
+
+   * `systemd.network.networks.<name>.dhcpV6PrefixDelegationConfig` is renamed to `systemd.network.networks.<name>.dhcpPrefixDelegationConfig`.
+   * `systemd.network.networks.<name>.dhcpV6Config` no longer accepts the `ForceDHCPv6PDOtherInformation=` setting. Please use the `WithoutRA=` and `UseDelegatedPrefix=` settings in your `systemd.network.networks.<name>.dhcpV6Config` and the `DHCPv6Client=` setting in your `systemd.network.networks.<name>.ipv6AcceptRAConfig` to control when the DHCPv6 client is started and how the delegated prefixes are handled by the DHCPv6 client.
+   * `systemd.network.networks.<name>.networkConfig` no longer accepts the `IPv6Token=` setting. Use the `Token=` setting in your `systemd.network.networks.<name>.ipv6AcceptRAConfig` instead. The `systemd.network.networks.<name>.ipv6Prefixes.*.ipv6PrefixConfig` now also accepts the `Token=` setting.
+
+- `arangodb` versions 3.3, 3.4, and 3.5 have been removed because they are at EOL upstream. The default is now 3.10.0. Support for aarch64-linux has been removed since the target cannot be built reproducibly. By default `arangodb` is now built for the `haswell` architecture. If you wish to build for a different architecture, you may override the `targetArchitecture` argument with a value from [this list supported upstream](https://github.com/arangodb/arangodb/blob/207ec6937e41a46e10aea34953879341f0606841/cmake/OptimizeForArchitecture.cmake#L594). Some architecture specific optimizations are also conditionally enabled. You may alter this behavior by overriding the `asmOptimizations` parameter. You may also add additional architecture support by adding more `-DHAS_XYZ` flags to `cmakeFlags` via `overrideAttrs`.
+
 - The `meta.mainProgram` attribute of packages in `wineWowPackages` now defaults to `"wine64"`.
 
-- (Neo)Vim can not be configured with `configure.pathogen` anymore to reduce maintainance burden.
-Use `configure.packages` instead.
+- The `paperless` module now defaults `PAPERLESS_TIME_ZONE` to your configured system timezone.
+
+- The top-level `termonad-with-packages` alias for `termonad` has been removed.
+
+- Linux 4.9 has been removed because it will reach its end of life within the lifespan of 22.11.
+
+- (Neo)Vim can not be configured with `configure.pathogen` anymore to reduce maintenance burden.
+  Use `configure.packages` instead.
+- Neovim can not be configured with plug anymore (still works for vim).
+
+- The `adguardhome` module no longer uses `host` and `port` options, use `settings.bind_host` and `settings.bind_port` instead.
 
-- `k3s` no longer supports docker as runtime due to upstream dropping support.
+- The default `kops` version is now 1.25.1 and support for 1.22 and older has been dropped.
+
+- The `zrepl` package has been updated from 0.5.0 to 0.6.0. See the [changelog](https://zrepl.github.io/changelog.html) for details.
+
+- `k3s` no longer supports Docker as runtime due to upstream dropping support.
+
+- `cassandra_2_1` and `cassandra_2_2` have been removed. Please update to `cassandra_3_11` or `cassandra_3_0`. See the [changelog](https://github.com/apache/cassandra/blob/cassandra-3.11.14/NEWS.txt) for more information about the upgrade process.
+
+- `mysql57` has been removed. Please update to `mysql80` or `mariadb`. See the [upgrade guide](https://mariadb.com/kb/en/upgrading-from-mysql-to-mariadb/) for more information.
+
+- Consequently, `cqrlog` and `amorok` now use `mariadb` instead of `mysql57` for their embedded databases. Running `mysql_upgrade` may be necessary.
+- `k3s` supports `clusterInit` option, and it is enabled by default, for servers.
+
+- `percona-server56` has been removed. Please migrate to `mysql` or `mariadb` if possible.
+
+- `obs-studio` hase been updated to version 28. If you have packaged custom plugins, check if they are compatible. `obs-websocket` has been integrated into `obs-studio`.
+
+- `signald` has been bumped to `0.23.0`. For the upgrade, a migration process is necessary. It can be
+  done by running a command like this before starting `signald.service`:
+
+  ```
+  signald -d /var/lib/signald/db \
+    --database sqlite:/var/lib/signald/db \
+    --migrate-data
+  ```
+
+  For further information, please read the upstream changelogs.
+
+- `stylua` no longer accepts `lua52Support` and `luauSupport` overrides. Use `features` instead, which defaults to `[ "lua54" "luau" ]`.
+
+- `ocamlPackages.ocaml_extlib` has been renamed to `ocamlPackages.extlib`.
+
+- `pkgs.fetchNextcloudApp` has been rewritten to circumvent impurities in e.g. tarballs from GitHub and to make it easier to
+  apply patches. This means that your hashes are out-of-date and the (previously required) attributes `name` and `version`
+  are no longer accepted.
+
+- The Syncthing service now only allows absolute paths---starting with `/` or
+  `~/`---for `services.syncthing.folders.<name>.path`.
+  In a future release other paths will be allowed again and interpreted
+  relative to `services.syncthing.dataDir`.
+
+- `services.github-runner` and `services.github-runners.<name>` gained the option `serviceOverrides` which allows overriding the systemd `serviceConfig`. If you have been overriding the systemd service configuration (i.e., by defining `systemd.services.github-runner.serviceConfig`), you have to use the `serviceOverrides` option now. Example:
+
+  ```
+  services.github-runner.serviceOverrides.SupplementaryGroups = [
+    "docker"
+  ];
+  ```
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 ## Other Notable Changes {#sec-release-22.11-notable-changes}
 
+- PHP is now built in `NTS` (Non-Thread Safe) mode by default.
+  - For Apache and `mod_php` usage, we enable `ZTS` (Zend Thread Safe) mode. This has been a
+  common practice for a long time in other distributions.
+
+- `firefox`, `thunderbird` and `librewolf` now come with Wayland support by default. The `firefox-wayland`, `firefox-esr-wayland`, `thunderbird-wayland` and `librewolf-wayland` attributes are obsolete and have been aliased to their generic attribute.
+
 - The `xplr` package has been updated from 0.18.0 to 0.19.0, which brings some breaking changes. See the [upstream release notes](https://github.com/sayanarijit/xplr/releases/tag/v0.19.0) for more details.
 
+- Configuring multiple GitHub runners is now possible through `services.github-runners.<name>`. The options under `services.github-runner` remain, to configure a single runner.
+
 - `github-runner` gained support for ephemeral runners and registrations using a personal access token (PAT) instead of a registration token. See `services.github-runner.ephemeral` and `services.github-runner.tokenFile` for details.
 
-- A new module was added for the Saleae Logic device family, providing the options `hardware.saleae-logic.enable` and `hardware.saleae-logic.package`.
+- A new module was added to provide hardware support for the Saleae Logic device family, providing the options `hardware.saleae-logic.enable` and `hardware.saleae-logic.package`.
+
+- ZFS module will no longer allow hibernation by default.
+  - This is a safety measure to prevent data loss cases like the ones described at [OpenZFS/260](https://github.com/openzfs/zfs/issues/260) and [OpenZFS/12842](https://github.com/openzfs/zfs/issues/12842).
+  - Use the `boot.zfs.allowHibernation` option to configure this behaviour.
+
+- Mastodon now automatically removes remote media attachments older than 30 days. This is configurable through `services.mastodon.mediaAutoRemove`.
 
 - The Redis module now disables RDB persistence when `services.redis.servers.<name>.save = []` instead of using the Redis default.
 
-- Neo4j was updated from version 3 to version 4. See this [migration guide](https://neo4j.com/docs/upgrade-migration-guide/current/) on how to migrate your Neo4j instance.
+- Neo4j was updated from version 3 to version 4. See upstream's [migration guide](https://neo4j.com/docs/upgrade-migration-guide/current/) for information on how to migrate your instance.
+
+- The `networking.wireguard` module now can set the mtu on interfaces and tag its packets with an fwmark.
 
-- Matrix Synapse now requires entries in the `state_group_edges` table to be unique, in order to prevent accidentally introducing duplicate information (for example, because a database backup was restored multiple times). If your Synapse database already has duplicate rows in this table, this could fail with an error and require manual remediation.
+- The option `overrideStrategy` was added to the different systemd unit options (`systemd.services.<name>`, `systemd.sockets.<name>`, …) to allow enforcing the creation of a dropin file, rather than the main unit file, by setting it to `asDropin`.
+  This is useful in cases where the existence of the main unit file is not known to Nix at evaluation time, for example when the main unit file is provided by adding a package to `systemd.packages`.
+  See the fix proposed in [NixOS's systemd abstraction doesn't work with systemd template units](https://github.com/NixOS/nixpkgs/issues/135557#issuecomment-1295392470) for an example.
 
-- `dockerTools.buildImage` deprecates the misunderstood `contents` parameter, in favor of `copyToRoot`.
+- The `polymc` package has been removed due to a rogue maintainer. It has been
+  replaced by `prismlauncher`, a fork by the rest of the maintainers. For more
+  details, see [the PR that made this change](https://github.com/NixOS/nixpkgs/pull/196624) and
+  [the issue detailing the vulnerability](https://github.com/NixOS/nixpkgs/issues/196460).
+  Users with existing installations should rename `~/.local/share/polymc` to
+  `~/.local/share/PrismLauncher`. The main config file's path has also moved
+  from `~/.local/share/polymc/polymc.cfg` to
+  `~/.local/share/PrismLauncher/prismlauncher.cfg`.
+
+- The `bloat` package has been updated from unstable-2022-03-31 to unstable-2022-10-25, which brings a breaking change. See [this upstream commit message](https://git.freesoftwareextremist.com/bloat/commit/?id=887ed241d64ba5db3fd3d87194fb5595e5ad7d73) for details.
+
+- Synapse's systemd unit has been hardened.
+
+- The module `services.grafana` was refactored to be compliant with [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md). To be precise, this means that the following things have changed:
+  - The newly introduced option [](#opt-services.grafana.settings) is an attribute-set that
+    will be converted into Grafana's INI format. This means that the configuration from
+    [Grafana's configuration reference](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/)
+    can be directly written as attribute-set in Nix within this option.
+  - The option `services.grafana.extraOptions` has been removed. This option was an association
+    of environment variables for Grafana. If you had an expression like
+
+    ```nix
+    {
+      services.grafana.extraOptions.SECURITY_ADMIN_USER = "foobar";
+    }
+    ```
+
+    your Grafana instance was running with `GF_SECURITY_ADMIN_USER=foobar` in its environment.
+
+    For the migration, it is recommended to turn it into the INI format, i.e.
+    to declare
+
+    ```nix
+    {
+      services.grafana.settings.security.admin_user = "foobar";
+    }
+    ```
+
+    instead.
+
+    The keys in `services.grafana.extraOptions` have the format `<INI section name>_<Key Name>`.
+    Further details are outlined in the [configuration reference](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#override-configuration-with-environment-variables).
+
+    Alternatively you can also set all your values from `extraOptions` to
+    `systemd.services.grafana.environment`, make sure you don't forget to add
+    the `GF_` prefix though!
+  - Previously, the options [services.grafana.provision.datasources](#opt-services.grafana.provision.datasources) and
+    [services.grafana.provision.dashboards](#opt-services.grafana.provision.dashboards) expected lists of datasources
+    or dashboards for the [declarative provisioning](https://grafana.com/docs/grafana/latest/administration/provisioning/).
+
+    To declare lists of
+    - **datasources**, please rename your declarations to [services.grafana.provision.datasources.settings.datasources](#opt-services.grafana.provision.datasources.settings.datasources).
+    - **dashboards**, please rename your declarations to [services.grafana.provision.dashboards.settings.providers](#opt-services.grafana.provision.dashboards.settings.providers).
+
+    This change was made to support more features for that:
+
+    - It's possible to declare the `apiVersion` of your dashboards and datasources
+      by [services.grafana.provision.datasources.settings.apiVersion](#opt-services.grafana.provision.datasources.settings.apiVersion) (or
+      [services.grafana.provision.dashboards.settings.apiVersion](#opt-services.grafana.provision.dashboards.settings.apiVersion)).
+
+    - Instead of declaring datasources and dashboards in pure Nix, it's also possible
+      to specify configuration files (or directories) with YAML instead using
+      [services.grafana.provision.datasources.path](#opt-services.grafana.provision.datasources.path) (or
+      [services.grafana.provision.dashboards.path](#opt-services.grafana.provision.dashboards.path). This is useful when having
+      provisioning files from non-NixOS Grafana instances that you also want to
+      deploy to NixOS.
+
+      __Note:__ secrets from these files will be leaked into the store unless you use a
+      [**file**-provider or env-var](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider) for secrets!
+
+    - [services.grafana.provision.notifiers](#opt-services.grafana.provision.notifiers) is not affected by this change because
+      this feature is deprecated by Grafana and will probably be removed in Grafana 10.
+      It's recommended to use `services.grafana.provision.alerting.contactPoints` instead.
+
+- The `services.grafana.provision.alerting` option was added. It includes suboptions for every alerting-related objects (with the exception of `notifiers`), which means it's now possible to configure modern Grafana alerting declaratively.
+
+- Synapse now requires entries in the `state_group_edges` table to be unique, in order to prevent accidentally introducing duplicate information (for example, because a database backup was restored multiple times). If your Synapse database already has duplicate rows in this table, this could fail with an error and require manual remediation.
+
+- The `diamond` package has been update from 0.8.36 to 2.0.15. See the [upstream release notes](https://github.com/bbuchfink/diamond/releases) for more details.
+
+- The `guake` package has been updated from 3.6.3 to 3.9.0, see the [changelog](https://github.com/Guake/guake/releases) for more details.
+
+- The `netlify-cli` package has been updated from 6.13.2 to 12.2.4, see the [changelog](https://github.com/netlify/cli/releases) for more details.
+
+- `dockerTools.buildImage`'s `contents` parameter has been deprecated in favor of `copyToRoot`.
   Use `copyToRoot = buildEnv { ... };` or similar if you intend to add packages to `/bin`.
 
+- The `proxmox.qemuConf.bios` option was added, it corresponds to `Hardware->BIOS` field in Proxmox web interface. Use `"ovmf"` value to build UEFI image, default value remains `"bios"`. New option `proxmox.partitionTableType` defaults to either `"legacy"` or `"efi"`, depending on the `bios` value. Setting `partitionTableType` to `"hybrid"` results in an image, which supports both methods (`"bios"` and `"ovmf"`), thereby remaining bootable after change to Proxmox `Hardware->BIOS` field.
+
 - memtest86+ was updated from 5.00-coreboot-002 to 6.00-beta2. It is now the upstream version from https://www.memtest.org/, as coreboot's fork is no longer available.
 
+- Option descriptions, examples, and defaults writing in DocBook are now deprecated. Using CommonMark is preferred and will become the default in a future release.
+
+- The `documentation.nixos.options.allowDocBook` option was added to ease the transition to CommonMark option documentation. Setting this option to `false` causes an error for every option included in the manual that uses DocBook documentation; it defaults to `true` to preserve the previous behavior and will be removed once the transition to CommonMark is complete.
+
+- The Redis module now persists each instance's configuration file in the state directory, in order to support some more advanced use cases like Sentinel.
+
+- `protonup` has been aliased to and replaced by `protonup-ng` due to upstream not maintaining it.
+
 - The udisks2 service, available at `services.udisks2.enable`, is now disabled by default. It will automatically be enabled through services and desktop environments as needed.
   This also means that polkit will now actually be disabled by default. The default for `security.polkit.enable` was already flipped in the previous release, but udisks2 being enabled by default re-enabled it.
 
-- Add udev rules for the Teensy family of microcontrollers.
+- Nextcloud has been updated to version **25**. Additionally the following things have changed
+  for Nextcloud in NixOS:
+  - For Nextcloud **>=24**, the default PHP version is 8.1.
+  - Nextcloud **23** has been removed since it will reach its [end of life in December 2022](https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule/d76576a12a626d53305d480a6065b57cab705d3d).
+  - If `system.stateVersion` is **>=22.11**, Nextcloud 25 will be installed by default. For older versions,
+    Nextcloud 24 will be installed.
+  - Please ensure that you only upgrade one major release at a time! Nextcloud doesn't support
+    upgrades across multiple versions, i.e. an upgrade from **23** to **25** is only possible
+    when upgrading to **24** first.
+
+- systemd-oomd is enabled by default. Depending on which systemd units have
+  `ManagedOOMSwap=kill` or `ManagedOOMMemoryPressure=kill`, systemd-oomd will
+  SIGKILL all the processes under the appropriate descendant cgroups when the
+  configured limits are exceeded. NixOS does currently not configure cgroups
+  with oomd by default, this can be enabled using
+  [systemd.oomd.enableRootSlice](options.html#opt-systemd.oomd.enableRootSlice),
+  [systemd.oomd.enableSystemSlice](options.html#opt-systemd.oomd.enableSystemSlice),
+  and [systemd.oomd.enableUserServices](options.html#opt-systemd.oomd.enableUserServices).
+
+- The `tt-rss` service performs two database migrations when you first use its web UI after upgrade. Consider backing up its database before updating.
 
 - The `pass-secret-service` package now includes systemd units from upstream, so adding it to the NixOS `services.dbus.packages` option will make it start automatically as a systemd user service when an application tries to talk to the libsecret D-Bus API.
 
-- There is a new module for AMD SEV CPU functionality, which grants access to the hardware.
+- The Wordpress module now has support for installing language packs through a new option, `services.wordpress.sites.<site>.languages`.
+
+- The default package for `services.mullvad-vpn.package` was changed to `pkgs.mullvad`, allowing cross-platform usage of Mullvad. `pkgs.mullvad` only contains the Mullvad CLI tool, so users who rely on the Mullvad GUI will want to change it back to `pkgs.mullvad-vpn`, or add `pkgs.mullvad-vpn` to their environment.
+
+- PowerDNS has been updated from v4.6.2 to v4.7.2. Please be sure to review the [Upgrade Notes](https://doc.powerdns.com/authoritative/upgrading.html#to-4-7-0-or-master) provided by upstream before upgrading. Worth specifically noting is that the new Catalog Zones feature comes with a mandatory schema change for the GSQL database backends, which has to be manually applied.
+
+- There is a new module for the `thunar` program (the Xfce file manager), which depends on the `xfconf` dbus service, and also has a dbus service and a systemd unit. The option `services.xserver.desktopManager.xfce.thunarPlugins` has been renamed to `programs.thunar.plugins`, and may be removed in a future release.
+
+- There is a new module for `xfconf` (the Xfce configuration storage system), which has a dbus service.
+
+- The Mastodon package has been upgraded to v4.0.0. See the [v4.0.0 release notes](https://github.com/mastodon/mastodon/releases/tag/v4.0.0) for a list of changes. On standard setups, no manual migration steps are required. Nevertheless, a database backup is recommended.
+
+- The `nomad` package now defaults to v1.3, which no longer has a downgrade path to v1.2 or older.
+
+- The `nodePackages` package set now defaults to the LTS release in the `nodejs` package again, instead of being pinned to `nodejs-14_x`. Several updates to node2nix have been made for compatibility with newer Node.js and npm versions and a new `postRebuild` hook has been added for packages to perform extra build steps before the npm install step prunes dev dependencies.
+
+- `boot.kernel.sysctl` is defined as a freeformType and adds a custom merge option for `net.core.rmem_max` (taking the highest value defined to avoid conflicts between 2 services trying to set that value).
+
+- The `mame` package does not ship with its tools anymore in the default output. They were moved to a separate `tools` output instead. For convenience, `mame-tools` package was added for those who want to use it.
+
+- A NixOS module for Firefox has been added which allows preferences and [policies](https://github.com/mozilla/policy-templates/blob/master/README.md) to be set. This also allows extensions to be installed via the `ExtensionSettings` policy. The new options are under `programs.firefox`.
+
+- The option `services.picom.experimentalBackends` was removed since it is now the default and the option will cause `picom` to quit instead.
+
+- `haskellPackages.callHackage` is not always invalidated if `all-cabal-hashes` changes, leading to less rebuilds of haskell dependencies.
+
+- `haskellPackages.callHackage` and `haskellPackages.callCabal2nix` (and related functions) no longer keep a reference to the `cabal2nix` call used to generate them. As a result, they will be garbage collected more often.
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+## New Services {#sec-release-22.11-new-services}
+
+- [alps](https://git.sr.ht/~migadu/alps), a simple and extensible webmail. Available as [services.alps](#opt-services.alps.enable).
+
+- [appvm](https://github.com/jollheef/appvm), Nix based app VMs. Available as [virtualisation.appvm](options.html#opt-virtualisation.appvm.enable).
+
+- [AusweisApp2](https://www.ausweisapp.bund.de/), the authentication software for the German ID card. Available as [programs.ausweisapp](#opt-programs.ausweisapp.enable).
+
+- [automatic-timezoned](https://github.com/maxbrunet/automatic-timezoned). a Linux daemon to automatically update the system timezone based on location. Available as [services.automatic-timezoned](#opt-services.automatic-timezoned.enable).
+
+- [Dolibarr](https://www.dolibarr.org/), an enterprise resource planning and customer relationship manager. Enable using [services.dolibarr](#opt-services.dolibarr.enable).
+
+- [dragonflydb](https://dragonflydb.io/), a modern replacement for Redis and Memcached. Available as [services.dragonflydb](#opt-services.dragonflydb.enable).
+
+- [endlessh-go](https://github.com/shizunge/endlessh-go), an SSH tarpit that exposes Prometheus metrics. Available as [services.endlessh-go](#opt-services.endlessh-go.enable).
+
+- [endlessh](https://github.com/skeeto/endlessh), an SSH tarpit. Available as [services.endlessh](#opt-services.endlessh.enable).
+
+- [EVCC](https://evcc.io) is an EV charge controller with PV integration. It supports a multitude of chargers, meters, vehicle APIs and more and ties that together with a well-tested backend and a lightweight web frontend. Available as [services.evcc](#opt-services.evcc.enable).
+
+- [expressvpn](https://www.expressvpn.com), the CLI client for ExpressVPN. Available as [services.expressvpn](#opt-services.expressvpn.enable).
+
+- [FreshRSS](https://freshrss.org/), a free, self-hostable RSS feed aggregator. Available as [services.freshrss](#opt-services.freshrss.enable).
+
+- [Garage](https://garagehq.deuxfleurs.fr/), a simple object storage server for geodistributed deployments, alternative to MinIO. Available as [services.garage](#opt-services.garage.enable).
+
+- [go-autoconfig](https://github.com/L11R/go-autoconfig), IMAP/SMTP autodiscover server. Available as [services.go-autoconfig](#opt-services.go-autoconfig.enable).
+
+- [Grafana Tempo](https://www.grafana.com/oss/tempo/), a distributed tracing store. Available as [services.tempo](#opt-services.tempo.enable).
+
+- [HBase cluster](https://hbase.apache.org/), a distributed, scalable, big data store. Available as [services.hadoop.hbase](options.html#opt-services.hadoop.hbase.enable).
+
+- [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle. Available as [services.infnoise](options.html#opt-services.infnoise.enable).
+
+- [kanata](https://github.com/jtroo/kanata), a tool to improve keyboard comfort and usability with advanced customization. Available as [services.kanata](options.html#opt-services.kanata.enable).
+
+- [karma](https://github.com/prymitive/karma), an alert dashboard for Prometheus Alertmanager. Available as [services.karma](options.html#opt-services.karma.enable)
+
+- [Komga](https://komga.org/), a free and open source comics/mangas media server. Available as [services.komga](#opt-services.komga.enable).
+
+- [kthxbye](https://github.com/prymitive/kthxbye), an alert acknowledgement management daemon for Prometheus Alertmanager. Available as [services.kthxbye](options.html#opt-services.kthxbye.enable)
+
+- [languagetool](https://languagetool.org/), a multilingual grammar, style, and spell checker. Available as [services.languagetool](options.html#opt-services.languagetool.enable).
+
+- [Listmonk](https://listmonk.app), a self-hosted newsletter manager. Enable using [services.listmonk](options.html#opt-services.listmonk.enable).
+
+- [Mepo](https://mepo.milesalan.com), a fast, simple, hackable OSM map viewer for mobile and desktop Linux. Available as [programs.mepo.enable](#opt-programs.mepo.enable).
+
+- [merecat](https://troglobit.com/projects/merecat/), a small and easy HTTP server based on thttpd. Available as [services.merecat](#opt-services.merecat.enable)
+
+- [netbird](https://netbird.io), a zero configuration VPN. Available as [services.netbird](options.html#opt-services.netbird.enable).
+
+- [ntfy.sh](https://ntfy.sh), a push notification service. Available as [services.ntfy-sh](#opt-services.ntfy-sh.enable)
+
+- [OpenRGB](https://gitlab.com/CalcProgrammer1/OpenRGB/-/tree/master), a FOSS tool for controlling RGB lighting. Available as [services.hardware.openrgb.enable](options.html#opt-services.hardware.openrgb.enable).
+
+- [Outline](https://www.getoutline.com/), a wiki and knowledge base similar to Notion. Available as [services.outline](#opt-services.outline.enable).
+
+- [Patroni](https://github.com/zalando/patroni), a template for PostgreSQL HA with ZooKeeper, etcd or Consul. Available as [services.patroni](options.html#opt-services.patroni.enable).
+
+- [persistent-evdev](https://github.com/aiberia/persistent-evdev), a daemon to add virtual proxy devices that mirror a physical input device but persist even if the underlying hardware is hot-plugged. Available as [services.persistent-evdev](#opt-services.persistent-evdev.enable).
+
+- [Please](https://github.com/edneville/please), a Sudo clone written in Rust. Available as [security.please](#opt-security.please.enable).
+
+- [Prometheus IPMI exporter](https://github.com/prometheus-community/ipmi_exporter), an IPMI exporter for Prometheus. Available as [services.prometheus.exporters.ipmi](#opt-services.prometheus.exporters.ipmi.enable).
+
+- [Sachet](https://github.com/messagebird/sachet/), an SMS alerting tool for the Prometheus Alertmanager. Available as [services.prometheus.sachet](#opt-services.prometheus.sachet.enable).
+
+- [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable).
+
+- [syncstorage-rs](https://github.com/mozilla-services/syncstorage-rs), a self-hostable sync server for Firefox. Available as [services.firefox-syncserver](options.html#opt-services.firefox-syncserver.enable).
+
+- [Tandoor Recipes](https://tandoor.dev), a self-hosted multi-tenant recipe collection. Available as [services.tandoor-recipes](options.html#opt-services.tandoor-recipes.enable).
+
+- [TAYGA](http://www.litech.org/tayga/), an out-of-kernel stateless NAT64 implementation. Available as [services.tayga](#opt-services.tayga.enable).
+
+- [tmate-ssh-server](https://github.com/tmate-io/tmate-ssh-server), server side part of [tmate](https://tmate.io/). Available as [services.tmate-ssh-server](#opt-services.tmate-ssh-server.enable).
 
-- There is a new module for the `thunar` program (the Xfce file manager), which depends on the `xfconf` dbus service, and also has a dbus service and a systemd unit. The option `services.xserver.desktopManager.xfce.thunarPlugins` has been renamed to `programs.thunar.plugins`, and in a future release it may be removed.
+- [Uptime Kuma](https://uptime.kuma.pet/), a fancy self-hosted monitoring tool. Available as [services.uptime-kuma](#opt-services.uptime-kuma.enable).
 
-- There is a new module for the `xfconf` program (the Xfce configuration storage system), which has a dbus service.
+- [WriteFreely](https://writefreely.org), a simple blogging platform with ActivityPub support. Available as [services.writefreely](options.html#opt-services.writefreely.enable).
 
-- The `nomad` package now defaults to 1.3, which no longer has a downgrade path to releases 1.2 or older.
+- [xray](https://github.com/XTLS/Xray-core), a fully compatible v2ray-core replacement. Features XTLS, which when enabled on server and client, brings UDP FullCone NAT to proxy setups. Available as [services.xray](options.html#opt-services.xray.enable).
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2305.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2305.section.md
new file mode 100644
index 000000000000..cca1d48ec564
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -0,0 +1,662 @@
+# Release 23.05 (“Stoat”, 2023.05/31) {#sec-release-23.05}
+
+The NixOS release team is happy to announce a new version of NixOS. The release is called NixOS 23.05 ("Stoat").
+
+NixOS is a Linux distribution, whose set of packages can also be used on other Linux systems and macOS.
+
+Support is planned until the end of December 2023, handing over to NixOS 23.11.
+
+To upgrade to the latest release, follow the [upgrade chapter](https://nixos.org/manual/nixos/stable/index.html#sec-upgrading).
+
+## Highlights {#sec-release-23.05-highlights}
+
+In addition to numerous new and updated packages, this release has the following highlights:
+
+- The default [Nix](https://github.com/NixOS/nix) version was updated from 2.11 to 2.13. In particular, this includes a [small language alteration](https://github.com/NixOS/nix/issues/8259) in the way floats are represented in `builtins.toJSON`. See the release notes for [2.12](https://nixos.org/manual/nix/stable/release-notes/rl-2.12.html) and [2.13](https://nixos.org/manual/nix/unstable/release-notes/rl-2.13.html) for more information.
+
+- The default [Linux Kernel](https://kernel.org/) was updated from version 5.15 to 6.1, see [Kernelnewbies](https://kernelnewbies.org/Linux_6.1) for what has changed. All Kernels currently shown on [kernel.org](https://kernel.org/) are available.
+
+- [systemd](https://systemd.io) has been updated from v252 to v253, see [the release notes](https://github.com/systemd/systemd/blob/v253/NEWS#L3-L659) for more information on the changes.
+    - Updating with `nixos-rebuild boot` and rebooting is recommended, since in some rare cases the `nixos-rebuild switch` into the new generation on a live system might fail due to missing mount units.
+
+- [glibc](https://www.gnu.org/software/libc/) has been updated from version 2.35 to 2.37, see [the release notes](https://sourceware.org/glibc/wiki/Release/2.37) for what was changed.
+
+- [libxcrypt](https://github.com/besser82/libxcrypt), the library providing the `crypt(3)` password hashing function, is now built without support for algorithms not flagged [`strong`](https://github.com/besser82/libxcrypt/blob/v4.4.33/lib/hashes.conf#L48). This affects the availability of password hashing algorithms used for system login (`login(1)`, `passwd(1)`), but also Apache2 Basic-Auth, Samba, OpenLDAP, Dovecot, and [many other packages](https://sourcegraph.com/search?q=context:global+repo:%5Egithub%5C.com/NixOS/nixpkgs%24+libxcrypt&patternType=standard&sm=1&groupBy=path).
+
+- NixOS now defaults to using [nsncd](https://github.com/twosigma/nsncd), a non-caching reimplementation of nscd in Rust, as its NSS lookup dispatcher. This replaces the buggy and deprecated nscd implementation provided through glibc. When you find problems, you can switch back by disabling it:
+  ```nix
+  services.nscd.enableNsncd = false;
+  ```
+
+- The internal option `boot.bootspec.enable` is now enabled by default because [RFC 0125](https://github.com/NixOS/rfcs/pull/125) was merged. This means you will have a bootspec document called `boot.json` generated for each system and specialisation in the top-level. This is useful to enable advanced boot use cases in NixOS, such as Secure Boot.
+
+- Two changes to `nixos-rebuild` are important to highlight as well.
+    - Support for an extra `--specialisation` option was added that can be used to change specialisation for `switch` and `test` commands.
+    - The `--target-host` and `--build-host` options no longer treat the `localhost` value specially – to build on resp. deploy to a local machine, omit the relevant flag.
+
+- [Python](https://www.python.org) implements [PEP 668](https://peps.python.org/pep-0668/), providing better feedback to users that try to run `pip install` for system-wide or user home installations.
+
+- [Cinnamon](https://github.com/linuxmint/Cinnamon) has been updated to version 5.6, see [the pull request](https://github.com/NixOS/nixpkgs/pull/201328#issue-1449910204) for what was changed.
+
+- [GNOME](https://www.gnome.org) has been updated to version 44, see the [the release notes](https://release.gnome.org/44/) for details.
+
+- [KDE Plasma](https://kde.org/de/plasma-desktop/) has been updated to version 5.27, see [the release notes](https://kde.org/announcements/plasma/5/5.27.0/) for what was changed.
+
+- `openra` was updated to `20230225`. Due to large scope of the update, currently only `openraPackages.engines.release` and `openraPackages.engines.latest` packages are available.
+  If you want to use the old engine versions or mods, they were moved to the `openraPackages_2019` namespace.
+
+## New Services {#sec-release-23.05-new-services}
+
+- [Akkoma](https://akkoma.social), an ActivityPub microblogging server. Available as [services.akkoma](options.html#opt-services.akkoma.enable).
+
+- [alertmanager-irc-relay](https://github.com/google/alertmanager-irc-relay), a Prometheus Alertmanager IRC Relay. Available as [services.prometheus.alertmanagerIrcRelay](options.html#opt-services.prometheus.alertmanagerIrcRelay.enable).
+
+- [alice-lg](github.com/alice-lg/alice-lg), a looking-glass for BGP sessions. Available as [services.alice-lg](#opt-services.alice-lg.enable).
+
+- [atuin](https://github.com/ellie/atuin), a sync server for shell history. Available as [services.atuin](#opt-services.atuin.enable).
+
+- [authelia](https://www.authelia.com/), an open-source authentication and authorization server. Available as [services.authelia](options.html#opt-services.authelia.enable).
+
+- [birdwatcher](github.com/alice-lg/birdwatcher), a small HTTP server meant to provide an API defined by Barry O'Donovan's birds-eye to the BIRD internet routing daemon. Available as [services.birdwatcher](#opt-services.birdwatcher.enable).
+
+- [blesh](https://github.com/akinomyoga/ble.sh), a line editor written in pure bash. Available as [programs.bash.blesh](#opt-programs.bash.blesh.enable).
+
+- [Budgie Desktop](https://github.com/BuddiesOfBudgie/budgie-desktop), a familiar, modern desktop environment. Available as [services.xserver.desktopManager.budgie](options.html#opt-services.xserver.desktopManager.budgie).
+
+- [clash-verge](https://github.com/zzzgydi/clash-verge), a Clash GUI based on tauri. Available as [programs.clash-verge](#opt-programs.clash-verge.enable).
+
+- [Cloudlog](https://www.magicbug.co.uk/cloudlog/), a web-based Amateur Radio logging application. Available as [services.cloudlog](#opt-services.cloudlog.enable).
+
+- [consul-template](https://github.com/hashicorp/consul-template/), a template renderer, notifier, and supervisor for HashiCorp Consul and Vault data. Available as [services.consul-template](#opt-services.consul-template.instances).
+
+- [cups-pdf-to-pdf](https://github.com/alexivkin/CUPS-PDF-to-PDF), a PDF-generating CUPS backend based on [cups-pdf](https://www.cups-pdf.de/). Available as [services.printing.cups-pdf](#opt-services.printing.cups-pdf.enable).
+
+- [Deepin Desktop Environment](https://github.com/linuxdeepin/dde), an elegant, easy to use and reliable desktop environment. Available as [services.xserver.desktopManager.deepin](options.html#opt-services.xserver.desktopManager.deepin).
+
+- [esphome](https://esphome.io), a dashboard to configure ESP8266/ESP32 devices for use with Home Automation systems. Available as [services.esphome](#opt-services.esphome.enable).
+
+- [frigate](https://frigate.video), an open source NVR built around real-time AI object detection. Available as [services.frigate](#opt-services.frigate.enable).
+
+- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
+
+- [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
+
+- [gitea-actions-runner](https://gitea.com/gitea/act_runner), a CI runner for Gitea/Forgejo Actions. Available as [services.gitea-actions-runner](#opt-services.gitea-actions-runner.instances).
+
+- [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer.  Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
+
+- [go2rtc](https://github.com/AlexxIT/go2rtc), a camera streaming appliation with support for RTSP, WebRTC, HomeKit, FFMPEG, RTMP and other protocols. Available as [services.go2rtc](options.html#opt-services.go2rtc.enable).
+
+- [goeland](https://github.com/slurdge/goeland), an alternative to rss2email written in Golang with many filters. Available as [services.goeland](#opt-services.goeland.enable).
+
+- [gonic](https://github.com/sentriz/gonic), a Subsonic music streaming server. Available as [services.gonic](#opt-services.gonic.enable).
+
+- [hardware.ipu6](#opt-hardware.ipu6.enable), drivers for IPU6 based webcams on Intel Tiger Lake and Alder Lake.
+
+- [harmonia](https://github.com/nix-community/harmonia/), a Nix binary cache implemented in Rust using [libnixstore](https://docs.rs/libnixstore/latest/libnixstore/). Available as [services.harmonia](options.html#opt-services.harmonia.enable).
+
+- [hyprland](https://github.com/hyprwm/hyprland), a dynamic tiling Wayland compositor that doesn't sacrifice on its looks. Available as [programs.hyprland](#opt-programs.hyprland.enable).
+
+- [imaginary](https://github.com/h2non/imaginary), a microservice for high-level image processing that Nextcloud can use to generate previews. Available as [services.imaginary](#opt-services.imaginary.enable).
+
+- [ivpn](https://www.ivpn.net/), a secure, private VPN with fast WireGuard connections. Available as [services.ivpn](#opt-services.ivpn.enable).
+
+- [vmalert](https://victoriametrics.com/), an alerting engine for VictoriaMetrics. Available as [services.vmalert](#opt-services.vmalert.enable).
+
+- [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable).
+
+- [kavita](https://kavitareader.com), a self-hosted digital library. Available as [services.kavita](options.html#opt-services.kavita.enable).
+
+- [keyd](https://github.com/rvaiya/keyd), a key remapping daemon for Linux. Available as [services.keyd](#opt-services.keyd.enable).
+
+- [lldap](https://github.com/lldap/lldap), a lightweight authentication server that provides an opinionated, simplified LDAP interface for authentication. Available as [services.lldap](#opt-services.lldap.enable).
+
+- [minipro](https://gitlab.com/DavidGriffith/minipro/), an open source program for controlling the MiniPRO TL866xx series of chip programmers. Available as [programs.minipro](options.html#opt-programs.minipro.enable).
+
+- [mmsd](https://gitlab.com/kop316/mmsd), a lower level daemon that transmits and receives MMSes. Available as [services.mmsd](#opt-services.mmsd.enable).
+
+- [monica](https://www.monicahq.com), an open source personal CRM. Available as [services.monica](options.html#opt-services.monica.enable).
+
+- [networkd-dispatcher](https://gitlab.com/craftyguy/networkd-dispatcher), a dispatcher service for systemd-networkd connection status changes. Available as [services.networkd-dispatcher](#opt-services.networkd-dispatcher.enable).
+
+- [nimdow](https://github.com/avahe-kellenberger/nimdow), a window manager written in Nim, inspired by dwm. Available as [services.xserver.windowManager.nimdow.enable](options.html#opt-services.xserver.windowManager.nimdow.enable).
+
+- [opensearch](https://opensearch.org), a search server alternative to Elasticsearch. Available as [services.opensearch](options.html#opt-services.opensearch.enable).
+
+- [openvscode-server](https://github.com/gitpod-io/openvscode-server), run VS Code on a remote machine with access through a modern web browser from any device, anywhere. Available as [services.openvscode-server](#opt-services.openvscode-server.enable).
+
+- [peroxide](https://github.com/ljanyst/peroxide), a fork of the official [ProtonMail bridge](https://github.com/ProtonMail/proton-bridge) that aims to be similar to [Hydroxide](https://github.com/emersion/hydroxide). Available as [services.peroxide](#opt-services.peroxide.enable).
+
+- [photoprism](https://photoprism.app/), a AI-powered photos app for the decentralized web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
+
+- [Pixelfed](https://pixelfed.org/), an Instagram-like ActivityPub server. Available as [services.pixelfed](options.html#opt-services.pixelfed.enable).
+
+- [PufferPanel](https://pufferpanel.com), a game server management panel designed to be easy to use. Available as [services.pufferpanel](#opt-services.pufferpanel.enable).
+
+- [QDMR](https://dm3mat.darc.de/qdmr/), a GUI application and command line tool for programming DMR radios [programs.qdmr](#opt-programs.qdmr.enable).
+
+- [readarr](https://github.com/Readarr/Readarr), book manager and automation (Sonarr for ebooks). Available as [services.readarr](options.html#opt-services.readarr.enable).
+
+- [ReGreet](https://github.com/rharish101/ReGreet), a clean and customizable greeter for greetd. Available as [programs.regreet](#opt-programs.regreet.enable).
+
+- [rshim](https://github.com/Mellanox/rshim-user-space), the user-space rshim driver for the BlueField SoC. Available as [services.rshim](options.html#opt-services.rshim.enable).
+
+- [SFTPGo](https://github.com/drakkan/sftpgo), a fully featured and highly configurable SFTP server with optional HTTP/S, FTP/S and WebDAV support. Available as [services.sftpgo](options.html#opt-services.sftpgo.enable).
+
+- [sharing](https://github.com/parvardegr/sharing), a command-line tool to share directories and files from the CLI to iOS and Android devices without the need of an extra client app. Available as [programs.sharing](#opt-programs.sharing.enable).
+
+- [sniffnet](https://github.com/GyulyVGC/sniffnet), an application to monitor your network traffic. Available as [programs.sniffnet](#opt-programs.sniffnet.enable).
+
+- [stargazer](https://sr.ht/~zethra/stargazer/), a fast and easy to use Gemini server. Available as [services.stargazer](#opt-services.stargazer.enable).
+
+- [stevenblack-blocklist](https://github.com/StevenBlack/hosts), a unified hosts file with base extensions for blocking unwanted websites. Available as [networking.stevenblack](options.html#opt-networking.stevenblack.enable).
+
+- [systemd-repart](https://www.freedesktop.org/software/systemd/man/systemd-repart.service.html), grow and add partitions to a partition table. Available as [systemd.repart](options.html#opt-systemd.repart) and [boot.initrd.systemd.repart](options.html#opt-boot.initrd.systemd.repart)
+
+- [trippy](https://github.com/fujiapple852/trippy), a network diagnostic tool. Available as [programs.trippy](#opt-programs.trippy.enable).
+
+- [tts](https://github.com/coqui-ai/TTS), a battle-tested deep learning toolkit for Text-to-Speech. Multiple servers may be configured below [services.tts.servers](#opt-services.tts.servers).
+
+- [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable).
+
+- [v2rayA](https://v2raya.org), a Linux web GUI client of Project V which supports V2Ray, Xray, SS, SSR, Trojan and Pingtunnel. Available as [services.v2raya](options.html#opt-services.v2raya.enable).
+
+- [v4l2-relayd](https://git.launchpad.net/v4l2-relayd), a streaming relay for v4l2loopback using gstreamer. Available as [services.v4l2-relayd](#opt-services.v4l2-relayd.instances._name_.enable).
+
+- [vault-agent](https://developer.hashicorp.com/vault/docs/agent), a template renderer and API auth proxy for HashiCorp Vault, similar to `consul-template`. Available as [services.vault-agent](#opt-services.vault-agent.instances).
+
+- [webhook](https://github.com/adnanh/webhook), a lightweight webhook server. Available as [services.webhook](#opt-services.webhook.enable).
+
+- [wgautomesh](https://git.deuxfleurs.fr/Deuxfleurs/wgautomesh), a simple utility to help connect wireguard nodes together in a full mesh topology. Available as [services.wgautomesh](options.html#opt-services.wgautomesh.enable).
+
+- [woodpecker](https://woodpecker-ci.org/), a simple CI engine with great extensibility. Available as [services.woodpecker-server](#opt-services.woodpecker-server.enable) and [services.woodpecker-agents](#opt-services.woodpecker-agents.agents._name_.enable).
+
+- [wstunnel](https://github.com/erebe/wstunnel), a proxy tunnelling arbitrary TCP or UDP traffic through a WebSocket connection. Available as [services.wstunnel](options.html#opt-services.wstunnel.enable).
+
+## Backward Incompatibilities {#sec-release-23.05-incompatibilities}
+
+- `services.asusd` configuration now uses strings instead of structured configuration, as upstream switched to the [RON](https://github.com/ron-rs/ron) configuration format. Support for structured configuration may return when [RON](https://github.com/ron-rs/ron) generation is implemented in nixpkgs.
+
+- `borgbackup` module now has an option for inhibiting system sleep while backups are running, defaulting to off (not inhibiting sleep), available as [`services.borgbackup.jobs.<name>.inhibitsSleep`](#opt-services.borgbackup.jobs._name_.inhibitsSleep).
+
+- The `openssh` client now comes with the `~C` escape sequence disabled by default. It can be re-enabled by setting `EnableEscapeCommandline yes`
+
+- The `programs.ssh` client module does not read `/etc/ssh/ssh_known_hosts2` anymore, since this location is [deprecated since 2001](https://marc.info/?l=openssh-unix-dev&m=100508718416162&w=2).
+
+- The `services.openssh` server module does not read `~/.ssh/authorized_keys2` anymore, since this location is [deprecated since 2001](https://marc.info/?l=openssh-unix-dev&m=100508718416162&w=2).
+
+- MAC-then-encrypt algorithms were removed from the default selection of `services.openssh.settings.Macs`. If you still require these [MACs](https://en.wikipedia.org/wiki/Message_authentication_code), for example when you are relying on libssh2 (e.g. VLC) or the SSH library shipped on the iPhone, you can re-add them like this:
+
+  ```nix
+  services.openssh.settings.Macs = [
+    "hmac-sha2-512"
+    "hmac-sha2-256"
+    "umac-128@openssh.com"
+  };
+  ```
+
+- `podman` now uses the `netavark` network stack. Users will need to delete all of their local containers, images, volumes, etc, by running `podman system reset --force` once before upgrading their systems.
+
+- `git-bug` has been updated to at least version 0.8.0, which includes backwards incompatible changes. The `git-bug-migration` package can be used to upgrade existing repositories.
+
+- `graylog` has been updated to version 5, which can not be updated directly from the previously packaged version 3.3. If you had installed the previously packaged version 3.3, please follow the [upgrade path](https://go2docs.graylog.org/5-0/upgrading_graylog/upgrade_path.htm) from 3.3 to 4.0 to 4.3 to 5.0.
+
+- `buildFHSUserEnv` is now called `buildFHSEnv` and uses FlatPak's Bubblewrap sandboxing tool rather than Nixpkgs' own chrootenv. The old chrootenv-based implemenation is still available via `buildFHSEnvChrootenv` but is considered deprecated and will be removed when the remaining uses inside Nixpkgs have been migrated. If your FHSEnv-wrapped application misbehaves when using the new bubblewrap implementation, please create an issue in Nixpkgs.
+
+- `nushell` has been updated to at least version 0.77.0, which includes potential breaking changes in aliases. The old aliases are now available as `old-alias` but it is recommended you migrate to the new format. See [Reworked aliases](https://www.nushell.sh/blog/2023-03-14-nushell_0_77.html#reworked-aliases-breaking-changes-kubouch).
+
+- `gajim` has been updated to version 1.7.3 which has disabled legacy ciphers. See [changelog for version 1.7.0](https://dev.gajim.org/gajim/gajim/-/releases/1.7.0).
+
+- `keepassx` and `keepassx2` have been removed, due to upstream [stopping development](https://www.keepassx.org/index.html%3Fp=636.html). Consider [KeePassXC](https://keepassxc.org) as a maintained alternative.
+
+- The [services.kubo.settings](#opt-services.kubo.settings) option is now no longer stateful. If you changed any of the options in [services.kubo.settings](#opt-services.kubo.settings) in the past and then removed them from your NixOS configuration again, those changes are still in your Kubo configuration file but will now be reset to the default. If you're unsure, you may want to make a backup of your configuration file (probably `/var/lib/ipfs/config`) and compare after the update.
+
+- The Kubo HTTP API will no longer listen on localhost and will instead only listen on a Unix domain socket by default. Read the [services.kubo.settings.Addresses.API](#opt-services.kubo.settings.Addresses.API) option description for more information.
+
+- The EC2 image module no longer fetches instance metadata in stage-1. This results in a significantly smaller initramfs, since network drivers no longer need to be included, and faster boots, since metadata fetching can happen in parallel with startup of other services.
+  This breaks services which rely on metadata being present by the time stage-2 is entered. Anything which reads EC2 metadata from `/etc/ec2-metadata` should now have an `after` dependency on `fetch-ec2-metadata.service`
+
+- The mailman service now defaults to using a randomly generated REST API password instead of a hard-coded one.
+
+- `minio` removed support for its legacy filesystem backend in [RELEASE.2022-10-29T06-21-33Z](https://github.com/minio/minio/releases/tag/RELEASE.2022-10-29T06-21-33Z). This means if your storage was created with the old format, minio will no longer start. Unfortunately, minio doesn't provide an automatic migration, they only provide [instructions how to manually convert the node](https://min.io/docs/minio/windows/operations/install-deploy-manage/migrate-fs-gateway.html). To facilitate this migration, we keep around the last version that still supports the old filesystem backend as `minio_legacy_fs`. Use it via `services.minio.package = minio_legacy_fs;` to export your data before switching to the new version. See the corresponding [issue](https://github.com/NixOS/nixpkgs/issues/199318) for more details.
+
+- `services.sourcehut.dispatch` and the corresponding package (`sourcehut.dispatchsrht`) have been removed due to [upstream deprecation](https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/).
+
+- The attributes used by `services.snapper.configs.<name>` have changed. Migrate from this:
+
+  ```nix
+  services.snapper.configs.example = {
+    subvolume = "/example";
+    extraConfig = ''
+      ALLOW_USERS="alice"
+    '';
+  };
+  ```
+
+  to this:
+
+  ```nix
+  services.snapper.configs.example = {
+    SUBVOLUME = "/example";
+    ALLOW_USERS = [ "alice" ];
+  };
+  ```
+
+- The default module options for [services.snapserver.openFirewall](#opt-services.snapserver.openFirewall), [services.tmate-ssh-server.openFirewall](#opt-services.tmate-ssh-server.openFirewall) and [services.unifi-video.openFirewall](#opt-services.unifi-video.openFirewall) have been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall.
+
+- The option `i18n.inputMethod.fcitx5.enableRimeData` has been removed. Default RIME data is now included in `fcitx5-rime` by default, and can be customized using
+
+  ```nix
+  fcitx5-rime.override {
+    rimeDataPkgs = [
+      pkgs.rime-data
+      # ...
+    ];
+  }
+  ```
+
+- The `udev` hwdb.bin file is now built with systemd-hwdb rather than the [deprecated "udevadm hwdb"](https://github.com/systemd/systemd/pull/25714). This may impact mappings where the same key is defined in multiple matching entries. The updated behavior will select the latest definition in case of conflict. In general, this should be a positive change, as the hwdb source files are designed with this ordering in mind. As an example, the mapping of the HP Dev One keyboard scan code for "mute mic" is corrected by this update. This change may impact users who have worked-around previously incorrect mappings.
+
+- Kime has been updated from 2.5.6 to 3.0.2 and the `i18n.inputMethod.kime.config` option has been removed. Users should use `daemonModules`, `iconColor`, and `extraConfig` options under `i18n.inputMethod.kime` instead.
+
+- `tut` has been updated from 1.0.34 to 2.0.0, and now uses the TOML format for the configuration file instead of INI. Additional information can be found [here](https://github.com/RasmusLindroth/tut/releases/tag/2.0.0).
+
+- `i3status-rust` has been updated from 0.22.0 to 0.30.5, and this brings many changes to its configuration format. Additional information can be found [here](https://github.com/greshake/i3status-rust/blob/v0.30.0/NEWS.md).
+
+- The `wordpress` derivation no longer contains any built-in plugins or themes. If you need them, you have to add them back to prevent your site from breaking. You can find them in `wordpressPackages.{plugins,themes}`.
+
+- `llvmPackages_rocm.llvm` will not contain `clang` or `compiler-rt`. `llvmPackages_rocm.clang` will not contain `llvm`. `llvmPackages_rocm.clangNoCompilerRt` has been removed in favor of using `llvmPackages_rocm.clang-unwrapped`.
+
+- `services.xserver.desktopManager.plasma5.excludePackages` has been moved to `environment.plasma5.excludePackages`, for consistency with other Desktop Environments.
+
+- `teleport` has been updated from major version 10 to major version 12. Please see upstream [upgrade instructions](https://goteleport.com/docs/setup/operations/upgrading/) and release notes for versions [11](https://goteleport.com/docs/changelog/#1100) and [12](https://goteleport.com/docs/changelog/#1201). Note that Teleport does not officially support upgrades across more than one major version at a time. If you're running Teleport server components, it is recommended to first upgrade to an intermediate 11.x version by setting `services.teleport.package = pkgs.teleport_11`. Afterwards, this option can be removed to upgrade to the default version (12).
+
+- The EC2 image module previously detected and automatically mounted ext3-formatted instance store devices and partitions in stage-1 (initramfs), storing `/tmp` on the first discovered device. This behaviour, which only catered to very specific use cases and could not be disabled, has been removed. Users relying on this should provide their own implementation, and probably use ext4 and perform the mount in stage-2.
+
+- The EC2 image module previously detected and activated swap-formatted instance store devices and partitions in stage-1 (initramfs). This behaviour has been removed. Users relying on this should provide their own implementation.
+
+- `gitlab` has been upgraded from major version 15 to major version 16 and requires at least PostgreSQL 13.6. Check the [upgrade guide](#module-services-postgres-upgrading) in the NixOS manual on how to upgrade your PostgreSQL installation.
+
+- `gitlab` 16 deprecates the use of external container registries, in our case `pkgs.docker-distribution`. Module users who have [`services.gitlab.registry.enable`](#opt-services.gitlab.registry.enable) set to `true` are advised to back up their state and switch to gitlab's fork by setting [`services.gitlab.registry.package`](#opt-services.gitlab.registry.package) to `pkgs.gitlab-container-registry`.
+
+- `fail2ban` has been updated to 1.0.2, which has a few breaking changes compared to 0.11.2 ([changelog for 1.0.1](https://github.com/fail2ban/fail2ban/blob/1.0.1/ChangeLog), [changelog for 1.0.2](https://github.com/fail2ban/fail2ban/blob/1.0.2/ChangeLog))
+
+- `albert` has been updated from 0.17.6 to 0.20.13, and 0.18.0 changed the config format and many plugins ([changelog for 0.18.0](https://github.com/albertlauncher/albert/blob/v0.18.0/CHANGELOG.md))
+
+- `dokuwiki` has been updated from 2023-07-31a (Igor) to 2023-04-04 (Jack Jackrum), which has [completely removed](https://www.dokuwiki.org/changes#release_2023-04-04_jack_jackrum) the options to embed HTML and PHP for security reasons. The [htmlok plugin](https://www.dokuwiki.org/plugin:htmlok) can be used to regain this functionality.
+
+- The old unsupported version 6.x of the ELK-stack and Elastic beats have been removed. Use OpenSearch instead.
+
+- The `cosmoc` package has been removed. The upstream scripts in `cosmocc` should be used instead.
+
+- Qt 5.12 and 5.14 have been removed, as the corresponding branches have been EOL upstream for a long time. This affected under 10 packages in nixpkgs, largely unmaintained upstream as well, however, out-of-tree package expressions may need to be updated manually.
+
+- The [services.wordpress.sites.&lt;name&gt;.plugins](#opt-services.wordpress.sites._name_.plugins) and [services.wordpress.sites.&lt;name&gt;.themes](#opt-services.wordpress.sites._name_.themes) options have been converted from sets to attribute sets to allow for consumers to specify explicit install paths via attribute name.
+
+- `protonmail-bridge` package has been updated to major version 3.
+
+- Nebula now runs as a system user and group created for each nebula network, using the `CAP_NET_ADMIN` ambient capability on launch rather than starting as root. Ensure that any files each Nebula instance needs to access are owned by the correct user and group, by default `nebula-${networkName}`.
+
+- The `i18n.inputMethod.fcitx` option has been replaced with `i18n.inputMethod.fcitx5` because fcitx 4 `pkgs.fcitx` has been removed.
+
+- In `mastodon` it is now necessary to specify location of file with `PostgreSQL` database password. In `services.mastodon.database.passwordFile` parameter default value `/var/lib/mastodon/secrets/db-password` has been changed to `null`.
+
+- The `nix.readOnlyStore` option has been renamed to `boot.readOnlyNixStore` to clarify that it configures the NixOS boot process, not the Nix daemon.
+
+- The latest available version of Nextcloud is v26 (available as `pkgs.nextcloud26`) which uses PHP 8.2 as interpreter by default. The installation logic is as follows:
+  - If `system.stateVersion` is >=23.05, `pkgs.nextcloud26` will be installed by default.
+  - If `system.stateVersion` is >=22.11, `pkgs.nextcloud25` will be installed by default.
+  - Please note that an upgrade from v24 (or older) to v26 directly is not possible. Please upgrade to `nextcloud25` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud25;`](options.html#opt-services.nextcloud.package).
+  - It's recommended to use the latest version available (i.e. v26) and to specify that using `services.nextcloud.package`.
+
+- .NET 5.0 and .NET 3.1 were removed due to being end-of-life, use a newer, supported .NET version. Visit the  [Support Policy](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core) for more information.
+
+- The iputils package, which is installed by default, no longer provides the
+  `ninfod`, `rarpd` and `rdisc` tools. See [upstream's release notes](https://github.com/iputils/iputils/releases/tag/20221126) for more details and available replacements.
+
+- The ppp plugin `rp-pppoe.so` has been renamed to `pppoe.so` in ppp 2.4.9. Starting from ppp 2.5.0, there is no longer an alias for backwards compatibility. Configurations that use this plugin must be updated accordingly from `plugin rp-pppoe.so` to `plugin pppoe.so`. See [upstream change](https://github.com/ppp-project/ppp/commit/610a7bd76eb1f99f22317541b35001b1e24877ed).
+
+- [services.xserver.videoDrivers](options.html#opt-services.xserver.videoDrivers) now defaults to the `modesetting` driver over device-specific ones. The `radeon`, `amdgpu` and `nouveau` drivers are still available, but effectively unmaintained and not recommended for use.
+
+- [services.xserver.libinput.enable](options.html#opt-services.xserver.libinput.enable) is now set by default, enabling the more actively maintained and consistently behaved input device driver.
+
+- To enable the HTTP3 (QUIC) protocol for a nginx virtual host, set the `quic` attribute on it to true, e.g. `services.nginx.virtualHosts.<name>.quic = true;`.
+
+- In `services.fail2ban`, `bantime-increment.<name>` options now default to `null` (except `bantime-increment.enable`) and are used to set the corresponding option in `jail.local` only if not `null`. Also, enforce that `bantime-increment.formula` and `bantime-increment.multipliers` are not both specified.
+
+- The default `asterisk` package was changed to v20 from v19. Asterisk versions 16 and 19 have been dropped due to being EOL. You may need to update /var/lib/asterisk to match the template files in `${asterisk-20}/var/lib/asterisk`.
+
+- conntrack helper autodetection has been removed from kernels 6.0 and up upstream, and an assertion was added to ensure things don't silently stop working. Migrate your configuration to assign helpers explicitly or use an older LTS kernel branch as a temporary workaround.
+
+- The `services.pipewire.config` options have been removed, as they have basically never worked correctly. All behavior defined by the default configuration can be overridden with drop-in files as necessary - see [below](#sec-release-23.05-migration-pipewire) for details.
+
+- The catch-all `hardware.video.hidpi.enable` option was removed. Users on high density displays may want to:
+
+  - Set `services.xserver.upscaleDefaultCursor` to upscale the default X11 cursor for higher resolutions
+  - Adjust settings under `fonts.fontconfig` according to preference
+  - Adjust `console.font` according to preference, though the kernel will generally choose a reasonably sized font
+
+- `services.pipewire.media-session` and the `pipewire-media-session` package have been removed, as they are no longer supported upstream. Users are encouraged to use `services.pipewire.wireplumber` instead.
+
+- The `baget` package and module was removed due to being unmaintained.
+
+- The `qlandkartegt` and `garmindev` packages were removed due to being unmaintained and insecure.
+
+- The `go-ethereum` package has been updated to v1.11.5 and the `puppeth` command is no longer available as of v1.11.0.
+
+- The `pnpm` package has be updated to from version 7.29.1 to version 8.1.1 and Node.js 14 support has been discontinued (though, there are workarounds if Node.js 14 is still required)
+  - Migration instructions: ["Before updating pnpm to v8 in your CI, regenerate your pnpm-lock.yaml. To upgrade your lockfile, run pnpm install and commit the changes. Existing dependencies will not be updated; however, due to configuration changes in pnpm v8, some missing peer dependencies may be added to the lockfile and some packages may get deduplicated. You can commit the new lockfile even before upgrading Node.js in the CI, as pnpm v7 already supports the new lockfile format."](https://github.com/pnpm/pnpm/releases/tag/v8.0.0)
+
+- The `zplug` package changes its output path from `$out` to `$out/share/zplug`. Users should update their dependency on `${pkgs.zplug}/init.zsh` to `${pkgs.zplug}/share/zplug/init.zsh`.
+
+- The `pict-rs` package was updated from an 0.3 alpha release to 0.3 stable, and related environment variables now require two underscores instead of one.
+
+- The `shattered-pixel-dungeon` game was updated from 1.1.2 to 2.0.2.
+  - The location of game data has changed. To migrate it, run `mv ~/.shatteredpixel ~/.local/share/.shatteredpixel`
+  - The update will delete all your in-progress games.
+
+- `espanso` has been updated to major version 2. Therefore, migration steps may need to be performed. See [the official migration instructions](https://espanso.org/docs/migration/overview/) for how to perform these migrations. Further, `espanso-wayland` can now be used for Wayland support.
+
+- Only `k3s` version 1.26 is included. Users of the `k3s_1_24` or `k3s_1_25` packages should upgrade to use the `1.26` version of the package.
+
+- The `nerdfonts` package has been updated to major version 3, which includes potential [breaking changes](https://github.com/ryanoasis/nerd-fonts/releases/tag/v3.0.0).
+
+## Other Notable Changes {#sec-release-23.05-notable-changes}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- To follow [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) a few options of `openssh` have been moved from `extraConfig` to the new freeform option `settings` and renamed, e.g.:
+  - `services.openssh.forwardX11` to `services.openssh.settings.X11Forwarding`
+  - `services.openssh.kbdInteractiveAuthentication` -> `services.openssh.settings.KbdInteractiveAuthentication`
+  - `services.openssh.passwordAuthentication` to `services.openssh.settings.PasswordAuthentication`
+  - `services.openssh.useDns` to `services.openssh.settings.UseDns`
+  - `services.openssh.permitRootLogin` to `services.openssh.settings.PermitRootLogin`
+  - `services.openssh.logLevel` to `services.openssh.settings.LogLevel`
+  - `services.openssh.kexAlgorithms` to `services.openssh.settings.KexAlgorithms`
+  - `services.openssh.macs` to `services.openssh.settings.Macs`
+  - `services.openssh.ciphers` to `services.openssh.settings.Ciphers`
+  - `services.openssh.gatewayPorts` to `services.openssh.settings.GatewayPorts`
+
+
+- `vim_configurable` has been renamed to `vim-full` to avoid confusion: `vim-full`'s build-time features are configurable, but both `vim` and `vim-full` are _customizable_ (in the sense of user configuration, like vimrc).
+
+- Pantheon now defaults to Mutter 43 and GNOME settings daemon 43, all Pantheon packages are now tracking elementary OS 7 updates.
+
+- The module for the application firewall `opensnitch` got the ability to configure rules. Available as [services.opensnitch.rules](#opt-services.opensnitch.rules)
+
+- The module `usbmuxd` now has the ability to change the package used by the daemon. In case you're experiencing issues with `usbmuxd` you can try an alternative program like `usbmuxd2`. Available as [services.usbmuxd.package](#opt-services.usbmuxd.package)
+
+- `netbox` was updated to 3.5. NixOS' `services.netbox.package` still defaults to 3.3 if `stateVersion` is earlier than 23.05. Please review upstream's breaking changes [for 3.4.0](https://github.com/netbox-community/netbox/releases/tag/v3.4.0) and [for 3.5.0](https://github.com/netbox-community/netbox/releases/tag/v3.5.0), and upgrade NetBox by changing `services.netbox.package`. Database migrations will be run automatically.
+
+- `services.netbox` now support RFC42-style options, through `services.netbox.settings`.
+
+- `services.mastodon` gained a tootctl wrapped named `mastodon-tootctl` similar to `nextcloud-occ` which can be executed from any user and switches to the configured mastodon user with sudo and sources the environment variables.
+
+- `services.borgmatic` now allows for multiple configurations, placed in `/etc/borgmatic.d/`, you can define them with `services.borgmatic.configurations`.
+
+- `service.openafsServer` features a new backup server `pkgs.fabs` as a
+  replacement for openafs's own `buserver`. See
+  [FABS](https://github.com/openafs-contrib/fabs) to check if this is an viable
+  replacement. It stores backups as volume dump files and thus better integrates
+  into contemporary backup solutions.
+
+- `services.maddy` got several updates:
+  - Configuration of users and their credentials using `services.maddy.ensureCredentials`.
+  - TLS configuration is now possible via `services.maddy.tls` with two loaders present: ACME and file based.
+
+- The `dnsmasq` service now takes configuration via the
+  `services.dnsmasq.settings` attribute set. The option
+  `services.dnsmasq.extraConfig` will be deprecated when NixOS 22.11 reaches
+  end of life.
+
+- The `dokuwiki` service is now configured via `services.dokuwiki.sites.<name>.settings` attribute set; `extraConfig` has been removed.
+  The `{aclUse,superUser,disableActions}` attributes have been renamed accordingly. `pluginsConfig` now only accepts an attribute set of booleans.
+  Passing plain PHP is no longer possible.
+  Same applies to `acl` which now also only accepts structured `settings`.
+
+- The `zsh` package changes the way to set environment variables on NixOS systems where `programs.zsh.enable` equals `false`.  It now sources `/etc/set-environment` when reading the system-level `zshenv` file.  Before, it sourced `/etc/profile` when reading the system-level `zprofile` file.
+
+- The `wordpress` service now takes configuration via the `services.wordpress.sites.<name>.settings` attribute set, `extraConfig` is still available to append  additional text to `wp-config.php`.
+
+- To reduce closure size in `nixos/modules/profiles/minimal.nix` profile disabled installation documentations and manuals. Also disabled `logrotate` and `udisks2` services.
+
+- To reduce closure size in `nixos/modules/installer/netboot/netboot-minimal.nix` profile disabled load linux firmwares, pre-installing the complete stdenv and `networking.wireless` service.
+
+- The minimal ISO image now uses the `nixos/modules/profiles/minimal.nix` profile.
+
+- NixOS installer ISOs can now be built for `powerpc64le-linux`; see `nixos/modules/installer/sd-card/sd-image-powerpc64le.nix` and [PR 192672](https://github.com/NixOS/nixpkgs/pull/192672).  Hydra does not support this platform, so you must build the binaries yourself.
+
+- The `ghcWithPackages` and `ghcWithHoogle` wrappers will now also symlink GHC's
+  and all included libraries' documentation to `$out/share/doc` for convenience.
+  If undesired, the old behavior can be restored by overriding the builders with
+  `{ installDocumentation = false; }`.
+
+- The nftables module now validates its ruleset at build time. The new `networking.nftables.checkRuleset` option allows disabling this check, which may fail when rules have very specific requirements, that the sandbox environment, by default, will not cover. The `networking.nftables.preCheckRuleset` option can be used to prepare the environment before the checks are run.
+
+- The `services.mastodon` module now supports connection to a remote `PostgreSQL` database.
+
+- [`services.nextcloud.database.createLocally`](#opt-services.nextcloud.database.createLocally) now uses socket authentication and is no longer compatible with password authentication.
+  - If you want the module to manage the database for you, unset [`services.nextcloud.config.dbpassFile`](#opt-services.nextcloud.config.dbpassFile) (and [`services.nextcloud.config.dbhost`](#opt-services.nextcloud.config.dbhost), if it's set).
+  - If you want to use password authentication **and** create the database locally, you will have to use [`services.mysql`](#opt-services.mysql.enable) to set it up.
+
+- [`services.nextcloud.config.objectstore.s3.sseCKeyFile`](#opt-services.nextcloud.config.objectstore.s3.sseCKeyFile) is a new option to enable server-side encryption with customer provided keys (SSE-C) for your S3 in Nextcloud.
+
+- NixOS swap partitions with random encryption can now control the sector size, cipher, and key size used to set up the plain encryption device over the underlying block device rather than allowing them to be determined by `cryptsetup(8)`. One can use these features like so:
+
+  ```nix
+  swapDevices = [ {
+    device = "/dev/disk/by-partlabel/swapspace";
+    randomEncryption = {
+      enable = true;
+      cipher = "aes-xts-plain64";
+      keySize = 512;
+      sectorSize = 4096;
+    };
+  } ];
+  ```
+
+- New option `security.pam.zfs` to enable unlocking and mounting of encrypted ZFS home dataset at login.
+
+- `services.peertube` now requires you to specify the secret file `secrets.secretsFile`. It can be generated by running `openssl rand -hex 32`.  Before upgrading, check the release notes for [PeerTube v5.0.0](https://github.com/Chocobozzz/PeerTube/releases/tag/v5.0.0).And backup your data.
+
+- `services.chronyd` is now started with additional systemd sandbox/hardening options for better security.
+
+- PostgreSQL has added opt-in support for [JIT compilation](https://www.postgresql.org/docs/current/jit-reason.html). It can be enabled like this:
+  ```nix
+  services.postgresql.enableJIT = true;
+  ```
+
+- `services.netdata` offers a [`services.netdata.deadlineBeforeStopSec`](#opt-services.netdata.deadlineBeforeStopSec) option which will control the deadline (in seconds) after which systemd will consider your netdata instance as dead if it didn't start in the elapsed time. It is helpful when your netdata instance takes longer to start because of a large amount of state or upgrades.
+
+- `services.dhcpcd` service stopped soliciting or accepting IPv6 Router Advertisements on interfaces that use static IPv6 addresses.
+  If your network provides both IPv6 unique local addresses (ULA) and globally unique addresses (GUA) through autoconfiguration with SLAAC, you must add the parameter `networking.dhcpcd.IPv6rs = true;`.
+
+- The module `services.headscale` was refactored to be compliant with [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md). To be precise, this means that the following things have changed:
+
+  - Most settings have been migrated below [services.headscale.settings](#opt-services.headscale.settings) which is a freeform attribute-set that will be converted into headscale's YAML config format. This means that the configuration from [headscale's example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) can be directly written as attribute-set in Nix within this option.
+
+- `services.kubo` now unmounts `ipfsMountDir` and `ipnsMountDir` even if it is killed unexpectedly when `autoMount` is enabled.
+
+- `services.grafana` listens only on localhost by default again. This was changed to the upstream default of `0.0.0.0` by accident in the freeform setting conversion.
+
+- Grafana Tempo has been updated to version 2.0. See the [upstream upgrade guide](https://grafana.com/docs/tempo/latest/release-notes/v2-0/#upgrade-considerations) for migration instructions.
+
+- A new `virtualisation.rosetta` module was added to allow running `x86_64` binaries through [Rosetta](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) inside virtualised NixOS guests on Apple Silicon. This feature works by default with the [UTM](https://docs.getutm.app/) virtualisation [package](https://search.nixos.org/packages?channel=23.05&show=utm&from=0&size=1&sort=relevance&type=packages&query=utm).
+
+- The new option `users.motdFile` allows configuring a Message Of The Day that can be updated dynamically.
+
+- The `root` package is now built with the `"-Dgnuinstall=ON"` CMake flag, making the output conform the `bin` `lib` `share` layout. In this layout, `tutorials` is under `share/doc/ROOT/`; `cmake`, `font`, `icons`, `js` and `macro` under `share/root`; `Makefile.comp` and `Makefile.config` under `etc/root`.
+
+- There are various new options in the `services.nginx` module:
+    - Enabling global redirect in `services.nginx.virtualHosts` now allows one to add exceptions with the `locations` option.
+    - The `proxyCachePath` option has been added to `services.nginx`. It allows configuring the [`proxy_cache_path`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path), that configures the storage path and various other settings for the cache.
+    - A new option `recommendedBrotliSettings` has been added to `services.nginx`. Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/blob/master/README.md).
+    - `services.nginx.recommendedProxySettings` now removes the `Connection` header preventing clients from closing backend connections.
+
+- The nginx module also received an update to `services.nginx.recommendedGzipSettings`:
+  - Enables gzip compression for only certain proxied requests.
+  - Allow checking and loading of precompressed files.
+  - Updated gzip mime-types.
+  - Increased the minimum length of a response that will be gzipped.
+
+- [Garage](https://garagehq.deuxfleurs.fr/) version is based on [system.stateVersion](options.html#opt-system.stateVersion), existing installations will keep using version 0.7. New installations will use version 0.8. In order to upgrade a Garage cluster, please follow [upstream instructions](https://garagehq.deuxfleurs.fr/documentation/cookbook/upgrading/) and configure [services.garage.package](options.html#opt-services.garage.package).
+
+- Nebula now supports the `services.nebula.networks.<name>.isRelay` and `services.nebula.networks.<name>.relays` configuration options for setting up or allowing traffic relaying. See the [announcement](https://www.defined.net/blog/announcing-relay-support-in-nebula/) for more details about relays.
+
+- Resilio sync secret keys can now be provided using a secrets file at runtime, preventing these secrets from ending up in the Nix store.
+
+- The `firewall` and `nat` modules can now optionally rely on an nftables based implementation. Enable `networking.nftables` to use it.
+
+- The `services.fwupd` module now allows arbitrary daemon settings to be configured in a structured manner ([`services.fwupd.daemonSettings`](#opt-services.fwupd.daemonSettings)).
+
+- `services.xserver.desktopManager.plasma5.phononBackend` now defaults to vlc according to [upstrean recommendation](https://community.kde.org/Distributions/Packaging_Recommendations#Non-Plasma_packages)
+
+- The `zramSwap` is now implemented with `zram-generator`, and the option `zramSwap.numDevices` for using ZRAM devices as general purpose ephemeral block devices has been removed.
+
+- As Singularity has renamed to [Apptainer](https://apptainer.org/news/community-announcement-20211130)
+  to distinguish from [an un-renamed fork by Sylabs Inc.](https://sylabs.io/2021/05/singularity-community-edition),
+  there are now two packages of Singularity/Apptainer:
+  * `apptainer`: From `github.com/apptainer/apptainer`, which is the new repo after renaming.
+  * `singularity`: From `github.com/sylabs/singularity`, which is the fork by Sylabs Inc..
+
+  `singularity-tools.buildImage` got a new input argument `singularity` to specify which package to use.
+
+- The new option `programs.singularity.enableFakeroot`, if set to `true`, provides `--fakeroot` support for `apptainer` and `singularity`.
+
+- The new option `services.tailscale.useRoutingFeatures` controls various settings for using Tailscale features like exit nodes and subnet routers. If you wish to use your machine as an exit node, you can set this setting to `server`, otherwise if you wish to use an exit node you can set this setting to `client`. The strict RPF warning has been removed as the RPF will be loosened automatically based on the value of this setting.
+
+- `openjdk` from version 11 and above is not build with `openjfx` (i.e.: JavaFX) support by default anymore. You can re-enable it by overriding, e.g.: `openjdk11.override { enableJavaFX = true; };`.
+
+- [Xastir](https://xastir.org/index.php/Main_Page) can now access AX.25 interfaces via the `libax25` package.
+
+- `nixos-version` now accepts `--configuration-revision` to display more information about the current generation revision
+
+- The option `services.nomad.extraSettingsPlugins` has been fixed to allow more than one plugin in the path.
+
+- The option `services.prometheus.exporters.pihole.interval` does not exist anymore and has been removed.
+
+- The option `services.gpsd.device` has been replaced with  `services.gpsd.devices`, which supports multiple devices.
+
+- `k3s` can now be configured with an `EnvironmentFile` for its systemd service, allowing secrets to be provided without ending up in the Nix Store.
+
+- The `gitea` module options have been moved into a freeform attribute set below `services.gitea.settings`.
+
+- `boot.initrd.luks.device.<name>` has a new `tryEmptyPassphrase` option, this is useful for OEMs who need to install an encrypted disk with a future settable passphrase
+
+- The `bind` module now allows the per-zone `allow-query` setting to be configured (previously it was hard-coded to `any`; it still defaults to `any` to retain compatibility).
+
+- The option `services.jitsi-videobridge.apis` has been renamed to `colibriRestApi` and turned into a boolean. Setting it to `true` will enable the private rest API, useful for monitoring using `services.prometheus.exporters.jitsi.enable`. Learn more about the API: "[The COLIBRI control interface (/colibri/)](https://github.com/jitsi/jitsi-videobridge/blob/v2.3/doc/rest.md)".
+
+- Booting from a volume managed by the Stratis storage management daemon is now supported. Use `fileSystems.<name>.stratis.poolUuid` to configure the pool containing the fs.
+
+## Nixpkgs internals {#sec-release-23.05-nixpkgs-internals}
+
+- `buildDunePackage` now defaults to `strictDeps = true` which means that any library should go into `buildInputs` or `checkInputs`. Any executable that is run on the building machine should go into `nativeBuildInputs` or `nativeCheckInputs` respectively. Example of executables are `ocaml`, `findlib` and `menhir`. PPXs are libraries which are built by dune and should therefore not go into `nativeBuildInputs`.
+
+- `buildFHSUserEnv` is now called `buildFHSEnv` and uses FlatPak's Bubblewrap sandboxing tool rather than Nixpkgs' own chrootenv. The old chrootenv-based implemenation is still available via `buildFHSEnvChrootenv` but is considered deprecated and will be removed when the remaining uses inside Nixpkgs have been migrated. If your FHSEnv-wrapped application misbehaves when using the new bubblewrap implementation, please create an issue in Nixpkgs.
+
+- Top-level `buildPlatform`, `hostPlatform`, `targetPlatform` have been deprecated, use `stdenv.X` instead.
+
+- `carnix` and `cratesIO` has been removed due to being unmaintained, use alternatives such as [naersk](https://github.com/nix-community/naersk) and [crate2nix](https://github.com/kolloch/crate2nix) instead.
+
+- `checkInputs` have been renamed to `nativeCheckInputs`, because they behave the same as `nativeBuildInputs` when `doCheck` is set. `checkInputs` now denote a new type of dependencies, added to `buildInputs` when `doCheck` is set. As a rule of thumb, `nativeCheckInputs` are tools on `$PATH` used during the tests, and `checkInputs` are libraries which are linked to executables built as part of the tests. Similarly, `installCheckInputs` are renamed to `nativeInstallCheckInputs`, corresponding to `nativeBuildInputs`, and `installCheckInputs` are a new type of dependencies added to `buildInputs` when `doInstallCheck` is set. (Note that this change will not cause breakage to derivations with `strictDeps` unset, which are most packages except python, rust, ocaml and go packages).
+
+- DocBook option documentation, which has been deprecated since 22.11, will now cause a warning when documentation is built. Out-of-tree modules should migrate to using CommonMark documentation as outlined in [](#sec-option-declarations) to silence this warning.
+
+  DocBook option documentation support will be removed in the next release and CommonMark will become the default. DocBook option documentation that has not been migrated until then will no longer render properly or cause errors.
+
+- `lib.systems.examples.ghcjs` and consequently `pkgsCross.ghcjs` now use the target triplet `javascript-unknown-ghcjs` instead of `js-unknown-ghcjs`. This has been done to match an [upstream decision](https://gitlab.haskell.org/ghc/ghc/-/commit/6636b670233522f01d002c9b97827d00289dbf5c) to follow Cabal's platform naming more closely. Nixpkgs will also reject `js` as an architecture name.
+
+- Lisp gained a [manual section](https://nixos.org/manual/nixpkgs/stable/#lisp), documenting a new and backwards incompatible interface. The previous interface will be removed in a future release.
+
+- Calling `makeSetupHook` without passing a `name` argument is deprecated.
+
+- `nixos/lib/make-disk-image.nix` handles `contents` arguments that are directories better, fixing a bug where it used to put them in a subdirectory of the intended `target`.
+
+- `nixos/lib/make-disk-image.nix` can now mutate EFI variables, run user-provided EFI firmware or variable templates. This is now extensively documented in the NixOS manual.
+
+- Nixpkgs now uses [IEEE-standard floating point arithmetic](https://github.com/NixOS/nixpkgs/pull/170215) on `powerpc64le-linux`.
+
+- Deprecated `xlibsWrapper` transitional package has been removed in favour of direct use of its constituents: `xorg.libX11`, `freetype` and others.
+
+## Detailed migration information {#sec-release-23.05-migration}
+
+### Pipewire configuration overrides {#sec-release-23.05-migration-pipewire}
+
+#### Why this change? {#sec-release-23.05-migration-pipewire-why}
+
+The Pipewire config semantics don't really match the NixOS module semantics, so it's extremely awkward to override the default config, especially when lists are involved. Vendoring the configuration files in nixpkgs also creates unnecessary maintenance overhead.
+
+Also, upstream added a lot of accommodations to allow doing most of the things you'd want to do with a config edit in better ways.
+
+#### Migrating your configuration {#sec-release-23.05-migration-pipewire-how}
+
+Compare your settings to [the defaults](https://gitlab.freedesktop.org/pipewire/pipewire/-/tree/master/src/daemon) and where your configuration differs from them.
+
+Then, create a drop-in JSON file in `/etc/pipewire/<config file name>.d/99-custom.conf` (the actual filename can be anything) and migrate your changes to it according to the following sections.
+
+Repeat for every file you've modified, changing the directory name accordingly.
+
+#### Things you can just copy over {#sec-release-23.05-migration-pipewire-simple}
+
+If you are:
+
+- setting properties via `*.properties`
+- loading a new module to `context.modules`
+- creating new objects with `context.objects`
+- declaring SPA libraries with `context.spa-libs`
+- running custom commands with `context.exec`
+- adding new rules with `*.rules`
+- running custom PulseAudio commands with `pulse.cmd`
+
+Simply move the definitions into the drop-in.
+
+Note that the use of `context.exec` is not recommended and other methods of running your thing are likely a better option.
+
+```json
+{
+  "context.properties": {
+    "your.property.name": "your.property.value"
+  },
+  "context.modules": [
+    { "name": "libpipewire-module-my-cool-thing" }
+  ],
+  "context.objects": [
+    { "factory": { ... } }
+  ],
+  "alsa.rules": [
+    { "matches: { ... }, "actions": { ... } }
+  ]
+}
+```
+
+#### Removing a module from `context.modules` {#sec-release-23.05-migration-pipewire-removing-modules}
+
+Look for an option to disable it via `context.properties` (`"module.x11.bell": "false"` is likely the most common use case here).
+If one is not available, proceed to [Nuclear option](#sec-release-23.05-migration-pipewire).
+
+#### Modifying a module's parameters in `context.modules` {#sec-release-23.05-migration-pipewire-modifying-modules}
+
+For most modules (e.g. `libpipewire-module-rt`) it's enough to load the module again with the new arguments, e.g.:
+
+```json
+{
+  "context.modules": [
+    {
+      "name": "libpipewire-module-rt",
+      "args": {
+        "rt.prio": 90
+      }
+    }
+  ]
+}
+```
+
+Note that `module-rt` specifically will generally use the highest values available by default, so setting limits on the `pipewire` systemd service is preferable to reloading.
+
+If reloading the module is not an option, proceed to [Nuclear option](#sec-release-23.05-migration-pipewire).
+
+#### Nuclear option {#sec-release-23.05-migration-pipewire-nuclear}
+If all else fails, you can still manually copy the contents of the default configuration file
+from `${pkgs.pipewire.lib}/share/pipewire` to `/etc/pipewire` and edit it to fully override the default.
+However, this should be done only as a last resort. Please talk to the Pipewire maintainers if you ever need to do this.
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md
new file mode 100644
index 000000000000..bc10f5b587c7
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md
@@ -0,0 +1,45 @@
+# Release 23.11 (“Tapir”, 2023.11/??) {#sec-release-23.11}
+
+## Highlights {#sec-release-23.11-highlights}
+
+- FoundationDB now defaults to major version 7.
+
+## New Services {#sec-release-23.11-new-services}
+
+- Create the first release note entry in this section!
+
+- [acme-dns](https://github.com/joohoi/acme-dns), a limited DNS server to handle ACME DNS challenges easily and securely. Available as [services.acme-dns](#opt-services.acme-dns.enable).
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- [river](https://github.com/riverwm/river), A dynamic tiling wayland compositor. Available as [programs.river](#opt-programs.river.enable).
+
+- [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable).
+
+## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
+
+- `writeTextFile` now requires `executable` to be boolean, values like `null` or `""` will now fail to evaluate.
+
+- The latest version of `clonehero` now stores custom content in `~/.clonehero`. See the [migration instructions](https://clonehero.net/2022/11/29/v23-to-v1-migration-instructions.html). Typically, these content files would exist along side the binary, but the previous build used a wrapper script that would store them in `~/.config/unity3d/srylain Inc_/Clone Hero`.
+
+- `python3.pkgs.fetchPypi` (and `python3Packages.fetchPypi`) has been deprecated in favor of top-level `fetchPypi`.
+
+- `mariadb` now defaults to `mariadb_1011` instead of `mariadb_106`, meaning the default version was upgraded from 10.6.x to 10.11.x. See the [upgrade notes](https://mariadb.com/kb/en/upgrading-from-mariadb-10-6-to-mariadb-10-11/) for potential issues.
+
+- `etcd` has been updated to 3.5, you will want to read the [3.3 to 3.4](https://etcd.io/docs/v3.5/upgrades/upgrade_3_4/) and [3.4 to 3.5](https://etcd.io/docs/v3.5/upgrades/upgrade_3_5/) upgrade guides
+
+- `himalaya` has been updated to `0.8.0`, which drops the native TLS support (in favor of Rustls) and add OAuth 2.0 support. See the [release note](https://github.com/soywod/himalaya/releases/tag/v0.8.0) for more details.
+
+- `util-linux` is now supported on Darwin and is no longer an alias to `unixtools`. Use the `unixtools.util-linux` package for access to the Apple variants of the utilities.
+
+- `fileSystems.<name>.autoFormat` now uses `systemd-makefs`, which does not accept formatting options. Therefore, `fileSystems.<name>.formatOptions` has been removed.
+
+- `fileSystems.<name>.autoResize` now uses `systemd-growfs` to resize the file system online in stage 2. This means that `f2fs` and `ext2` can no longer be auto resized, while `xfs` and `btrfs` now can be.
+
+## Other Notable Changes {#sec-release-23.11-notable-changes}
+
+- The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration.
+
+- A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.
+
+- `services.nginx` gained a `defaultListen` option at server-level with support for PROXY protocol listeners, also `proxyProtocol` is now exposed in `services.nginx.virtualHosts.<name>.listen` option. It is now possible to run PROXY listeners and non-PROXY listeners at a server-level, see [#213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more details.
diff --git a/nixpkgs/nixos/doc/manual/shell.nix b/nixpkgs/nixos/doc/manual/shell.nix
deleted file mode 100644
index e5ec9b8f97f7..000000000000
--- a/nixpkgs/nixos/doc/manual/shell.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-let
-  pkgs = import ../../.. { };
-in
-pkgs.mkShell {
-  name = "nixos-manual";
-
-  packages = with pkgs; [ xmlformat jing xmloscopy ruby ];
-}
diff --git a/nixpkgs/nixos/doc/varlistentry-fixer.rb b/nixpkgs/nixos/doc/varlistentry-fixer.rb
deleted file mode 100755
index 02168016b554..000000000000
--- a/nixpkgs/nixos/doc/varlistentry-fixer.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/env ruby
-
-# This script is written intended as a living, evolving tooling
-# to fix oopsies within the docbook documentation.
-#
-# This is *not* a formatter. It, instead, handles some known cases
-# where something bad happened, and fixing it manually is tedious.
-#
-# Read the code to see the different cases it handles.
-#
-# ALWAYS `make format` after fixing with this!
-# ALWAYS read the changes, this tool isn't yet proven to be always right.
-
-require "rexml/document"
-include REXML
-
-if ARGV.length < 1 then
-  $stderr.puts "Needs a filename."
-  exit 1
-end
-
-filename = ARGV.shift
-doc = Document.new(File.open(filename))
-
-$touched = false
-
-# Fixing varnames having a sibling element without spacing.
-# This is to fix an initial `xmlformat` issue where `term`
-# would mangle as spaces.
-#
-#   <varlistentry>
-#    <term><varname>types.separatedString</varname><replaceable>sep</replaceable> <----
-#    </term>
-#    ...
-#
-# Generates: types.separatedStringsep
-#                               ^^^^
-#
-# <varlistentry xml:id='fun-makeWrapper'>
-#  <term>
-#   <function>makeWrapper</function><replaceable>executable</replaceable><replaceable>wrapperfile</replaceable><replaceable>args</replaceable>  <----
-#  </term>
-#
-# Generates: makeWrapperexecutablewrapperfileargs
-#                     ^^^^      ^^^^    ^^  ^^
-#
-#    <term>
-#     <option>--option</option><replaceable>name</replaceable><replaceable>value</replaceable> <-----
-#    </term>
-#
-# Generates: --optionnamevalue
-#                   ^^  ^^
-doc.elements.each("//varlistentry/term") do |term|
-  ["varname", "function", "option", "replaceable"].each do |prev_name|
-    term.elements.each(prev_name) do |el|
-      if el.next_element and
-          el.next_element.name == "replaceable" and
-          el.next_sibling_node.class == Element
-        then
-        $touched = true
-        term.insert_after(el, Text.new(" "))
-      end
-    end
-  end
-end
-
-
-
-#  <cmdsynopsis>
-#   <command>nixos-option</command>
-#   <arg>
-#    <option>-I</option><replaceable>path</replaceable>        <------
-#   </arg>
-#
-# Generates: -Ipath
-#             ^^
-doc.elements.each("//cmdsynopsis/arg") do |term|
-  ["option", "replaceable"].each do |prev_name|
-    term.elements.each(prev_name) do |el|
-      if el.next_element and
-        el.next_element.name == "replaceable" and
-        el.next_sibling_node.class == Element
-      then
-        $touched = true
-        term.insert_after(el, Text.new(" "))
-      end
-    end
-  end
-end
-
-#  <cmdsynopsis>
-#   <arg>
-#    <group choice='req'>
-#    <arg choice='plain'>
-#     <option>--profile-name</option>
-#    </arg>
-#
-#    <arg choice='plain'>
-#     <option>-p</option>
-#    </arg>
-#     </group><replaceable>name</replaceable>   <----
-#   </arg>
-#
-# Generates: [{--profile-name | -p }name]
-#                                   ^^^^
-doc.elements.each("//cmdsynopsis/arg") do |term|
-  ["group"].each do |prev_name|
-    term.elements.each(prev_name) do |el|
-      if el.next_element and
-        el.next_element.name == "replaceable" and
-        el.next_sibling_node.class == Element
-      then
-        $touched = true
-        term.insert_after(el, Text.new(" "))
-      end
-    end
-  end
-end
-
-
-if $touched then
-  doc.context[:attribute_quote] = :quote
-  doc.write(output: File.open(filename, "w"))
-end
diff --git a/nixpkgs/nixos/doc/xmlformat.conf b/nixpkgs/nixos/doc/xmlformat.conf
deleted file mode 100644
index c3f39c7fd81b..000000000000
--- a/nixpkgs/nixos/doc/xmlformat.conf
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# DocBook Configuration file for "xmlformat"
-# see http://www.kitebird.com/software/xmlformat/
-# 10 Sept. 2004
-#
-
-# Only block elements
-ackno address appendix article biblioentry bibliography bibliomixed \
-biblioset blockquote book bridgehead callout calloutlist caption caution \
-chapter chapterinfo classsynopsis cmdsynopsis colophon constraintdef \
-constructorsynopsis dedication destructorsynopsis entry epigraph equation example \
-figure formalpara funcsynopsis glossary glossdef glossdiv glossentry glosslist \
-glosssee glossseealso graphic graphicco highlights imageobjectco important \
-index indexdiv indexentry indexinfo info informalequation informalexample \
-informalfigure informaltable legalnotice literallayout lot lotentry mediaobject \
-mediaobjectco msgmain msgset note orderedlist para part preface primaryie \
-procedure qandadiv qandaentry qandaset refentry refentrytitle reference \
-refnamediv refsect1 refsect2 refsect3 refsection revhistory screenshot sect1 \
-sect2 sect3 sect4 sect5 section seglistitem set setindex sidebar simpara \
-simplesect step substeps synopfragment synopsis table term title \
-toc variablelist varlistentry warning itemizedlist listitem \
-footnote colspec partintro row simplelist subtitle tbody tgroup thead tip
-  format      block
-  normalize   no
-
-
-#appendix bibliography chapter glossary preface reference
-#  element-break   3
-
-sect1 section
-  element-break   2
-
-
-#
-para abstract
-  format       block
-  entry-break  1
-  exit-break   1
-  normalize    yes
-
-title
-  format       block
-  normalize = yes
-  entry-break = 0
-  exit-break = 0
-
-# Inline elements
-abbrev accel acronym action application citation citebiblioid citerefentry citetitle \
-classname co code command computeroutput constant country database date email emphasis \
-envar errorcode errorname errortext errortype exceptionname fax filename \
-firstname firstterm footnoteref foreignphrase funcdef funcparams function \
-glossterm group guibutton guiicon guilabel guimenu guimenuitem guisubmenu \
-hardware holder honorific indexterm inlineequation inlinegraphic inlinemediaobject \
-interface interfacename \
-keycap keycode keycombo keysym lineage link literal manvolnum markup medialabel \
-menuchoice methodname methodparam modifier mousebutton olink ooclass ooexception \
-oointerface option optional otheraddr othername package paramdef parameter personname \
-phrase pob postcode productname prompt property quote refpurpose replaceable \
-returnvalue revnumber sgmltag state street structfield structname subscript \
-superscript surname symbol systemitem token trademark type ulink userinput \
-uri varargs varname void wordasword xref year mathphrase member tag
-  format       inline
-
-programlisting screen
-  format       verbatim
-  entry-break = 0
-  exit-break = 0
-
-# This is needed so that the spacing inside those tags is kept.
-term cmdsynopsis arg
-  normalize yes
-  format    block
diff --git a/nixpkgs/nixos/lib/build-vms.nix b/nixpkgs/nixos/lib/build-vms.nix
deleted file mode 100644
index 18af49db1777..000000000000
--- a/nixpkgs/nixos/lib/build-vms.nix
+++ /dev/null
@@ -1,113 +0,0 @@
-{ system
-, # Use a minimal kernel?
-  minimal ? false
-, # Ignored
-  config ? null
-, # Nixpkgs, for qemu, lib and more
-  pkgs, lib
-, # !!! See comment about args in lib/modules.nix
-  specialArgs ? {}
-, # NixOS configuration to add to the VMs
-  extraConfigurations ? []
-}:
-
-with lib;
-
-rec {
-
-  inherit pkgs;
-
-  # Build a virtual network from an attribute set `{ machine1 =
-  # config1; ... machineN = configN; }', where `machineX' is the
-  # hostname and `configX' is a NixOS system configuration.  Each
-  # machine is given an arbitrary IP address in the virtual network.
-  buildVirtualNetwork =
-    nodes: let nodesOut = mapAttrs (n: buildVM nodesOut) (assignIPAddresses nodes); in nodesOut;
-
-
-  buildVM =
-    nodes: configurations:
-
-    import ./eval-config.nix {
-      inherit system specialArgs;
-      modules = configurations ++ extraConfigurations;
-      baseModules =  (import ../modules/module-list.nix) ++
-        [ ../modules/virtualisation/qemu-vm.nix
-          ../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
-          { key = "no-manual"; documentation.nixos.enable = false; }
-          { key = "no-revision";
-            # Make the revision metadata constant, in order to avoid needless retesting.
-            # The human version (e.g. 21.05-pre) is left as is, because it is useful
-            # for external modules that test with e.g. testers.nixosTest and rely on that
-            # version number.
-            config.system.nixos.revision = mkForce "constant-nixos-revision";
-          }
-          { key = "nodes"; _module.args.nodes = nodes; }
-        ] ++ optional minimal ../modules/testing/minimal-kernel.nix;
-    };
-
-
-  # Given an attribute set { machine1 = config1; ... machineN =
-  # configN; }, sequentially assign IP addresses in the 192.168.1.0/24
-  # range to each machine, and set the hostname to the attribute name.
-  assignIPAddresses = nodes:
-
-    let
-
-      machines = attrNames nodes;
-
-      machinesNumbered = zipLists machines (range 1 254);
-
-      nodes_ = forEach machinesNumbered (m: nameValuePair m.fst
-        [ ( { config, nodes, ... }:
-            let
-              interfacesNumbered = zipLists config.virtualisation.vlans (range 1 255);
-              interfaces = forEach interfacesNumbered ({ fst, snd }:
-                nameValuePair "eth${toString snd}" { ipv4.addresses =
-                  [ { address = "192.168.${toString fst}.${toString m.snd}";
-                      prefixLength = 24;
-                  } ];
-                });
-
-              networkConfig =
-                { networking.hostName = mkDefault m.fst;
-
-                  networking.interfaces = listToAttrs interfaces;
-
-                  networking.primaryIPAddress =
-                    optionalString (interfaces != []) (head (head interfaces).value.ipv4.addresses).address;
-
-                  # Put the IP addresses of all VMs in this machine's
-                  # /etc/hosts file.  If a machine has multiple
-                  # interfaces, use the IP address corresponding to
-                  # the first interface (i.e. the first network in its
-                  # virtualisation.vlans option).
-                  networking.extraHosts = flip concatMapStrings machines
-                    (m': let config = (getAttr m' nodes).config; in
-                      optionalString (config.networking.primaryIPAddress != "")
-                        ("${config.networking.primaryIPAddress} " +
-                         optionalString (config.networking.domain != null)
-                           "${config.networking.hostName}.${config.networking.domain} " +
-                         "${config.networking.hostName}\n"));
-
-                  virtualisation.qemu.options =
-                    let qemu-common = import ../lib/qemu-common.nix { inherit lib pkgs; };
-                    in flip concatMap interfacesNumbered
-                      ({ fst, snd }: qemu-common.qemuNICFlags snd fst m.snd);
-                };
-
-              in
-                { key = "ip-address";
-                  config = networkConfig // {
-                    # Expose the networkConfig items for tests like nixops
-                    # that need to recreate the network config.
-                    system.build.networkConfig = networkConfig;
-                  };
-                }
-          )
-          (getAttr m.fst nodes)
-        ] );
-
-    in listToAttrs nodes_;
-
-}
diff --git a/nixpkgs/nixos/lib/default.nix b/nixpkgs/nixos/lib/default.nix
index 2b3056e01457..65d91342d4d1 100644
--- a/nixpkgs/nixos/lib/default.nix
+++ b/nixpkgs/nixos/lib/default.nix
@@ -21,6 +21,8 @@ let
   seqAttrsIf = cond: a: lib.mapAttrs (_: v: seqIf cond a v);
 
   eval-config-minimal = import ./eval-config-minimal.nix { inherit lib; };
+
+  testing-lib = import ./testing/default.nix { inherit lib; };
 in
 /*
   This attribute set appears as lib.nixos in the flake, or can be imported
@@ -30,4 +32,10 @@ in
   inherit (seqAttrsIf (!featureFlags?minimalModules) minimalModulesWarning eval-config-minimal)
     evalModules
     ;
+
+  inherit (testing-lib)
+    evalTest
+    runTest
+    ;
+
 }
diff --git a/nixpkgs/nixos/lib/eval-cacheable-options.nix b/nixpkgs/nixos/lib/eval-cacheable-options.nix
index c3ba2ce66375..d26967ebe09b 100644
--- a/nixpkgs/nixos/lib/eval-cacheable-options.nix
+++ b/nixpkgs/nixos/lib/eval-cacheable-options.nix
@@ -33,6 +33,7 @@ let
     ];
     specialArgs = {
       inherit config pkgs utils;
+      class = "nixos";
     };
   };
   docs = import "${nixosPath}/doc/manual" {
diff --git a/nixpkgs/nixos/lib/eval-config-minimal.nix b/nixpkgs/nixos/lib/eval-config-minimal.nix
index d45b9ffd4261..036389121973 100644
--- a/nixpkgs/nixos/lib/eval-config-minimal.nix
+++ b/nixpkgs/nixos/lib/eval-config-minimal.nix
@@ -38,6 +38,7 @@ let
   #       is experimental.
   lib.evalModules {
     inherit prefix modules;
+    class = "nixos";
     specialArgs = {
       modulesPath = builtins.toString ../modules;
     } // specialArgs;
diff --git a/nixpkgs/nixos/lib/eval-config.nix b/nixpkgs/nixos/lib/eval-config.nix
index 791a03a3ba3c..058ab7280ccc 100644
--- a/nixpkgs/nixos/lib/eval-config.nix
+++ b/nixpkgs/nixos/lib/eval-config.nix
@@ -17,6 +17,8 @@ evalConfigArgs@
   # be set modularly anyway.
   pkgs ? null
 , # !!! what do we gain by making this configurable?
+  #     we can add modules that are included in specialisations, regardless
+  #     of inheritParentConfig.
   baseModules ? import ../modules/module-list.nix
 , # !!! See comment about args in lib/modules.nix
   extraArgs ? {}
@@ -36,6 +38,8 @@ let pkgs_ = pkgs;
 in
 
 let
+  inherit (lib) optional;
+
   evalModulesMinimal = (import ./default.nix {
     inherit lib;
     # Implicit use of feature is noted in implementation.
@@ -45,15 +49,19 @@ let
   pkgsModule = rec {
     _file = ./eval-config.nix;
     key = _file;
-    config = {
-      # Explicit `nixpkgs.system` or `nixpkgs.localSystem` should override
-      # this.  Since the latter defaults to the former, the former should
-      # default to the argument. That way this new default could propagate all
-      # they way through, but has the last priority behind everything else.
-      nixpkgs.system = lib.mkIf (system != null) (lib.mkDefault system);
-
-      _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_);
-    };
+    config = lib.mkMerge (
+      (optional (system != null) {
+        # Explicit `nixpkgs.system` or `nixpkgs.localSystem` should override
+        # this.  Since the latter defaults to the former, the former should
+        # default to the argument. That way this new default could propagate all
+        # they way through, but has the last priority behind everything else.
+        nixpkgs.system = lib.mkDefault system;
+      })
+      ++
+      (optional (pkgs_ != null) {
+        _module.args.pkgs = lib.mkForce pkgs_;
+      })
+    );
   };
 
   withWarnings = x:
diff --git a/nixpkgs/nixos/lib/make-channel.nix b/nixpkgs/nixos/lib/make-channel.nix
index 9b920b989fcf..0a511468fb2d 100644
--- a/nixpkgs/nixos/lib/make-channel.nix
+++ b/nixpkgs/nixos/lib/make-channel.nix
@@ -23,7 +23,7 @@ pkgs.releaseTools.makeSourceTarball {
     cp -prd . ../$releaseName
     chmod -R u+w ../$releaseName
     ln -s . ../$releaseName/nixpkgs # hack to make ‘<nixpkgs>’ work
-    NIX_STATE_DIR=$TMPDIR nix-env -f ../$releaseName/default.nix -qaP --meta --xml \* > /dev/null
+    NIX_STATE_DIR=$TMPDIR nix-env -f ../$releaseName/default.nix -qaP --meta --show-trace --xml \* > /dev/null
     cd ..
     chmod -R u+w $releaseName
     tar cfJ $out/tarballs/$releaseName.tar.xz $releaseName
diff --git a/nixpkgs/nixos/lib/make-disk-image.nix b/nixpkgs/nixos/lib/make-disk-image.nix
index e784ec9e6778..33d834e36b44 100644
--- a/nixpkgs/nixos/lib/make-disk-image.nix
+++ b/nixpkgs/nixos/lib/make-disk-image.nix
@@ -1,3 +1,85 @@
+/* Technical details
+
+`make-disk-image` has a bit of magic to minimize the amount of work to do in a virtual machine.
+
+It relies on the [LKL (Linux Kernel Library) project](https://github.com/lkl/linux) which provides Linux kernel as userspace library.
+
+The Nix-store only image only need to run LKL tools to produce an image and will never spawn a virtual machine, whereas full images will always require a virtual machine, but also use LKL.
+
+### Image preparation phase
+
+Image preparation phase will produce the initial image layout in a folder:
+
+- devise a root folder based on `$PWD`
+- prepare the contents by copying and restoring ACLs in this root folder
+- load in the Nix store database all additional paths computed by `pkgs.closureInfo` in a temporary Nix store
+- run `nixos-install` in a temporary folder
+- transfer from the temporary store the additional paths registered to the installed NixOS
+- compute the size of the disk image based on the apparent size of the root folder
+- partition the disk image using the corresponding script according to the partition table type
+- format the partitions if needed
+- use `cptofs` (LKL tool) to copy the root folder inside the disk image
+
+At this step, the disk image already contains the Nix store, it now only needs to be converted to the desired format to be used.
+
+### Image conversion phase
+
+Using `qemu-img`, the disk image is converted from a raw format to the desired format: qcow2(-compressed), vdi, vpc.
+
+### Image Partitioning
+
+#### `none`
+
+No partition table layout is written. The image is a bare filesystem image.
+
+#### `legacy`
+
+The image is partitioned using MBR. There is one primary ext4 partition starting at 1 MiB that fills the rest of the disk image.
+
+This partition layout is unsuitable for UEFI.
+
+#### `legacy+gpt`
+
+This partition table type uses GPT and:
+
+- create a "no filesystem" partition from 1MiB to 2MiB ;
+- set `bios_grub` flag on this "no filesystem" partition, which marks it as a [GRUB BIOS partition](https://www.gnu.org/software/parted/manual/html_node/set.html) ;
+- create a primary ext4 partition starting at 2MiB and extending to the full disk image ;
+- perform optimal alignments checks on each partition
+
+This partition layout is unsuitable for UEFI boot, because it has no ESP (EFI System Partition) partition. It can work with CSM (Compatibility Support Module) which emulates legacy (BIOS) boot for UEFI.
+
+#### `efi`
+
+This partition table type uses GPT and:
+
+- creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
+- creates an primary ext4 partition starting after the boot partition and extending to the full disk image
+
+#### `hybrid`
+
+This partition table type uses GPT and:
+
+- creates a "no filesystem" partition from 0 to 1MiB, set `bios_grub` flag on it ;
+- creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
+- creates a primary ext4 partition starting after the boot one and extending to the full disk image
+
+This partition could be booted by a BIOS able to understand GPT layouts and recognizing the MBR at the start.
+
+### How to run determinism analysis on results?
+
+Build your derivation with `--check` to rebuild it and verify it is the same.
+
+If it fails, you will be left with two folders with one having `.check`.
+
+You can use `diffoscope` to see the differences between the folders.
+
+However, `diffoscope` is currently not able to diff two QCOW2 filesystems, thus, it is advised to use raw format.
+
+Even if you use raw disks, `diffoscope` cannot diff the partition table and partitions recursively.
+
+To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$image-p$i.raw skip=$start count=$sectors` for each `(start, sectors)` listed in the `fdisk` output. Now, you will have each partition as a separate file and you can compare them in pairs.
+*/
 { pkgs
 , lib
 
@@ -47,6 +129,18 @@
 , # Whether to invoke `switch-to-configuration boot` during image creation
   installBootLoader ? true
 
+, # Whether to output have EFIVARS available in $out/efi-vars.fd and use it during disk creation
+  touchEFIVars ? false
+
+, # OVMF firmware derivation
+  OVMF ? pkgs.OVMF.fd
+
+, # EFI firmware
+  efiFirmware ? OVMF.firmware
+
+, # EFI variables
+  efiVariables ? OVMF.variables
+
 , # The root file system type.
   fsType ? "ext4"
 
@@ -60,6 +154,9 @@
 , # Shell code executed after the VM has finished.
   postVM ? ""
 
+, # Guest memory size
+  memSize ? 1024
+
 , # Copy the contents of the Nix store to the root of the image and
   # skip further setup. Incompatible with `contents`,
   # `installBootLoader` and `configFile`.
@@ -70,6 +167,22 @@
 , # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
   format ? "raw"
 
+  # Whether to fix:
+  #   - GPT Disk Unique Identifier (diskGUID)
+  #   - GPT Partition Unique Identifier: depends on the layout, root partition UUID can be controlled through `rootGPUID` option
+  #   - GPT Partition Type Identifier: fixed according to the layout, e.g. ESP partition, etc. through `parted` invocation.
+  #   - Filesystem Unique Identifier when fsType = ext4 for *root partition*.
+  # BIOS/MBR support is "best effort" at the moment.
+  # Boot partitions may not be deterministic.
+  # Also, to fix last time checked of the ext4 partition if fsType = ext4.
+, deterministic ? true
+
+  # GPT Partition Unique Identifier for root partition.
+, rootGPUID ? "F222513B-DED1-49FA-B591-20CE86A2FE7F"
+  # When fsType = ext4, this is the root Filesystem Unique Identifier.
+  # TODO: support other filesystems someday.
+, rootFSUID ? (if fsType == "ext4" then rootGPUID else null)
+
 , # 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
@@ -80,15 +193,18 @@
   additionalPaths ? []
 }:
 
-assert partitionTableType == "legacy" || partitionTableType == "legacy+gpt" || partitionTableType == "efi" || partitionTableType == "hybrid" || partitionTableType == "none";
-# We use -E offset=X below, which is only supported by e2fsprogs
-assert partitionTableType != "none" -> fsType == "ext4";
+assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "hybrid" "none" ]);
+assert (lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null) "In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID.");
+  # We use -E offset=X below, which is only supported by e2fsprogs
+assert (lib.assertMsg (partitionTableType != "none" -> fsType == "ext4") "to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4");
+assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi or legacy+gpt.");
+  # If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader.
+assert (lib.assertMsg (onlyNixStore -> contents == [] && configFile == null && !installBootLoader) "In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed.");
 # Either both or none of {user,group} need to be set
-assert lib.all
+assert (lib.assertMsg (lib.all
          (attrs: ((attrs.user  or null) == null)
               == ((attrs.group or null) == null))
-         contents;
-assert onlyNixStore -> contents == [] && configFile == null && !installBootLoader;
+        contents) "Contents of the disk image should set none of {user, group} or both at the same time.");
 
 with lib;
 
@@ -127,6 +243,14 @@ let format' = format; in let
         mkpart primary ext4 2MB -1 \
         align-check optimal 2 \
         print
+      ${optionalString deterministic ''
+          sgdisk \
+          --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
+          --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
+          --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
+          --partition-guid=3:${rootGPUID} \
+          $diskImage
+      ''}
     '';
     efi = ''
       parted --script $diskImage -- \
@@ -134,6 +258,13 @@ let format' = format; in let
         mkpart ESP fat32 8MiB ${bootSize} \
         set 1 boot on \
         mkpart primary ext4 ${bootSize} -1
+      ${optionalString deterministic ''
+          sgdisk \
+          --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
+          --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
+          --partition-guid=2:${rootGPUID} \
+          $diskImage
+      ''}
     '';
     hybrid = ''
       parted --script $diskImage -- \
@@ -143,10 +274,20 @@ let format' = format; in let
         mkpart no-fs 0 1024KiB \
         set 2 bios_grub on \
         mkpart primary ext4 ${bootSize} -1
+      ${optionalString deterministic ''
+          sgdisk \
+          --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
+          --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
+          --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
+          --partition-guid=3:${rootGPUID} \
+          $diskImage
+      ''}
     '';
     none = "";
   }.${partitionTableType};
 
+  useEFIBoot = touchEFIVars;
+
   nixpkgs = cleanSource pkgs.path;
 
   # FIXME: merge with channel.nix / make-channel.nix.
@@ -171,7 +312,9 @@ let format' = format; in let
       config.system.build.nixos-enter
       nix
       systemdMinimal
-    ] ++ stdenv.initialPath);
+    ]
+    ++ lib.optional deterministic gptfdisk
+    ++ stdenv.initialPath);
 
   # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
   # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
@@ -259,11 +402,16 @@ let format' = format; in let
         done
       else
         mkdir -p $root/$(dirname $target)
-        if ! [ -e $root/$target ]; then
-          rsync $rsync_flags $source $root/$target
-        else
+        if [ -e $root/$target ]; then
           echo "duplicate entry $target -> $source"
           exit 1
+        elif [ -d $source ]; then
+          # Append a slash to the end of source to get rsync to copy the
+          # directory _to_ the target instead of _inside_ the target.
+          # (See `man rsync`'s note on a trailing slash.)
+          rsync $rsync_flags $source/ $root/$target
+        else
+          rsync $rsync_flags $source $root/$target
         fi
       fi
     done
@@ -363,25 +511,40 @@ let format' = format; in let
     ${if format == "raw" then ''
       mv $diskImage $out/${filename}
     '' else ''
-      ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
+      ${pkgs.qemu-utils}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
     ''}
     diskImage=$out/${filename}
   '';
 
+  createEFIVars = ''
+    efiVars=$out/efi-vars.fd
+    cp ${efiVariables} $efiVars
+    chmod 0644 $efiVars
+  '';
+
   buildImage = pkgs.vmTools.runInLinuxVM (
     pkgs.runCommand name {
-      preVM = prepareImage;
+      preVM = prepareImage + lib.optionalString touchEFIVars createEFIVars;
       buildInputs = with pkgs; [ util-linux e2fsprogs dosfstools ];
       postVM = moveOrConvertImage + postVM;
-      memSize = 1024;
+      QEMU_OPTS =
+        concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
+        ++ lib.optionals touchEFIVars [
+          "-drive if=pflash,format=raw,unit=1,file=$efiVars"
+        ]
+      );
+      inherit memSize;
     } ''
       export PATH=${binPath}:$PATH
 
       rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"}
 
-      # Some tools assume these exist
-      ln -s vda /dev/xvda
-      ln -s vda /dev/sda
+      # It is necessary to set root filesystem unique identifier in advance, otherwise
+      # bootloader might get the wrong one and fail to boot.
+      # At the end, we reset again because we want deterministic timestamps.
+      ${optionalString (fsType == "ext4" && deterministic) ''
+        tune2fs -T now ${optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
+      ''}
       # make systemd-boot find ESP without udev
       mkdir /dev/block
       ln -s /dev/vda1 /dev/block/254:1
@@ -396,6 +559,8 @@ let format' = format; in let
         mkdir -p /mnt/boot
         mkfs.vfat -n ESP /dev/vda1
         mount /dev/vda1 /mnt/boot
+
+        ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
       ''}
 
       # Install a configuration.nix
@@ -405,7 +570,13 @@ let format' = format; in let
       ''}
 
       ${lib.optionalString installBootLoader ''
-        # Set up core system link, GRUB, etc.
+        # In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb
+        # Use this option to create a symlink from vda to any arbitrary device you want.
+        ${optionalString (config.boot.loader.grub.device != "/dev/vda") ''
+            ln -s /dev/vda ${config.boot.loader.grub.device}
+        ''}
+
+        # Set up core system link, bootloader (sd-boot, GRUB, uboot, etc.), 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
@@ -432,8 +603,12 @@ let format' = format; in let
       # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
       # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
       # output, of course, but we can fix that when/if we start making images deterministic.
+      # In deterministic mode, this is fixed to 1970-01-01 (UNIX timestamp 0).
+      # This two-step approach is necessary otherwise `tune2fs` will want a fresher filesystem to perform
+      # some changes.
       ${optionalString (fsType == "ext4") ''
-        tune2fs -T now -c 0 -i 0 $rootDisk
+        tune2fs -T now ${optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
+        ${optionalString deterministic "tune2fs -f -T 19700101 $rootDisk"}
       ''}
     ''
   );
diff --git a/nixpkgs/nixos/lib/make-iso9660-image.nix b/nixpkgs/nixos/lib/make-iso9660-image.nix
index 549530965f6e..2f7dcf519a16 100644
--- a/nixpkgs/nixos/lib/make-iso9660-image.nix
+++ b/nixpkgs/nixos/lib/make-iso9660-image.nix
@@ -47,16 +47,16 @@ assert usbBootable -> isohybridMbrImage != "";
 
 stdenv.mkDerivation {
   name = isoName;
-  builder = ./make-iso9660-image.sh;
+  __structuredAttrs = true;
+
+  buildCommandPath = ./make-iso9660-image.sh;
   nativeBuildInputs = [ xorriso syslinux zstd libossp_uuid ];
 
   inherit isoName bootable bootImage compressImage volumeID efiBootImage efiBootable isohybridMbrImage usbBootable;
 
-  # !!! should use XML.
   sources = map (x: x.source) contents;
   targets = map (x: x.target) contents;
 
-  # !!! should use XML.
   objects = map (x: x.object) storeContents;
   symlinks = map (x: x.symlink) storeContents;
 
diff --git a/nixpkgs/nixos/lib/make-iso9660-image.sh b/nixpkgs/nixos/lib/make-iso9660-image.sh
index 9273b8d3db8d..34febe9cfe0e 100644
--- a/nixpkgs/nixos/lib/make-iso9660-image.sh
+++ b/nixpkgs/nixos/lib/make-iso9660-image.sh
@@ -1,12 +1,3 @@
-source $stdenv/setup
-
-sources_=($sources)
-targets_=($targets)
-
-objects=($objects)
-symlinks=($symlinks)
-
-
 # Remove the initial slash from a path, since genisofs likes it that way.
 stripSlash() {
     res="$1"
@@ -35,13 +26,13 @@ if test -n "$bootable"; then
     # The -boot-info-table option modifies the $bootImage file, so
     # find it in `contents' and make a copy of it (since the original
     # is read-only in the Nix store...).
-    for ((i = 0; i < ${#targets_[@]}; i++)); do
-        stripSlash "${targets_[$i]}"
+    for ((i = 0; i < ${#targets[@]}; i++)); do
+        stripSlash "${targets[$i]}"
         if test "$res" = "$bootImage"; then
-            echo "copying the boot image ${sources_[$i]}"
-            cp "${sources_[$i]}" boot.img
+            echo "copying the boot image ${sources[$i]}"
+            cp "${sources[$i]}" boot.img
             chmod u+w boot.img
-            sources_[$i]=boot.img
+            sources[$i]=boot.img
         fi
     done
 
@@ -66,9 +57,9 @@ touch pathlist
 
 
 # Add the individual files.
-for ((i = 0; i < ${#targets_[@]}; i++)); do
-    stripSlash "${targets_[$i]}"
-    addPath "$res" "${sources_[$i]}"
+for ((i = 0; i < ${#targets[@]}; i++)); do
+    stripSlash "${targets[$i]}"
+    addPath "$res" "${sources[$i]}"
 done
 
 
diff --git a/nixpkgs/nixos/lib/make-multi-disk-zfs-image.nix b/nixpkgs/nixos/lib/make-multi-disk-zfs-image.nix
index 0a894c8b9888..077bb8f22707 100644
--- a/nixpkgs/nixos/lib/make-multi-disk-zfs-image.nix
+++ b/nixpkgs/nixos/lib/make-multi-disk-zfs-image.nix
@@ -73,6 +73,9 @@
 , # Shell code executed after the VM has finished.
   postVM ? ""
 
+, # Guest memory size
+  memSize ? 1024
+
 , name ? "nixos-disk-image"
 
 , # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
@@ -143,13 +146,6 @@ let
       properties
   );
 
-  featuresToProperties = features:
-    lib.listToAttrs
-      (builtins.map (feature: {
-        name = "feature@${feature}";
-        value = "enabled";
-      }) features);
-
   createDatasets =
     let
       datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
@@ -249,6 +245,7 @@ let
       {
         QEMU_OPTS = "-drive file=$bootDiskImage,if=virtio,cache=unsafe,werror=report"
          + " -drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report";
+         inherit memSize;
         preVM = ''
           PATH=$PATH:${pkgs.qemu_kvm}/bin
           mkdir $out
@@ -264,8 +261,8 @@ let
           mv $bootDiskImage $out/${bootFilename}
           mv $rootDiskImage $out/${rootFilename}
         '' else ''
-          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
-          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
+          ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
+          ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
         ''}
           bootDiskImage=$out/${bootFilename}
           rootDiskImage=$out/${rootFilename}
diff --git a/nixpkgs/nixos/lib/make-options-doc/default.nix b/nixpkgs/nixos/lib/make-options-doc/default.nix
index 3f98e2cf87b8..a2385582a014 100644
--- a/nixpkgs/nixos/lib/make-options-doc/default.nix
+++ b/nixpkgs/nixos/lib/make-options-doc/default.nix
@@ -19,13 +19,15 @@
 { pkgs
 , lib
 , options
-, transformOptions ? lib.id  # function for additional tranformations of the options
+, transformOptions ? lib.id  # function for additional transformations of the options
 , documentType ? "appendix" # TODO deprecate "appendix" in favor of "none"
                             #      and/or rename function to moduleOptionDoc for clean slate
 
   # If you include more than one option list into a document, you need to
   # provide different ids.
 , variablelistId ? "configuration-variable-list"
+  # String to prefix to the option XML/HTML id attributes.
+, optionIdPrefix ? "opt-"
 , revision ? "" # Specify revision for the options
 # a set of options the docs we are generating will be merged into, as if by recursiveUpdate.
 # used to split the options doc build into a static part (nixos/modules) and a dynamic part
@@ -34,31 +36,21 @@
 # instead of printing warnings for eg options with missing descriptions (which may be lost
 # by nix build unless -L is given), emit errors instead and fail the build
 , warningsAreErrors ? true
+# allow docbook option docs if `true`. only markdown documentation is allowed when set to
+# `false`, and a different renderer may be used with different bugs and performance
+# characteristics but (hopefully) indistinguishable output.
+, allowDocBook ? true
+# whether lib.mdDoc is required for descriptions to be read as markdown.
+# !!! when this is eventually flipped to true, `lib.doRename` should also default to emitting Markdown
+, markdownByDefault ? false
 }:
 
 let
-  # 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;
-
   rawOpts = lib.optionAttrSetToDocList options;
   transformedOpts = map transformOptions rawOpts;
   filteredOpts = lib.filter (opt: opt.visible && !opt.internal) transformedOpts;
   optionsList = lib.flip map filteredOpts
    (opt: opt
-    // 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; }
    );
 
@@ -86,67 +78,65 @@ let
           title = args.title or null;
           name = args.name or (lib.concatStringsSep "." args.path);
         in ''
-          <listitem>
-            <para>
-              <link xlink:href="https://search.nixos.org/packages?show=${name}&amp;sort=relevance&amp;query=${name}">
-                <literal>${lib.optionalString (title != null) "${title} aka "}pkgs.${name}</literal>
-              </link>
-            </para>
-            ${lib.optionalString (args ? comment) "<para>${args.comment}</para>"}
-          </listitem>
+          - [${lib.optionalString (title != null) "${title} aka "}`pkgs.${name}`](
+              https://search.nixos.org/packages?show=${name}&sort=relevance&query=${name}
+            )${
+              lib.optionalString (args ? comment) "\n\n  ${args.comment}"
+            }
         '';
-    in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>";
+    in lib.concatMapStrings (p: describe (unpack p)) packages;
 
   optionsNix = builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList);
 
 in rec {
   inherit optionsNix;
 
-  optionsAsciiDoc = pkgs.runCommand "options.adoc" {} ''
-    ${pkgs.python3Minimal}/bin/python ${./generateAsciiDoc.py} \
-      < ${optionsJSON}/share/doc/nixos/options.json \
-      > $out
+  optionsAsciiDoc = pkgs.runCommand "options.adoc" {
+    nativeBuildInputs = [ pkgs.nixos-render-docs ];
+  } ''
+    nixos-render-docs -j $NIX_BUILD_CORES options asciidoc \
+      --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \
+      --revision ${lib.escapeShellArg revision} \
+      ${optionsJSON}/share/doc/nixos/options.json \
+      $out
   '';
 
-  optionsCommonMark = pkgs.runCommand "options.md" {} ''
-    ${pkgs.python3Minimal}/bin/python ${./generateCommonMark.py} \
-      < ${optionsJSON}/share/doc/nixos/options.json \
-      > $out
+  optionsCommonMark = pkgs.runCommand "options.md" {
+    nativeBuildInputs = [ pkgs.nixos-render-docs ];
+  } ''
+    nixos-render-docs -j $NIX_BUILD_CORES options commonmark \
+      --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \
+      --revision ${lib.escapeShellArg revision} \
+      ${optionsJSON}/share/doc/nixos/options.json \
+      $out
   '';
 
   optionsJSON = pkgs.runCommand "options.json"
     { meta.description = "List of NixOS options in JSON format";
-      buildInputs = [
+      nativeBuildInputs = [
         pkgs.brotli
-        (let
-          self = (pkgs.python3Minimal.override {
-            inherit self;
-            includeSiteCustomize = true;
-           });
-         in self.withPackages (p: [ p.mistune ]))
+        pkgs.python3Minimal
       ];
       options = builtins.toFile "options.json"
         (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix));
+      # merge with an empty set if baseOptionsJSON is null to run markdown
+      # processing on the input options
+      baseJSON =
+        if baseOptionsJSON == null
+        then builtins.toFile "base.json" "{}"
+        else baseOptionsJSON;
     }
     ''
       # Export list of options in different format.
       dst=$out/share/doc/nixos
       mkdir -p $dst
 
-      ${
-        if baseOptionsJSON == null
-          then ''
-            # `cp $options $dst/options.json`, but with temporary
-            # markdown processing
-            python ${./mergeJSON.py} $options <(echo '{}') > $dst/options.json
-          ''
-          else ''
-            python ${./mergeJSON.py} \
-              ${lib.optionalString warningsAreErrors "--warnings-are-errors"} \
-              ${baseOptionsJSON} $options \
-              > $dst/options.json
-          ''
-      }
+      TOUCH_IF_DB=$dst/.used-docbook \
+      python ${./mergeJSON.py} \
+        ${lib.optionalString warningsAreErrors "--warnings-are-errors"} \
+        ${if allowDocBook then "--warn-on-docbook" else "--error-on-docbook"} \
+        $baseJSON $options \
+        > $dst/options.json
 
       brotli -9 < $dst/options.json > $dst/options.json.br
 
@@ -155,21 +145,30 @@ in rec {
       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"
+  optionsUsedDocbook = pkgs.runCommand "options-used-docbook" {} ''
+    if [ -e ${optionsJSON}/share/doc/nixos/.used-docbook ]; then
+      echo 1
+    else
+      echo 0
+    fi >"$out"
   '';
 
-  optionsDocBook = pkgs.runCommand "options-docbook.xml" {} ''
-    optionsXML=${optionsXML}
-    if grep /nixpkgs/nixos/modules $optionsXML; then
+  optionsDocBook = pkgs.runCommand "options-docbook.xml" {
+    nativeBuildInputs = [
+      pkgs.nixos-render-docs
+    ];
+  } ''
+    nixos-render-docs -j $NIX_BUILD_CORES options docbook \
+      --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \
+      --revision ${lib.escapeShellArg revision} \
+      --document-type ${lib.escapeShellArg documentType} \
+      --varlist-id ${lib.escapeShellArg variablelistId} \
+      --id-prefix ${lib.escapeShellArg optionIdPrefix} \
+      ${lib.optionalString markdownByDefault "--markdown-by-default"} \
+      ${optionsJSON}/share/doc/nixos/options.json \
+      options.xml
+
+    if grep /nixpkgs/nixos/modules options.xml; then
       echo "The manual appears to depend on the location of Nixpkgs, which is bad"
       echo "since this prevents sharing via the NixOS channel.  This is typically"
       echo "caused by an option default that refers to a relative path (see above"
@@ -177,13 +176,7 @@ in rec {
       exit 1
     fi
 
-    ${pkgs.python3Minimal}/bin/python ${./sortXML.py} $optionsXML sorted.xml
-    ${pkgs.libxslt.bin}/bin/xsltproc \
-      --stringparam documentType '${documentType}' \
-      --stringparam revision '${revision}' \
-      --stringparam variablelistId '${variablelistId}' \
-      -o intermediate.xml ${./options-to-docbook.xsl} sorted.xml
     ${pkgs.libxslt.bin}/bin/xsltproc \
-      -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml
+      -o "$out" ${./postprocess-option-descriptions.xsl} options.xml
   '';
 }
diff --git a/nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py b/nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py
deleted file mode 100644
index 48eadd248c5a..000000000000
--- a/nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py
+++ /dev/null
@@ -1,37 +0,0 @@
-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
deleted file mode 100644
index 404e53b0df9c..000000000000
--- a/nixpkgs/nixos/lib/make-options-doc/generateCommonMark.py
+++ /dev/null
@@ -1,27 +0,0 @@
-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/mergeJSON.py b/nixpkgs/nixos/lib/make-options-doc/mergeJSON.py
index d7dc6ca30074..b4f72b8a3fdc 100644
--- a/nixpkgs/nixos/lib/make-options-doc/mergeJSON.py
+++ b/nixpkgs/nixos/lib/make-options-doc/mergeJSON.py
@@ -1,13 +1,9 @@
 import collections
 import json
+import os
 import sys
 from typing import Any, Dict, List
 
-# for MD conversion
-import mistune
-import re
-from xml.sax.saxutils import escape, quoteattr
-
 JSON = Dict[str, Any]
 
 class Key:
@@ -46,156 +42,21 @@ def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]:
         result[opt.name] = opt.value
     return result
 
-admonitions = {
-    '.warning': 'warning',
-    '.important': 'important',
-    '.note': 'note'
-}
-class Renderer(mistune.renderers.BaseRenderer):
-    def _get_method(self, name):
-        try:
-            return super(Renderer, self)._get_method(name)
-        except AttributeError:
-            def not_supported(*args, **kwargs):
-                raise NotImplementedError("md node not supported yet", name, args, **kwargs)
-            return not_supported
-
-    def text(self, text):
-        return escape(text)
-    def paragraph(self, text):
-        return text + "\n\n"
-    def newline(self):
-        return "<literallayout>\n</literallayout>"
-    def codespan(self, text):
-        return f"<literal>{escape(text)}</literal>"
-    def block_code(self, text, info=None):
-        info = f" language={quoteattr(info)}" if info is not None else ""
-        return f"<programlisting{info}>\n{escape(text)}</programlisting>"
-    def link(self, link, text=None, title=None):
-        tag = "link"
-        if link[0:1] == '#':
-            if text == "":
-                tag = "xref"
-            attr = "linkend"
-            link = quoteattr(link[1:])
-        else:
-            # try to faithfully reproduce links that were of the form <link href="..."/>
-            # in docbook format
-            if text == link:
-                text = ""
-            attr = "xlink:href"
-            link = quoteattr(link)
-        return f"<{tag} {attr}={link}>{text}</{tag}>"
-    def list(self, text, ordered, level, start=None):
-        if ordered:
-            raise NotImplementedError("ordered lists not supported yet")
-        return f"<itemizedlist>\n{text}\n</itemizedlist>"
-    def list_item(self, text, level):
-        return f"<listitem><para>{text}</para></listitem>\n"
-    def block_text(self, text):
-        return text
-    def emphasis(self, text):
-        return f"<emphasis>{text}</emphasis>"
-    def strong(self, text):
-        return f"<emphasis role=\"strong\">{text}</emphasis>"
-    def admonition(self, text, kind):
-        if kind not in admonitions:
-            raise NotImplementedError(f"admonition {kind} not supported yet")
-        tag = admonitions[kind]
-        # we don't keep whitespace here because usually we'll contain only
-        # a single paragraph and the original docbook string is no longer
-        # available to restore the trailer.
-        return f"<{tag}><para>{text.rstrip()}</para></{tag}>"
-    def block_quote(self, text):
-        return f"<blockquote><para>{text}</para></blockquote>"
-    def command(self, text):
-        return f"<command>{escape(text)}</command>"
-    def option(self, text):
-        return f"<option>{escape(text)}</option>"
-    def file(self, text):
-        return f"<filename>{escape(text)}</filename>"
-    def manpage(self, page, section):
-        title = f"<refentrytitle>{escape(page)}</refentrytitle>"
-        vol = f"<manvolnum>{escape(section)}</manvolnum>"
-        return f"<citerefentry>{title}{vol}</citerefentry>"
-
-    def finalize(self, data):
-        return "".join(data)
-
-def p_command(md):
-    COMMAND_PATTERN = r'\{command\}`(.*?)`'
-    def parse(self, m, state):
-        return ('command', m.group(1))
-    md.inline.register_rule('command', COMMAND_PATTERN, parse)
-    md.inline.rules.append('command')
-
-def p_file(md):
-    FILE_PATTERN = r'\{file\}`(.*?)`'
-    def parse(self, m, state):
-        return ('file', m.group(1))
-    md.inline.register_rule('file', FILE_PATTERN, parse)
-    md.inline.rules.append('file')
-
-def p_option(md):
-    OPTION_PATTERN = r'\{option\}`(.*?)`'
-    def parse(self, m, state):
-        return ('option', m.group(1))
-    md.inline.register_rule('option', OPTION_PATTERN, parse)
-    md.inline.rules.append('option')
-
-def p_manpage(md):
-    MANPAGE_PATTERN = r'\{manpage\}`(.*?)\((.+?)\)`'
-    def parse(self, m, state):
-        return ('manpage', m.group(1), m.group(2))
-    md.inline.register_rule('manpage', MANPAGE_PATTERN, parse)
-    md.inline.rules.append('manpage')
-
-def p_admonition(md):
-    ADMONITION_PATTERN = re.compile(r'^::: \{([^\n]*?)\}\n(.*?)^:::\n', flags=re.MULTILINE|re.DOTALL)
-    def parse(self, m, state):
-        return {
-            'type': 'admonition',
-            'children': self.parse(m.group(2), state),
-            'params': [ m.group(1) ],
-        }
-    md.block.register_rule('admonition', ADMONITION_PATTERN, parse)
-    md.block.rules.append('admonition')
-
-md = mistune.create_markdown(renderer=Renderer(), plugins=[
-    p_command, p_file, p_option, p_manpage, p_admonition
-])
-
-# converts in-place!
-def convertMD(options: Dict[str, Any]) -> str:
-    def convertString(path: str, text: str) -> str:
-        try:
-            rendered = md(text)
-            # keep trailing spaces so we can diff the generated XML to check for conversion bugs.
-            return rendered.rstrip() + text[len(text.rstrip()):]
-        except:
-            print(f"error in {path}")
-            raise
-
-    def optionIs(option: Dict[str, Any], key: str, typ: str) -> bool:
-        if key not in option: return False
-        if type(option[key]) != dict: return False
-        if '_type' not in option[key]: return False
-        return option[key]['_type'] == typ
-
-    for (name, option) in options.items():
-        if optionIs(option, 'description', 'mdDoc'):
-            option['description'] = convertString(name, option['description']['text'])
-        if optionIs(option, 'example', 'literalMD'):
-            docbook = convertString(name, option['example']['text'])
-            option['example'] = { '_type': 'literalDocBook', 'text': docbook }
-        if optionIs(option, 'default', 'literalMD'):
-            docbook = convertString(name, option['default']['text'])
-            option['default'] = { '_type': 'literalDocBook', 'text': docbook }
-
-    return options
-
-warningsAreErrors = sys.argv[1] == "--warnings-are-errors"
-optOffset = 1 if warningsAreErrors else 0
+warningsAreErrors = False
+warnOnDocbook = False
+errorOnDocbook = False
+optOffset = 0
+for arg in sys.argv[1:]:
+    if arg == "--warnings-are-errors":
+        optOffset += 1
+        warningsAreErrors = True
+    if arg == "--warn-on-docbook":
+        optOffset += 1
+        warnOnDocbook = True
+    elif arg == "--error-on-docbook":
+        optOffset += 1
+        errorOnDocbook = True
+
 options = pivot(json.load(open(sys.argv[1 + optOffset], 'r')))
 overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r')))
 
@@ -223,9 +84,38 @@ for (k, v) in overrides.items():
 
 severity = "error" if warningsAreErrors else "warning"
 
+def is_docbook(o, key):
+    val = o.get(key, {})
+    if not isinstance(val, dict):
+        return False
+    return val.get('_type', '') == 'literalDocBook'
+
 # check that every option has a description
 hasWarnings = False
+hasErrors = False
+hasDocBook = False
 for (k, v) in options.items():
+    if warnOnDocbook or errorOnDocbook:
+        kind = "error" if errorOnDocbook else "warning"
+        if isinstance(v.value.get('description', {}), str):
+            hasErrors |= errorOnDocbook
+            hasDocBook = True
+            print(
+                f"\x1b[1;31m{kind}: option {v.name} description uses DocBook\x1b[0m",
+                file=sys.stderr)
+        elif is_docbook(v.value, 'defaultText'):
+            hasErrors |= errorOnDocbook
+            hasDocBook = True
+            print(
+                f"\x1b[1;31m{kind}: option {v.name} default uses DocBook\x1b[0m",
+                file=sys.stderr)
+        elif is_docbook(v.value, 'example'):
+            hasErrors |= errorOnDocbook
+            hasDocBook = True
+            print(
+                f"\x1b[1;31m{kind}: option {v.name} example uses DocBook\x1b[0m",
+                file=sys.stderr)
+
     if v.value.get('description', None) is None:
         hasWarnings = True
         print(f"\x1b[1;31m{severity}: option {v.name} has no description\x1b[0m", file=sys.stderr)
@@ -236,6 +126,32 @@ for (k, v) in options.items():
             f"\x1b[1;31m{severity}: option {v.name} has no type. Please specify a valid type, see " +
             "https://nixos.org/manual/nixos/stable/index.html#sec-option-types\x1b[0m", file=sys.stderr)
 
+if hasDocBook:
+    (why, what) = (
+        ("disallowed for in-tree modules", "contribution") if errorOnDocbook
+        else ("deprecated for option documentation", "module")
+    )
+    print("Explanation: The documentation contains descriptions, examples, or defaults written in DocBook. " +
+        "NixOS is in the process of migrating from DocBook to Markdown, and " +
+        f"DocBook is {why}. To change your {what} to "+
+        "use Markdown, apply mdDoc and literalMD and use the *MD variants of option creation " +
+        "functions where they are available. For example:\n" +
+        "\n" +
+        "  example.foo = mkOption {\n" +
+        "    description = lib.mdDoc ''your description'';\n" +
+        "    defaultText = lib.literalMD ''your description of default'';\n" +
+        "  };\n" +
+        "\n" +
+        "  example.enable = mkEnableOption (lib.mdDoc ''your thing'');\n" +
+        "  example.package = mkPackageOptionMD pkgs \"your-package\" {};\n" +
+        "  imports = [ (mkAliasOptionModuleMD [ \"example\" \"args\" ] [ \"example\" \"settings\" ]) ];",
+        file = sys.stderr)
+    with open(os.getenv('TOUCH_IF_DB'), 'x'):
+        # just make sure it exists
+        pass
+
+if hasErrors:
+    sys.exit(1)
 if hasWarnings and warningsAreErrors:
     print(
         "\x1b[1;31m" +
@@ -245,4 +161,4 @@ if hasWarnings and warningsAreErrors:
         file=sys.stderr)
     sys.exit(1)
 
-json.dump(convertMD(unpivot(options)), fp=sys.stdout)
+json.dump(unpivot(options), fp=sys.stdout)
diff --git a/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
deleted file mode 100644
index 978d5e2468a8..000000000000
--- a/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
+++ /dev/null
@@ -1,283 +0,0 @@
-<?xml version="1.0"?>
-
-<xsl:stylesheet version="1.0"
-                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-                xmlns:str="http://exslt.org/strings"
-                xmlns:xlink="http://www.w3.org/1999/xlink"
-                xmlns:nixos="tag:nixos.org"
-                xmlns="http://docbook.org/ns/docbook"
-                extension-element-prefixes="str"
-                >
-
-  <xsl:output method='xml' encoding="UTF-8" />
-
-  <xsl:param name="revision" />
-  <xsl:param name="documentType" />
-  <xsl:param name="program" />
-  <xsl:param name="variablelistId" />
-
-
-  <xsl:template match="/expr/list">
-    <xsl:choose>
-      <xsl:when test="$documentType = 'appendix'">
-        <appendix xml:id="appendix-configuration-options">
-          <title>Configuration Options</title>
-          <xsl:call-template name="variable-list"/>
-        </appendix>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:call-template name="variable-list"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
-
-  <xsl:template name="variable-list">
-      <variablelist>
-      <xsl:attribute name="id" namespace="http://www.w3.org/XML/1998/namespace"><xsl:value-of select="$variablelistId"/></xsl:attribute>
-        <xsl:for-each select="attrs">
-          <xsl:variable name="id" select="
-            concat('opt-',
-              translate(
-                attr[@name = 'name']/string/@value,
-                '*&lt; >[]:',
-                '_______'
-            ))" />
-          <varlistentry>
-            <term xlink:href="#{$id}">
-              <xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute>
-              <option>
-                <xsl:value-of select="attr[@name = 'name']/string/@value" />
-              </option>
-            </term>
-
-            <listitem>
-
-              <nixos:option-description>
-                <para>
-                  <xsl:value-of disable-output-escaping="yes"
-                                select="attr[@name = 'description']/string/@value" />
-                </para>
-              </nixos:option-description>
-
-              <xsl:if test="attr[@name = 'type']">
-                <para>
-                  <emphasis>Type:</emphasis>
-                  <xsl:text> </xsl:text>
-                  <xsl:value-of select="attr[@name = 'type']/string/@value"/>
-                  <xsl:if test="attr[@name = 'readOnly']/bool/@value = 'true'">
-                    <xsl:text> </xsl:text>
-                    <emphasis>(read only)</emphasis>
-                  </xsl:if>
-                </para>
-              </xsl:if>
-
-              <xsl:if test="attr[@name = 'default']">
-                <para>
-                  <emphasis>Default:</emphasis>
-                  <xsl:text> </xsl:text>
-                  <xsl:apply-templates select="attr[@name = 'default']/*" mode="top" />
-                </para>
-              </xsl:if>
-
-              <xsl:if test="attr[@name = 'example']">
-                <para>
-                  <emphasis>Example:</emphasis>
-                  <xsl:text> </xsl:text>
-                  <xsl:apply-templates select="attr[@name = 'example']/*" mode="top" />
-                </para>
-              </xsl:if>
-
-              <xsl:if test="attr[@name = 'relatedPackages']">
-                <para>
-                  <emphasis>Related packages:</emphasis>
-                  <xsl:text> </xsl:text>
-                  <xsl:value-of disable-output-escaping="yes"
-                                select="attr[@name = 'relatedPackages']/string/@value" />
-                </para>
-              </xsl:if>
-
-              <xsl:if test="count(attr[@name = 'declarations']/list/*) != 0">
-                <para>
-                  <emphasis>Declared by:</emphasis>
-                </para>
-                <xsl:apply-templates select="attr[@name = 'declarations']" />
-              </xsl:if>
-
-              <xsl:if test="count(attr[@name = 'definitions']/list/*) != 0">
-                <para>
-                  <emphasis>Defined by:</emphasis>
-                </para>
-                <xsl:apply-templates select="attr[@name = 'definitions']" />
-              </xsl:if>
-
-            </listitem>
-
-          </varlistentry>
-
-        </xsl:for-each>
-
-      </variablelist>
-  </xsl:template>
-
-
-  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExpression']]]" mode = "top">
-    <xsl:choose>
-      <xsl:when test="contains(attr[@name = 'text']/string/@value, '&#010;')">
-        <programlisting><xsl:value-of select="attr[@name = 'text']/string/@value" /></programlisting>
-      </xsl:when>
-      <xsl:otherwise>
-        <literal><xsl:value-of select="attr[@name = 'text']/string/@value" /></literal>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
-
-
-  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalDocBook']]]" mode = "top">
-    <xsl:value-of disable-output-escaping="yes" select="attr[@name = 'text']/string/@value" />
-  </xsl:template>
-
-
-  <xsl:template match="string[contains(@value, '&#010;')]" mode="top">
-    <programlisting>
-      <xsl:text>''&#010;</xsl:text>
-      <xsl:value-of select='str:replace(str:replace(@value, "&apos;&apos;", "&apos;&apos;&apos;"), "${", "&apos;&apos;${")' />
-      <xsl:text>''</xsl:text>
-    </programlisting>
-  </xsl:template>
-
-
-  <xsl:template match="*" mode="top">
-    <literal><xsl:apply-templates select="." /></literal>
-  </xsl:template>
-
-
-  <xsl:template match="null">
-    <xsl:text>null</xsl:text>
-  </xsl:template>
-
-
-  <xsl:template match="string">
-    <xsl:choose>
-      <xsl:when test="(contains(@value, '&quot;') or contains(@value, '\')) and not(contains(@value, '&#010;'))">
-        <xsl:text>''</xsl:text><xsl:value-of select='str:replace(str:replace(@value, "&apos;&apos;", "&apos;&apos;&apos;"), "${", "&apos;&apos;${")' /><xsl:text>''</xsl:text>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:text>"</xsl:text><xsl:value-of select="str:replace(str:replace(str:replace(str:replace(@value, '\', '\\'), '&quot;', '\&quot;'), '&#010;', '\n'), '${', '\${')" /><xsl:text>"</xsl:text>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
-
-
-  <xsl:template match="int">
-    <xsl:value-of select="@value" />
-  </xsl:template>
-
-
-  <xsl:template match="bool[@value = 'true']">
-    <xsl:text>true</xsl:text>
-  </xsl:template>
-
-
-  <xsl:template match="bool[@value = 'false']">
-    <xsl:text>false</xsl:text>
-  </xsl:template>
-
-
-  <xsl:template match="list">
-    [
-    <xsl:for-each select="*">
-      <xsl:apply-templates select="." />
-      <xsl:text> </xsl:text>
-    </xsl:for-each>
-    ]
-  </xsl:template>
-
-
-  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExpression']]]">
-    <xsl:value-of select="attr[@name = 'text']/string/@value" />
-  </xsl:template>
-
-
-  <xsl:template match="attrs">
-    {
-    <xsl:for-each select="attr">
-      <xsl:value-of select="@name" />
-      <xsl:text> = </xsl:text>
-      <xsl:apply-templates select="*" /><xsl:text>; </xsl:text>
-    </xsl:for-each>
-    }
-  </xsl:template>
-
-
-  <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>
-
-  <xsl:template match="attr[@name = 'declarations' or @name = 'definitions']">
-    <simplelist>
-      <!--
-        Example:
-          opt.declarations = [ { name = "foo/bar.nix"; url = "https://github.com/....."; } ];
-      -->
-      <xsl:for-each select="list/attrs[attr[@name = 'name']]">
-        <member><filename>
-          <xsl:if test="attr[@name = 'url']">
-            <xsl:attribute name="xlink:href"><xsl:value-of select="attr[@name = 'url']/string/@value"/></xsl:attribute>
-          </xsl:if>
-          <xsl:value-of select="attr[@name = 'name']/string/@value"/>
-        </filename></member>
-      </xsl:for-each>
-
-      <!--
-        When the declarations/definitions are raw strings,
-        fall back to hardcoded location logic, specific to Nixpkgs.
-      -->
-      <xsl:for-each select="list/string">
-        <member><filename>
-          <!-- Hyperlink the filename either to the NixOS Subversion
-          repository (if it’s a module and we have a revision number),
-          or to the local filesystem. -->
-          <xsl:choose>
-            <xsl:when test="not(starts-with(@value, '/'))">
-              <xsl:choose>
-                <xsl:when test="$revision = 'local'">
-                  <xsl:attribute name="xlink:href">https://github.com/NixOS/nixpkgs/blob/master/<xsl:value-of select="@value"/></xsl:attribute>
-                </xsl:when>
-                <xsl:otherwise>
-                  <xsl:attribute name="xlink:href">https://github.com/NixOS/nixpkgs/blob/<xsl:value-of select="$revision"/>/<xsl:value-of select="@value"/></xsl:attribute>
-                </xsl:otherwise>
-              </xsl:choose>
-            </xsl:when>
-            <xsl:when test="$revision != 'local' and $program = 'nixops' and contains(@value, '/nix/')">
-              <xsl:attribute name="xlink:href">https://github.com/NixOS/nixops/blob/<xsl:value-of select="$revision"/>/nix/<xsl:value-of select="substring-after(@value, '/nix/')"/></xsl:attribute>
-            </xsl:when>
-            <xsl:otherwise>
-              <xsl:attribute name="xlink:href">file://<xsl:value-of select="@value"/></xsl:attribute>
-            </xsl:otherwise>
-          </xsl:choose>
-          <!-- Print the filename and make it user-friendly by replacing the
-          /nix/store/<hash> prefix by the default location of nixos
-          sources. -->
-          <xsl:choose>
-            <xsl:when test="not(starts-with(@value, '/'))">
-              &lt;nixpkgs/<xsl:value-of select="@value"/>&gt;
-            </xsl:when>
-            <xsl:when test="contains(@value, 'nixops') and contains(@value, '/nix/')">
-              &lt;nixops/<xsl:value-of select="substring-after(@value, '/nix/')"/>&gt;
-            </xsl:when>
-            <xsl:otherwise>
-              <xsl:value-of select="@value" />
-            </xsl:otherwise>
-          </xsl:choose>
-        </filename></member>
-      </xsl:for-each>
-    </simplelist>
-  </xsl:template>
-
-
-  <xsl:template match="function">
-    <xsl:text>λ</xsl:text>
-  </xsl:template>
-
-
-</xsl:stylesheet>
diff --git a/nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix b/nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix
deleted file mode 100644
index ba50c5f898b5..000000000000
--- a/nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix
+++ /dev/null
@@ -1,6 +0,0 @@
-{ 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
deleted file mode 100644
index e63ff3538b3f..000000000000
--- a/nixpkgs/nixos/lib/make-options-doc/sortXML.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import xml.etree.ElementTree as ET
-import sys
-
-tree = ET.parse(sys.argv[1])
-# the xml tree is of the form
-# <expr><list> {all options, each an attrs} </list></expr>
-options = list(tree.getroot().find('list'))
-
-def sortKey(opt):
-    def order(s):
-        if s.startswith("enable"):
-            return 0
-        if s.startswith("package"):
-            return 1
-        return 2
-
-    return [
-        (order(p.attrib['value']), p.attrib['value'])
-        for p in opt.findall('attr[@name="loc"]/list/string')
-    ]
-
-options.sort(key=sortKey)
-
-doc = ET.Element("expr")
-newOptions = ET.SubElement(doc, "list")
-newOptions.extend(options)
-ET.ElementTree(doc).write(sys.argv[2], encoding='utf-8')
diff --git a/nixpkgs/nixos/lib/make-single-disk-zfs-image.nix b/nixpkgs/nixos/lib/make-single-disk-zfs-image.nix
index 98150584fb3e..a3564f9a8b68 100644
--- a/nixpkgs/nixos/lib/make-single-disk-zfs-image.nix
+++ b/nixpkgs/nixos/lib/make-single-disk-zfs-image.nix
@@ -131,15 +131,6 @@ let
       properties
   );
 
-  featuresToProperties = features:
-    lib.listToAttrs
-      (builtins.map
-        (feature: {
-          name = "feature@${feature}";
-          value = "enabled";
-        })
-        features);
-
   createDatasets =
     let
       datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
@@ -253,7 +244,7 @@ let
             ${if formatOpt == "raw" then ''
             mv $rootDiskImage $out/${rootFilename}
           '' else ''
-            ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
+            ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
           ''}
             rootDiskImage=$out/${rootFilename}
             set -x
diff --git a/nixpkgs/nixos/lib/make-squashfs.nix b/nixpkgs/nixos/lib/make-squashfs.nix
index 170d315fb751..d1260a48f229 100644
--- a/nixpkgs/nixos/lib/make-squashfs.nix
+++ b/nixpkgs/nixos/lib/make-squashfs.nix
@@ -10,6 +10,7 @@
 
 stdenv.mkDerivation {
   name = "squashfs.img";
+  __structuredAttrs = true;
 
   nativeBuildInputs = [ squashfsTools ];
 
diff --git a/nixpkgs/nixos/lib/make-system-tarball.nix b/nixpkgs/nixos/lib/make-system-tarball.nix
index dab168f4a481..325792f97e8f 100644
--- a/nixpkgs/nixos/lib/make-system-tarball.nix
+++ b/nixpkgs/nixos/lib/make-system-tarball.nix
@@ -22,7 +22,7 @@
   # Extra tar arguments
 , extraArgs ? ""
   # Command used for compression
-, compressCommand ? "pixz"
+, compressCommand ? "pixz -t"
   # Extension for the compressed tarball
 , compressionExtension ? ".xz"
   # extra inputs, like the compressor to use
diff --git a/nixpkgs/nixos/lib/qemu-common.nix b/nixpkgs/nixos/lib/qemu-common.nix
index fc3dcb24ab9c..a8ed27dd6091 100644
--- a/nixpkgs/nixos/lib/qemu-common.nix
+++ b/nixpkgs/nixos/lib/qemu-common.nix
@@ -4,29 +4,61 @@
 let
   zeroPad = n:
     lib.optionalString (n < 16) "0" +
-      (if n > 255
-       then throw "Can't have more than 255 nets or nodes!"
-       else lib.toHexString n);
+    (if n > 255
+    then throw "Can't have more than 255 nets or nodes!"
+    else lib.toHexString n);
 in
 
 rec {
   qemuNicMac = net: machine: "52:54:00:12:${zeroPad net}:${zeroPad machine}";
 
   qemuNICFlags = nic: net: machine:
-    [ "-device virtio-net-pci,netdev=vlan${toString nic},mac=${qemuNicMac net machine}"
+    [
+      "-device virtio-net-pci,netdev=vlan${toString nic},mac=${qemuNicMac net machine}"
       ''-netdev vde,id=vlan${toString nic},sock="$QEMU_VDE_SOCKET_${toString net}"''
     ];
 
-  qemuSerialDevice = if pkgs.stdenv.hostPlatform.isx86 || pkgs.stdenv.hostPlatform.isRiscV then "ttyS0"
-        else if (with pkgs.stdenv.hostPlatform; isAarch || isPower) then "ttyAMA0"
-        else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'";
+  qemuSerialDevice =
+    if with pkgs.stdenv.hostPlatform; isx86 || isMips64 || isRiscV then "ttyS0"
+    else if (with pkgs.stdenv.hostPlatform; isAarch || 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";
-    armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -machine virt,accel=kvm:tcg -cpu max";
-    aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -machine virt,gic-version=max,accel=kvm:tcg -cpu max";
-    powerpc64le-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
-    powerpc64-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
-    x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu max";
-  }.${pkgs.stdenv.hostPlatform.system} or "${qemuPkg}/bin/qemu-kvm";
+  qemuBinary = qemuPkg:
+    let
+      hostStdenv = qemuPkg.stdenv;
+      hostSystem = hostStdenv.system;
+      guestSystem = pkgs.stdenv.hostPlatform.system;
+
+      linuxHostGuestMatrix = {
+        x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu max";
+        armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -machine virt,accel=kvm:tcg -cpu max";
+        aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -machine virt,gic-version=max,accel=kvm:tcg -cpu max";
+        powerpc64le-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
+        powerpc64-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
+        x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu max";
+      };
+      otherHostGuestMatrix = {
+        aarch64-darwin = {
+          aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -machine virt,gic-version=2,accel=hvf:tcg -cpu max";
+        };
+        x86_64-darwin = {
+          x86_64-linux = "${qemuPkg}/bin/qemu-system-x86_64 -machine type=q35,accel=hvf:tcg -cpu max";
+        };
+      };
+
+      throwUnsupportedHostSystem =
+        let
+          supportedSystems = [ "linux" ] ++ (lib.attrNames otherHostGuestMatrix);
+        in
+        throw "Unsupported host system ${hostSystem}, supported: ${lib.concatStringsSep ", " supportedSystems}";
+      throwUnsupportedGuestSystem = guestMap:
+        throw "Unsupported guest system ${guestSystem} for host ${hostSystem}, supported: ${lib.concatStringsSep ", " (lib.attrNames guestMap)}";
+    in
+    if hostStdenv.isLinux then
+      linuxHostGuestMatrix.${guestSystem} or "${qemuPkg}/bin/qemu-kvm"
+    else
+      let
+        guestMap = (otherHostGuestMatrix.${hostSystem} or throwUnsupportedHostSystem);
+      in
+      (guestMap.${guestSystem} or (throwUnsupportedGuestSystem guestMap));
 }
diff --git a/nixpkgs/nixos/lib/systemd-lib.nix b/nixpkgs/nixos/lib/systemd-lib.nix
index 779dadece582..eb2bcb9d3b98 100644
--- a/nixpkgs/nixos/lib/systemd-lib.nix
+++ b/nixpkgs/nixos/lib/systemd-lib.nix
@@ -8,9 +8,9 @@ let
   systemd = cfg.package;
 in rec {
 
-  shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s);
+  shellEscape = s: (replaceStrings [ "\\" ] [ "\\\\" ] s);
 
-  mkPathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"] ["-" "-" "-" "" ""];
+  mkPathSafeName = lib.replaceStrings ["@" ":" "\\" "[" "]"] ["-" "-" "-" "" ""];
 
   # a type for options that take a unit name
   unitNameType = types.strMatching "[a-zA-Z0-9@%:_.\\-]+[.](service|socket|device|mount|automount|swap|target|path|timer|scope|slice)";
@@ -24,7 +24,7 @@ in rec {
         }
         ''
           name=${shellEscape name}
-          mkdir -p "$out/$(dirname "$name")"
+          mkdir -p "$out/$(dirname -- "$name")"
           echo -n "$text" > "$out/$name"
         ''
     else
@@ -187,11 +187,14 @@ in rec {
         done
       done
 
-      # Symlink all units defined by systemd.units. If these are also
-      # provided by systemd or systemd.packages, then add them as
+      # Symlink units defined by systemd.units where override strategy
+      # shall be automatically detected. If these are also provided by
+      # systemd or systemd.packages, then add them as
       # <unit-name>.d/overrides.conf, which makes them extend the
       # upstream unit.
-      for i in ${toString (mapAttrsToList (n: v: v.unit) units)}; do
+      for i in ${toString (mapAttrsToList
+          (n: v: v.unit)
+          (lib.filterAttrs (n: v: (attrByPath [ "overrideStrategy" ] "asDropinIfExists" v) == "asDropinIfExists") units))}; do
         fn=$(basename $i/*)
         if [ -e $out/$fn ]; then
           if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
@@ -210,11 +213,21 @@ in rec {
         fi
       done
 
+      # Symlink units defined by systemd.units which shall be
+      # treated as drop-in file.
+      for i in ${toString (mapAttrsToList
+          (n: v: v.unit)
+          (lib.filterAttrs (n: v: v ? overrideStrategy && v.overrideStrategy == "asDropin") units))}; do
+        fn=$(basename $i/*)
+        mkdir -p $out/$fn.d
+        ln -s $i/$fn $out/$fn.d/overrides.conf
+      done
+
       # Create service aliases from aliases option.
       ${concatStrings (mapAttrsToList (name: unit:
           concatMapStrings (name2: ''
             ln -sfn '${name}' $out/'${name2}'
-          '') unit.aliases) units)}
+          '') (unit.aliases or [])) units)}
 
       # Create .wants and .requires symlinks from the wantedBy and
       # requiredBy options.
@@ -222,13 +235,13 @@ in rec {
           concatMapStrings (name2: ''
             mkdir -p $out/'${name2}.wants'
             ln -sfn '../${name}' $out/'${name2}.wants'/
-          '') unit.wantedBy) units)}
+          '') (unit.wantedBy or [])) units)}
 
       ${concatStrings (mapAttrsToList (name: unit:
           concatMapStrings (name2: ''
             mkdir -p $out/'${name2}.requires'
             ln -sfn '../${name}' $out/'${name2}.requires'/
-          '') unit.requiredBy) units)}
+          '') (unit.requiredBy or [])) units)}
 
       ${optionalString (type == "system") ''
         # Stupid misc. symlinks.
@@ -245,7 +258,7 @@ in rec {
 
   makeJobScript = name: text:
     let
-      scriptName = replaceChars [ "\\" "@" ] [ "-" "_" ] (shellEscape name);
+      scriptName = replaceStrings [ "\\" "@" ] [ "-" "_" ] (shellEscape name);
       out = (pkgs.writeShellScriptBin scriptName ''
         set -e
         ${text}
@@ -276,9 +289,9 @@ in rec {
         // optionalAttrs (config.requisite != [])
           { Requisite = toString config.requisite; }
         // optionalAttrs (config ? restartTriggers && config.restartTriggers != [])
-          { X-Restart-Triggers = toString config.restartTriggers; }
+          { X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers" (toString config.restartTriggers)}"; }
         // optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [])
-          { X-Reload-Triggers = toString config.reloadTriggers; }
+          { X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers" (toString config.reloadTriggers)}"; }
         // optionalAttrs (config.description != "") {
           Description = config.description; }
         // optionalAttrs (config.documentation != []) {
@@ -340,7 +353,7 @@ in rec {
     '';
 
   targetToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text =
         ''
           [Unit]
@@ -349,7 +362,7 @@ in rec {
     };
 
   serviceToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Service]
@@ -371,7 +384,7 @@ in rec {
     };
 
   socketToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Socket]
@@ -382,7 +395,7 @@ in rec {
     };
 
   timerToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Timer]
@@ -391,7 +404,7 @@ in rec {
     };
 
   pathToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Path]
@@ -400,7 +413,7 @@ in rec {
     };
 
   mountToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Mount]
@@ -409,7 +422,7 @@ in rec {
     };
 
   automountToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Automount]
@@ -418,7 +431,7 @@ in rec {
     };
 
   sliceToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text = commonUnitText def +
         ''
           [Slice]
diff --git a/nixpkgs/nixos/lib/systemd-types.nix b/nixpkgs/nixos/lib/systemd-types.nix
index 961b6d7f9851..a109f248b170 100644
--- a/nixpkgs/nixos/lib/systemd-types.nix
+++ b/nixpkgs/nixos/lib/systemd-types.nix
@@ -37,11 +37,11 @@ rec {
 
   initrdContents = types.attrsOf (types.submodule ({ config, options, name, ... }: {
     options = {
-      enable = mkEnableOption "copying of this file and symlinking it" // { default = true; };
+      enable = mkEnableOption (lib.mdDoc "copying of this file and symlinking it") // { default = true; };
 
       target = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path of the symlink.
         '';
         default = name;
@@ -50,12 +50,12 @@ rec {
       text = mkOption {
         default = null;
         type = types.nullOr types.lines;
-        description = "Text of the file.";
+        description = lib.mdDoc "Text of the file.";
       };
 
       source = mkOption {
         type = types.path;
-        description = "Path of the source file.";
+        description = lib.mdDoc "Path of the source file.";
       };
     };
 
diff --git a/nixpkgs/nixos/lib/systemd-unit-options.nix b/nixpkgs/nixos/lib/systemd-unit-options.nix
index 84388b555038..9c69bda471bb 100644
--- a/nixpkgs/nixos/lib/systemd-unit-options.nix
+++ b/nixpkgs/nixos/lib/systemd-unit-options.nix
@@ -37,49 +37,65 @@ in rec {
     enable = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         If set to false, this unit will be a symlink to
         /dev/null. This is primarily useful to prevent specific
         template instances
-        (e.g. <literal>serial-getty@ttyS0</literal>) from being
-        started. Note that <literal>enable=true</literal> does not
+        (e.g. `serial-getty@ttyS0`) from being
+        started. Note that `enable=true` does not
         make a unit start by default at boot; if you want that, see
-        <literal>wantedBy</literal>.
+        `wantedBy`.
+      '';
+    };
+
+    overrideStrategy = mkOption {
+      default = "asDropinIfExists";
+      type = types.enum [ "asDropinIfExists" "asDropin" ];
+      description = lib.mdDoc ''
+        Defines how unit configuration is provided for systemd:
+
+        `asDropinIfExists` creates a unit file when no unit file is provided by the package
+        otherwise a drop-in file name `overrides.conf`.
+
+        `asDropin` creates a drop-in file named `overrides.conf`.
+        Mainly needed to define instances for systemd template units (e.g. `systemd-nspawn@mycontainer.service`).
+
+        See also {manpage}`systemd.unit(5)`.
       '';
     };
 
     requiredBy = mkOption {
       default = [];
       type = types.listOf unitNameType;
-      description = ''
-        Units that require (i.e. depend on and need to go down with)
-        this unit. The discussion under <literal>wantedBy</literal>
-        applies here as well: inverse <literal>.requires</literal>
-        symlinks are established.
+      description = lib.mdDoc ''
+        Units that require (i.e. depend on and need to go down with) this unit.
+        As discussed in the `wantedBy` option description this also creates
+        `.requires` symlinks automatically.
       '';
     };
 
     wantedBy = mkOption {
       default = [];
       type = types.listOf unitNameType;
-      description = ''
-        Units that want (i.e. depend on) this unit. The standard way
-        to make a unit start by default at boot is to set this option
-        to <literal>[ "multi-user.target" ]</literal>. That's despite
-        the fact that the systemd.unit(5) manpage says this option
-        goes in the <literal>[Install]</literal> section that controls
-        the behaviour of <literal>systemctl enable</literal>. Since
-        such a process is stateful and thus contrary to the design of
-        NixOS, setting this option instead causes the equivalent
-        inverse <literal>.wants</literal> symlink to be present,
-        establishing the same desired relationship in a stateless way.
+      description = lib.mdDoc ''
+        Units that want (i.e. depend on) this unit. The default method for
+        starting a unit by default at boot time is to set this option to
+        `["multi-user.target"]` for system services. Likewise for user units
+        (`systemd.user.<name>.*`) set it to `["default.target"]` to make a unit
+        start by default when the user `<name>` logs on.
+
+        This option creates a `.wants` symlink in the given target that exists
+        statelessly without the need for running `systemctl enable`.
+        The `[Install]` section described in {manpage}`systemd.unit(5)` however is
+        not supported because it is a stateful process that does not fit well
+        into the NixOS design.
       '';
     };
 
     aliases = mkOption {
       default = [];
       type = types.listOf unitNameType;
-      description = "Aliases of that unit.";
+      description = lib.mdDoc "Aliases of that unit.";
     };
 
   };
@@ -89,12 +105,12 @@ in rec {
     text = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Text of this systemd unit.";
+      description = lib.mdDoc "Text of this systemd unit.";
     };
 
     unit = mkOption {
       internal = true;
-      description = "The generated unit.";
+      description = lib.mdDoc "The generated unit.";
     };
 
   };
@@ -105,19 +121,19 @@ in rec {
       description = mkOption {
         default = "";
         type = types.singleLineStr;
-        description = "Description of this unit used in systemd messages and progress indicators.";
+        description = lib.mdDoc "Description of this unit used in systemd messages and progress indicators.";
       };
 
       documentation = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = "A list of URIs referencing documentation for this unit or its configuration.";
+        description = lib.mdDoc "A list of URIs referencing documentation for this unit or its configuration.";
       };
 
       requires = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           Start the specified units when this unit is started, and stop
           this unit when the specified units are stopped or fail.
         '';
@@ -126,7 +142,7 @@ in rec {
       wants = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           Start the specified units when this unit is started.
         '';
       };
@@ -134,7 +150,7 @@ in rec {
       after = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           If the specified units are started at the same time as
           this unit, delay this unit until they have started.
         '';
@@ -143,7 +159,7 @@ in rec {
       before = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           If the specified units are started at the same time as
           this unit, delay them until this unit has started.
         '';
@@ -152,7 +168,7 @@ in rec {
       bindsTo = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           Like ‘requires’, but in addition, if the specified units
           unexpectedly disappear, this unit will be stopped as well.
         '';
@@ -161,7 +177,7 @@ in rec {
       partOf = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           If the specified units are stopped or restarted, then this
           unit is stopped or restarted as well.
         '';
@@ -170,7 +186,7 @@ in rec {
       conflicts = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           If the specified units are started, then this unit is stopped
           and vice versa.
         '';
@@ -179,7 +195,7 @@ in rec {
       requisite = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           Similar to requires. However if the units listed are not started,
           they will not be started and the transaction will fail.
         '';
@@ -189,18 +205,17 @@ in rec {
         default = {};
         example = { RequiresMountsFor = "/data"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Unit]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.unit</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Unit]` section of the unit.  See
+          {manpage}`systemd.unit(5)` for details.
         '';
       };
 
       onFailure = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           A list of one or more units that are activated when
           this unit enters the "failed" state.
         '';
@@ -209,7 +224,7 @@ in rec {
       onSuccess = mkOption {
         default = [];
         type = types.listOf unitNameType;
-        description = ''
+        description = lib.mdDoc ''
           A list of one or more units that are activated when
           this unit enters the "inactive" state.
         '';
@@ -217,7 +232,7 @@ in rec {
 
       startLimitBurst = mkOption {
          type = types.int;
-         description = ''
+         description = lib.mdDoc ''
            Configure unit start rate limiting. Units which are started
            more than startLimitBurst times within an interval time
            interval are not permitted to start any more.
@@ -226,7 +241,7 @@ in rec {
 
       startLimitIntervalSec = mkOption {
          type = types.int;
-         description = ''
+         description = lib.mdDoc ''
            Configure unit start rate limiting. Units which are started
            more than startLimitBurst times within an interval time
            interval are not permitted to start any more.
@@ -245,7 +260,7 @@ in rec {
       restartTriggers = mkOption {
         default = [];
         type = types.listOf types.unspecified;
-        description = ''
+        description = lib.mdDoc ''
           An arbitrary list of items such as derivations.  If any item
           in the list changes between reconfigurations, the service will
           be restarted.
@@ -255,7 +270,7 @@ in rec {
       reloadTriggers = mkOption {
         default = [];
         type = types.listOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           An arbitrary list of items such as derivations.  If any item
           in the list changes between reconfigurations, the service will
           be reloaded.  If anything but a reload trigger changes in the
@@ -273,16 +288,16 @@ in rec {
         default = {};
         type = with types; attrsOf (nullOr (oneOf [ str path package ]));
         example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
-        description = "Environment variables passed to the service's processes.";
+        description = lib.mdDoc "Environment variables passed to the service's processes.";
       };
 
       path = mkOption {
         default = [];
         type = with types; listOf (oneOf [ package str ]);
-        description = ''
-          Packages added to the service's <envar>PATH</envar>
-          environment variable.  Both the <filename>bin</filename>
-          and <filename>sbin</filename> subdirectories of each
+        description = lib.mdDoc ''
+          Packages added to the service's {env}`PATH`
+          environment variable.  Both the {file}`bin`
+          and {file}`sbin` subdirectories of each
           package are added.
         '';
       };
@@ -293,30 +308,33 @@ in rec {
           { RestartSec = 5;
           };
         type = types.addCheck (types.attrsOf unitOption) checkService;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Service]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.service</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Service]` section of the unit.  See
+          {manpage}`systemd.service(5)` for details.
         '';
       };
 
       script = mkOption {
         type = types.lines;
         default = "";
-        description = "Shell commands executed as the service's main process.";
+        description = lib.mdDoc "Shell commands executed as the service's main process.";
       };
 
       scriptArgs = mkOption {
         type = types.str;
         default = "";
-        description = "Arguments passed to the main process script.";
+        example = "%i";
+        description = lib.mdDoc ''
+          Arguments passed to the main process script.
+          Can contain specifiers (`%` placeholders expanded by systemd, see {manpage}`systemd.unit(5)`).
+        '';
       };
 
       preStart = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed before the service's main process
           is started.
         '';
@@ -325,7 +343,7 @@ in rec {
       postStart = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed after the service's main process
           is started.
         '';
@@ -334,7 +352,7 @@ in rec {
       reload = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed when the service's main process
           is reloaded.
         '';
@@ -343,7 +361,7 @@ in rec {
       preStop = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed to stop the service.
         '';
       };
@@ -351,7 +369,7 @@ in rec {
       postStop = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed after the service's main process
           has exited.
         '';
@@ -360,7 +378,7 @@ in rec {
       jobScripts = mkOption {
         type = with types; coercedTo path singleton (listOf path);
         internal = true;
-        description = "A list of all job script derivations of this unit.";
+        description = lib.mdDoc "A list of all job script derivations of this unit.";
         default = [];
       };
 
@@ -405,7 +423,7 @@ in rec {
       restartIfChanged = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the service should be restarted during a NixOS
           configuration switch if its definition has changed.
         '';
@@ -414,14 +432,14 @@ in rec {
       reloadIfChanged = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether the service should be reloaded during a NixOS
           configuration switch if its definition has changed.  If
-          enabled, the value of <option>restartIfChanged</option> is
+          enabled, the value of {option}`restartIfChanged` is
           ignored.
 
           This option should not be used anymore in favor of
-          <option>reloadTriggers</option> which allows more granular
+          {option}`reloadTriggers` which allows more granular
           control of when a service is reloaded and when a service
           is restarted.
         '';
@@ -430,14 +448,14 @@ in rec {
       stopIfChanged = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           If set, a changed unit is restarted by calling
-          <command>systemctl stop</command> in the old configuration,
-          then <command>systemctl start</command> in the new one.
+          {command}`systemctl stop` in the old configuration,
+          then {command}`systemctl start` in the new one.
           Otherwise, it is restarted in a single step using
-          <command>systemctl restart</command> in the new configuration.
+          {command}`systemctl restart` in the new configuration.
           The latter is less correct because it runs the
-          <literal>ExecStop</literal> commands from the new
+          `ExecStop` commands from the new
           configuration.
         '';
       };
@@ -446,13 +464,12 @@ in rec {
         type = with types; either str (listOf str);
         default = [];
         example = "Sun 14:00:00";
-        description = ''
+        description = lib.mdDoc ''
           Automatically start this unit at the given date/time, which
           must be in the format described in
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.  This is equivalent
+          {manpage}`systemd.time(7)`.  This is equivalent
           to adding a corresponding timer unit with
-          <option>OnCalendar</option> set to the value given here.
+          {option}`OnCalendar` set to the value given here.
         '';
         apply = v: if isList v then v else [ v ];
       };
@@ -474,9 +491,9 @@ in rec {
         default = [];
         type = types.listOf types.str;
         example = [ "0.0.0.0:993" "/run/my-socket" ];
-        description = ''
-          For each item in this list, a <literal>ListenStream</literal>
-          option in the <literal>[Socket]</literal> section will be created.
+        description = lib.mdDoc ''
+          For each item in this list, a `ListenStream`
+          option in the `[Socket]` section will be created.
         '';
       };
 
@@ -484,9 +501,9 @@ in rec {
         default = [];
         type = types.listOf types.str;
         example = [ "0.0.0.0:993" "/run/my-socket" ];
-        description = ''
-          For each item in this list, a <literal>ListenDatagram</literal>
-          option in the <literal>[Socket]</literal> section will be created.
+        description = lib.mdDoc ''
+          For each item in this list, a `ListenDatagram`
+          option in the `[Socket]` section will be created.
         '';
       };
 
@@ -494,11 +511,10 @@ in rec {
         default = {};
         example = { ListenStream = "/run/my-socket"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Socket]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.socket</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Socket]` section of the unit.  See
+          {manpage}`systemd.socket(5)` for details.
         '';
       };
     };
@@ -527,13 +543,11 @@ in rec {
         default = {};
         example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Timer]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.timer</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> and
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry> for details.
+          `[Timer]` section of the unit.  See
+          {manpage}`systemd.timer(5)` and
+          {manpage}`systemd.time(7)` for details.
         '';
       };
 
@@ -562,11 +576,10 @@ in rec {
         default = {};
         example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Path]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.path</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Path]` section of the unit.  See
+          {manpage}`systemd.path(5)` for details.
         '';
       };
 
@@ -594,13 +607,13 @@ in rec {
       what = mkOption {
         example = "/dev/sda1";
         type = types.str;
-        description = "Absolute path of device node, file or other resource. (Mandatory)";
+        description = lib.mdDoc "Absolute path of device node, file or other resource. (Mandatory)";
       };
 
       where = mkOption {
         example = "/mnt";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Absolute path of a directory of the mount point.
           Will be created if it doesn't exist. (Mandatory)
         '';
@@ -610,25 +623,24 @@ in rec {
         default = "";
         example = "ext4";
         type = types.str;
-        description = "File system type.";
+        description = lib.mdDoc "File system type.";
       };
 
       options = mkOption {
         default = "";
         example = "noatime";
         type = types.commas;
-        description = "Options used to mount the file system.";
+        description = lib.mdDoc "Options used to mount the file system.";
       };
 
       mountConfig = mkOption {
         default = {};
         example = { DirectoryMode = "0775"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Mount]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.mount</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Mount]` section of the unit.  See
+          {manpage}`systemd.mount(5)` for details.
         '';
       };
 
@@ -655,7 +667,7 @@ in rec {
       where = mkOption {
         example = "/mnt";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Absolute path of a directory of the mount point.
           Will be created if it doesn't exist. (Mandatory)
         '';
@@ -665,11 +677,10 @@ in rec {
         default = {};
         example = { DirectoryMode = "0775"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Automount]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.automount</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Automount]` section of the unit.  See
+          {manpage}`systemd.automount(5)` for details.
         '';
       };
 
@@ -697,11 +708,10 @@ in rec {
         default = {};
         example = { MemoryMax = "2G"; };
         type = types.attrsOf unitOption;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Slice]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.slice</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Slice]` section of the unit.  See
+          {manpage}`systemd.slice(5)` for details.
         '';
       };
 
diff --git a/nixpkgs/nixos/lib/test-driver/default.nix b/nixpkgs/nixos/lib/test-driver/default.nix
index e3786622c3c5..33313059fff7 100644
--- a/nixpkgs/nixos/lib/test-driver/default.nix
+++ b/nixpkgs/nixos/lib/test-driver/default.nix
@@ -31,7 +31,7 @@ python3Packages.buildPythonApplication rec {
     ++ extraPythonPackages python3Packages;
 
   doCheck = true;
-  checkInputs = with python3Packages; [ mypy pylint black ];
+  nativeCheckInputs = with python3Packages; [ mypy pylint black ];
   checkPhase = ''
     mypy --disallow-untyped-defs \
           --no-implicit-optional \
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py b/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py
index 61d91c9ed654..db7e0ed33a89 100755
--- a/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py
@@ -41,11 +41,9 @@ def writeable_dir(arg: str) -> Path:
     """
     path = Path(arg)
     if not path.is_dir():
-        raise argparse.ArgumentTypeError("{0} is not a directory".format(path))
+        raise argparse.ArgumentTypeError(f"{path} is not a directory")
     if not os.access(path, os.W_OK):
-        raise argparse.ArgumentTypeError(
-            "{0} is not a writeable directory".format(path)
-        )
+        raise argparse.ArgumentTypeError(f"{path} is not a writeable directory")
     return path
 
 
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/driver.py b/nixpkgs/nixos/lib/test-driver/test_driver/driver.py
index e32f6810ca87..835d60ec3b4f 100644
--- a/nixpkgs/nixos/lib/test-driver/test_driver/driver.py
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/driver.py
@@ -2,6 +2,7 @@ from contextlib import contextmanager
 from pathlib import Path
 from typing import Any, Dict, Iterator, List, Union, Optional, Callable, ContextManager
 import os
+import re
 import tempfile
 
 from test_driver.logger import rootlog
@@ -19,19 +20,19 @@ def get_tmp_dir() -> Path:
     tmp_dir.mkdir(mode=0o700, exist_ok=True)
     if not tmp_dir.is_dir():
         raise NotADirectoryError(
-            "The directory defined by TMPDIR, TEMP, TMP or CWD: {0} is not a directory".format(
-                tmp_dir
-            )
+            f"The directory defined by TMPDIR, TEMP, TMP or CWD: {tmp_dir} is not a directory"
         )
     if not os.access(tmp_dir, os.W_OK):
         raise PermissionError(
-            "The directory defined by TMPDIR, TEMP, TMP, or CWD: {0} is not writeable".format(
-                tmp_dir
-            )
+            f"The directory defined by TMPDIR, TEMP, TMP, or CWD: {tmp_dir} is not writeable"
         )
     return tmp_dir
 
 
+def pythonize_name(name: str) -> str:
+    return re.sub(r"^[^A-z_]|[^A-z0-9_]", "_", name)
+
+
 class Driver:
     """A handle to the driver that sets up the environment
     and runs the tests"""
@@ -117,7 +118,7 @@ class Driver:
             polling_condition=self.polling_condition,
             Machine=Machine,  # for typing
         )
-        machine_symbols = {m.name: m for m in self.machines}
+        machine_symbols = {pythonize_name(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:
@@ -162,11 +163,6 @@ class Driver:
                 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 = get_tmp_dir()
 
         if args.get("startCommand"):
@@ -183,7 +179,6 @@ class Driver:
             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:
@@ -220,6 +215,20 @@ class Driver:
                 res = driver.polling_conditions.pop()
                 assert res is self.condition
 
+            def wait(self, timeout: int = 900) -> None:
+                def condition(last: bool) -> bool:
+                    if last:
+                        rootlog.info(f"Last chance for {self.condition.description}")
+                    ret = self.condition.check(force=True)
+                    if not ret and not last:
+                        rootlog.info(
+                            f"({self.condition.description} failure not fatal yet)"
+                        )
+                    return ret
+
+                with rootlog.nested(f"waiting for {self.condition.description}"):
+                    retry(condition, timeout=timeout)
+
         if fun_ is None:
             return Poll
         else:
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/logger.py b/nixpkgs/nixos/lib/test-driver/test_driver/logger.py
index 59ed29547231..e6182ff7c761 100644
--- a/nixpkgs/nixos/lib/test-driver/test_driver/logger.py
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/logger.py
@@ -36,7 +36,7 @@ class Logger:
 
     def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
         if "machine" in attributes:
-            return "{}: {}".format(attributes["machine"], message)
+            return f"{attributes['machine']}: {message}"
         return message
 
     def log_line(self, message: str, attributes: Dict[str, str]) -> None:
@@ -62,9 +62,7 @@ class Logger:
     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
-            )
+            self._eprint(Style.DIM + f"{machine} # {message}" + Style.RESET_ALL)
 
     def enqueue(self, item: Dict[str, str]) -> None:
         self.queue.put(item)
@@ -97,7 +95,7 @@ class Logger:
         yield
         self.drain_log_queue()
         toc = time.time()
-        self.log("(finished: {}, in {:.2f} seconds)".format(message, toc - tic))
+        self.log(f"(finished: {message}, in {toc - tic:.2f} seconds)")
 
         self.xml.endElement("nest")
 
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/machine.py b/nixpkgs/nixos/lib/test-driver/test_driver/machine.py
index 3ff3cf5645f8..1d1d5bef9bf4 100644
--- a/nixpkgs/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/machine.py
@@ -1,4 +1,4 @@
-from contextlib import _GeneratorContextManager
+from contextlib import _GeneratorContextManager, nullcontext
 from pathlib import Path
 from queue import Queue
 from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
@@ -7,6 +7,7 @@ import io
 import os
 import queue
 import re
+import select
 import shlex
 import shutil
 import socket
@@ -99,16 +100,16 @@ def _perform_ocr_on_screenshot(
         + "-blur 1x65535"
     )
 
-    tess_args = f"-c debug_file=/dev/null --psm 11"
+    tess_args = "-c debug_file=/dev/null --psm 11"
 
-    cmd = f"convert {magick_args} {screenshot_path} tiff:{screenshot_path}.tiff"
+    cmd = f"convert {magick_args} '{screenshot_path}' 'tiff:{screenshot_path}.tiff'"
     ret = subprocess.run(cmd, shell=True, capture_output=True)
     if ret.returncode != 0:
         raise Exception(f"TIFF conversion failed with exit code {ret.returncode}")
 
     model_results = []
     for model_id in model_ids:
-        cmd = f"tesseract {screenshot_path}.tiff - {tess_args} --oem {model_id}"
+        cmd = f"tesseract '{screenshot_path}.tiff' - {tess_args} --oem '{model_id}'"
         ret = subprocess.run(cmd, shell=True, capture_output=True)
         if ret.returncode != 0:
             raise Exception(f"OCR failed with exit code {ret.returncode}")
@@ -132,7 +133,7 @@ def retry(fn: Callable, timeout: int = 900) -> None:
 
 
 class StartCommand:
-    """The Base Start Command knows how to append the necesary
+    """The Base Start Command knows how to append the necessary
     runtime qemu options as determined by a particular test driver
     run. Any such start command is expected to happily receive and
     append additional qemu args.
@@ -144,7 +145,7 @@ class StartCommand:
         self,
         monitor_socket_path: Path,
         shell_socket_path: Path,
-        allow_reboot: bool = False,  # TODO: unused, legacy?
+        allow_reboot: bool = False,
     ) -> str:
         display_opts = ""
         display_available = any(x in os.environ for x in ["DISPLAY", "WAYLAND_DISPLAY"])
@@ -152,16 +153,15 @@ class StartCommand:
             display_opts += " -nographic"
 
         # qemu options
-        qemu_opts = ""
-        qemu_opts += (
-            ""
-            if allow_reboot
-            else " -no-reboot"
+        qemu_opts = (
             " -device virtio-serial"
+            # Note: virtconsole will map to /dev/hvc0 in Linux guests
             " -device virtconsole,chardev=shell"
             " -device virtio-rng-pci"
             " -serial stdio"
         )
+        if not allow_reboot:
+            qemu_opts += " -no-reboot"
         # TODO: qemu script already catpures this env variable, legacy?
         qemu_opts += " " + os.environ.get("QEMU_OPTS", "")
 
@@ -195,9 +195,10 @@ class StartCommand:
         shared_dir: Path,
         monitor_socket_path: Path,
         shell_socket_path: Path,
+        allow_reboot: bool,
     ) -> subprocess.Popen:
         return subprocess.Popen(
-            self.cmd(monitor_socket_path, shell_socket_path),
+            self.cmd(monitor_socket_path, shell_socket_path, allow_reboot),
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.STDOUT,
@@ -210,7 +211,7 @@ class StartCommand:
 class NixStartScript(StartCommand):
     """A start script from nixos/modules/virtualiation/qemu-vm.nix
     that also satisfies the requirement of the BaseStartCommand.
-    These Nix commands have the particular charactersitic that the
+    These Nix commands have the particular characteristic that the
     machine name can be extracted out of them via a regex match.
     (Admittedly a _very_ implicit contract, evtl. TODO fix)
     """
@@ -312,7 +313,6 @@ class Machine:
 
     start_command: StartCommand
     keep_vm_state: bool
-    allow_reboot: bool
 
     process: Optional[subprocess.Popen]
     pid: Optional[int]
@@ -337,13 +337,11 @@ class Machine:
         start_command: StartCommand,
         name: str = "machine",
         keep_vm_state: bool = False,
-        allow_reboot: bool = False,
         callbacks: Optional[List[Callable]] = None,
     ) -> None:
         self.out_dir = out_dir
         self.tmp_dir = tmp_dir
         self.keep_vm_state = keep_vm_state
-        self.allow_reboot = allow_reboot
         self.name = name
         self.start_command = start_command
         self.callbacks = callbacks if callbacks is not None else []
@@ -371,8 +369,8 @@ class Machine:
     @staticmethod
     def create_startcommand(args: Dict[str, str]) -> StartCommand:
         rootlog.warning(
-            "Using legacy create_startcommand(),"
-            "please use proper nix test vm instrumentation, instead"
+            "Using legacy create_startcommand(), "
+            "please use proper nix test vm instrumentation, instead "
             "to generate the appropriate nixos test vm qemu startup script"
         )
         hda = None
@@ -406,27 +404,27 @@ class Machine:
         return rootlog.nested(msg, my_attrs)
 
     def wait_for_monitor_prompt(self) -> str:
-        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
+        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:
         self.run_callbacks()
-        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()
+        message = f"{command}\n".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:
+    def wait_for_unit(
+        self, unit: str, user: Optional[str] = None, timeout: int = 900
+    ) -> None:
         """Wait for a systemd unit to get into "active" state.
         Throws exceptions on "failed" and "inactive" states as well as
         after timing out.
@@ -436,7 +434,7 @@ class Machine:
             info = self.get_unit_info(unit, user)
             state = info["ActiveState"]
             if state == "failed":
-                raise Exception('unit "{}" reached state "{}"'.format(unit, state))
+                raise Exception(f'unit "{unit}" reached state "{state}"')
 
             if state == "inactive":
                 status, jobs = self.systemctl("list-jobs --full 2>&1", user)
@@ -444,27 +442,24 @@ class Machine:
                     info = self.get_unit_info(unit, user)
                     if info["ActiveState"] == state:
                         raise Exception(
-                            (
-                                'unit "{}" is inactive and there ' "are no pending jobs"
-                            ).format(unit)
+                            f'unit "{unit}" is inactive and there are no pending jobs'
                         )
 
             return state == "active"
 
         with self.nested(
-            "waiting for unit {}{}".format(
-                unit, f" with user {user}" if user is not None else ""
-            )
+            f"waiting for unit {unit}"
+            + (f" with user {user}" if user is not None else "")
         ):
-            retry(check_active)
+            retry(check_active, timeout)
 
     def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]:
-        status, lines = self.systemctl('--no-pager show "{}"'.format(unit), user)
+        status, lines = self.systemctl(f'--no-pager show "{unit}"', user)
         if status != 0:
             raise Exception(
-                'retrieving systemctl info for unit "{}" {} failed with exit code {}'.format(
-                    unit, "" if user is None else 'under user "{}"'.format(user), status
-                )
+                f'retrieving systemctl info for unit "{unit}"'
+                + ("" if user is None else f' under user "{user}"')
+                + f" failed with exit code {status}"
             )
 
         line_pattern = re.compile(r"^([^=]+)=(.*)$")
@@ -484,24 +479,22 @@ class Machine:
         if user is not None:
             q = q.replace("'", "\\'")
             return self.execute(
-                (
-                    "su -l {} --shell /bin/sh -c "
-                    "$'XDG_RUNTIME_DIR=/run/user/`id -u` "
-                    "systemctl --user {}'"
-                ).format(user, q)
+                f"su -l {user} --shell /bin/sh -c "
+                "$'XDG_RUNTIME_DIR=/run/user/`id -u` "
+                f"systemctl --user {q}'"
             )
-        return self.execute("systemctl {}".format(q))
+        return self.execute(f"systemctl {q}")
 
     def require_unit_state(self, unit: str, require_state: str = "active") -> None:
         with self.nested(
-            "checking if unit ‘{}’ has reached state '{}'".format(unit, require_state)
+            f"checking if unit '{unit}' has reached state '{require_state}'"
         ):
             info = self.get_unit_info(unit)
             state = info["ActiveState"]
             if state != require_state:
                 raise Exception(
-                    "Expected unit ‘{}’ to to be in state ".format(unit)
-                    + "'{}' but it is in state ‘{}’".format(require_state, state)
+                    f"Expected unit '{unit}' to to be in state "
+                    f"'{require_state}' but it is in state '{state}'"
                 )
 
     def _next_newline_closed_block_from_shell(self) -> str:
@@ -533,8 +526,10 @@ class Machine:
         if timeout is not None:
             timeout_str = f"timeout {timeout}"
 
+        # While sh is bash on NixOS, this is not the case for every distro.
+        # We explicitly call bash here to allow for the driver to boot other distros as well.
         out_command = (
-            f"{timeout_str} sh -c {shlex.quote(command)} | (base64 --wrap 0; echo)\n"
+            f"{timeout_str} bash -c {shlex.quote(command)} | (base64 --wrap 0; echo)\n"
         )
 
         assert self.shell
@@ -550,20 +545,29 @@ class Machine:
         self.shell.send("echo ${PIPESTATUS[0]}\n".encode())
         rc = int(self._next_newline_closed_block_from_shell().strip())
 
-        return (rc, output.decode())
+        return (rc, output.decode(errors="replace"))
 
-    def shell_interact(self) -> None:
-        """Allows you to interact with the guest shell
+    def shell_interact(self, address: Optional[str] = None) -> None:
+        """Allows you to interact with the guest shell for debugging purposes.
 
-        Should only be used during test development, not in the production test."""
+        @address string passed to socat that will be connected to the guest shell.
+        Check the `Running Tests interactivly` chapter of NixOS manual for an example.
+        """
         self.connect()
-        self.log("Terminal is ready (there is no initial prompt):")
+
+        if address is None:
+            address = "READLINE,prompt=$ "
+            self.log("Terminal is ready (there is no initial prompt):")
 
         assert self.shell
-        subprocess.run(
-            ["socat", "READLINE,prompt=$ ", f"FD:{self.shell.fileno()}"],
-            pass_fds=[self.shell.fileno()],
-        )
+        try:
+            subprocess.run(
+                ["socat", address, f"FD:{self.shell.fileno()}"],
+                pass_fds=[self.shell.fileno()],
+            )
+            # allow users to cancel this command without breaking the test
+        except KeyboardInterrupt:
+            pass
 
     def console_interact(self) -> None:
         """Allows you to interact with QEMU's stdin
@@ -591,13 +595,11 @@ class Machine:
         """Execute each command and check that it succeeds."""
         output = ""
         for command in commands:
-            with self.nested("must succeed: {}".format(command)):
+            with self.nested(f"must succeed: {command}"):
                 (status, out) = self.execute(command, timeout=timeout)
                 if status != 0:
-                    self.log("output: {}".format(out))
-                    raise Exception(
-                        "command `{}` failed (exit code {})".format(command, status)
-                    )
+                    self.log(f"output: {out}")
+                    raise Exception(f"command `{command}` failed (exit code {status})")
                 output += out
         return output
 
@@ -605,12 +607,10 @@ class Machine:
         """Execute each command and check that it fails."""
         output = ""
         for command in commands:
-            with self.nested("must fail: {}".format(command)):
+            with self.nested(f"must fail: {command}"):
                 (status, out) = self.execute(command, timeout=timeout)
                 if status == 0:
-                    raise Exception(
-                        "command `{}` unexpectedly succeeded".format(command)
-                    )
+                    raise Exception(f"command `{command}` unexpectedly succeeded")
                 output += out
         return output
 
@@ -625,7 +625,7 @@ class Machine:
             status, output = self.execute(command, timeout=timeout)
             return status == 0
 
-        with self.nested("waiting for success: {}".format(command)):
+        with self.nested(f"waiting for success: {command}"):
             retry(check_success, timeout)
             return output
 
@@ -640,7 +640,7 @@ class Machine:
             status, output = self.execute(command, timeout=timeout)
             return status != 0
 
-        with self.nested("waiting for failure: {}".format(command)):
+        with self.nested(f"waiting for failure: {command}"):
             retry(check_failure)
             return output
 
@@ -659,8 +659,8 @@ class Machine:
 
     def get_tty_text(self, tty: str) -> str:
         status, output = self.execute(
-            "fold -w$(stty -F /dev/tty{0} size | "
-            "awk '{{print $2}}') /dev/vcs{0}".format(tty)
+            f"fold -w$(stty -F /dev/tty{tty} size | "
+            f"awk '{{print $2}}') /dev/vcs{tty}"
         )
         return output
 
@@ -679,50 +679,59 @@ class Machine:
                 )
             return len(matcher.findall(text)) > 0
 
-        with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)):
+        with self.nested(f"waiting for {regexp} to appear on tty {tty}"):
             retry(tty_matches)
 
-    def send_chars(self, chars: str) -> None:
-        with self.nested("sending keys ‘{}‘".format(chars)):
+    def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None:
+        with self.nested(f"sending keys {repr(chars)}"):
             for char in chars:
-                self.send_key(char)
+                self.send_key(char, delay, log=False)
 
     def wait_for_file(self, filename: str) -> None:
         """Waits until the file exists in machine's file system."""
 
         def check_file(_: Any) -> bool:
-            status, _ = self.execute("test -e {}".format(filename))
+            status, _ = self.execute(f"test -e {filename}")
             return status == 0
 
-        with self.nested("waiting for file ‘{}‘".format(filename)):
+        with self.nested(f"waiting for file '{filename}'"):
             retry(check_file)
 
-    def wait_for_open_port(self, port: int) -> None:
+    def wait_for_open_port(self, port: int, addr: str = "localhost") -> None:
         def port_is_open(_: Any) -> bool:
-            status, _ = self.execute("nc -z localhost {}".format(port))
+            status, _ = self.execute(f"nc -z {addr} {port}")
             return status == 0
 
-        with self.nested("waiting for TCP port {}".format(port)):
+        with self.nested(f"waiting for TCP port {port} on {addr}"):
             retry(port_is_open)
 
-    def wait_for_closed_port(self, port: int) -> None:
+    def wait_for_closed_port(self, port: int, addr: str = "localhost") -> None:
         def port_is_closed(_: Any) -> bool:
-            status, _ = self.execute("nc -z localhost {}".format(port))
+            status, _ = self.execute(f"nc -z {addr} {port}")
             return status != 0
 
-        with self.nested("waiting for TCP port {} to be closed"):
+        with self.nested(f"waiting for TCP port {port} on {addr} 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)
+        return self.systemctl(f"start {jobname}", user)
 
     def stop_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
-        return self.systemctl("stop {}".format(jobname), user)
+        return self.systemctl(f"stop {jobname}", user)
 
     def wait_for_job(self, jobname: str) -> None:
         self.wait_for_unit(jobname)
 
     def connect(self) -> None:
+        def shell_ready(timeout_secs: int) -> bool:
+            """We sent some data from the backdoor service running on the guest
+            to indicate that the backdoor shell is ready.
+            As soon as we read some data from the socket here, we assume that
+            our root shell is operational.
+            """
+            (ready, _, _) = select.select([self.shell], [], [], timeout_secs)
+            return bool(ready)
+
         if self.connected:
             return
 
@@ -732,26 +741,30 @@ class Machine:
             assert self.shell
 
             tic = time.time()
-            self.shell.recv(1024)
-            # TODO: Timeout
+            # TODO: do we want to bail after a set number of attempts?
+            while not shell_ready(timeout_secs=30):
+                self.log("Guest root shell did not produce any data yet...")
+
+            self.log(self.shell.recv(1024).decode())
             toc = time.time()
 
             self.log("connected to guest root shell")
-            self.log("(connecting took {:.2f} seconds)".format(toc - tic))
+            self.log(f"(connecting took {toc - tic:.2f} seconds)")
             self.connected = True
 
     def screenshot(self, filename: str) -> None:
-        word_pattern = re.compile(r"^\w+$")
-        if word_pattern.match(filename):
-            filename = os.path.join(self.out_dir, "{}.png".format(filename))
-        tmp = "{}.ppm".format(filename)
+        if "." not in filename:
+            filename += ".png"
+        if "/" not in filename:
+            filename = os.path.join(self.out_dir, filename)
+        tmp = f"{filename}.ppm"
 
         with self.nested(
-            "making screenshot {}".format(filename),
+            f"making screenshot {filename}",
             {"image": os.path.basename(filename)},
         ):
-            self.send_monitor_command("screendump {}".format(tmp))
-            ret = subprocess.run("pnmtopng {} > {}".format(tmp, filename), shell=True)
+            self.send_monitor_command(f"screendump {tmp}")
+            ret = subprocess.run(f"pnmtopng '{tmp}' > '{filename}'", shell=True)
             os.unlink(tmp)
             if ret.returncode != 0:
                 raise Exception("Cannot convert screenshot")
@@ -813,7 +826,7 @@ class Machine:
 
     def dump_tty_contents(self, tty: str) -> None:
         """Debugging: Dump the contents of the TTY<n>"""
-        self.execute("fold -w 80 /dev/vcs{} | systemd-cat".format(tty))
+        self.execute(f"fold -w 80 /dev/vcs{tty} | systemd-cat")
 
     def _get_screen_text_variants(self, model_ids: Iterable[int]) -> List[str]:
         with tempfile.TemporaryDirectory() as tmpdir:
@@ -835,33 +848,54 @@ class Machine:
                     return True
 
             if last:
-                self.log("Last OCR attempt failed. Text was: {}".format(variants))
+                self.log(f"Last OCR attempt failed. Text was: {variants}")
 
             return False
 
-        with self.nested("waiting for {} to appear on screen".format(regex)):
+        with self.nested(f"waiting for {regex} to appear on screen"):
             retry(screen_matches)
 
-    def wait_for_console_text(self, regex: str) -> None:
-        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:
+    def wait_for_console_text(self, regex: str, timeout: int | None = None) -> None:
+        """
+        Wait for the provided regex to appear on console.
+        For each reads,
+
+        If timeout is None, timeout is infinite.
+
+        `timeout` is in seconds.
+        """
+        # Buffer the console output, this is needed
+        # to match multiline regexes.
+        console = io.StringIO()
+
+        def console_matches() -> bool:
+            nonlocal console
+            try:
+                # This will return as soon as possible and
+                # sleep 1 second.
+                console.write(self.last_lines.get(block=False))
+            except queue.Empty:
+                pass
+            console.seek(0)
+            matches = re.search(regex, console.read())
+            return matches is not None
+
+        with self.nested(f"waiting for {regex} to appear on console"):
+            if timeout is not None:
+                retry(console_matches, timeout)
+            else:
+                while not console_matches():
+                    pass
+
+    def send_key(
+        self, key: str, delay: Optional[float] = 0.01, log: Optional[bool] = True
+    ) -> None:
         key = CHAR_TO_KEY.get(key, key)
-        self.send_monitor_command("sendkey {}".format(key))
-        time.sleep(0.01)
+        context = self.nested(f"sending key {repr(key)}") if log else nullcontext()
+        with context:
+            self.send_monitor_command(f"sendkey {key}")
+            if delay is not None:
+                time.sleep(delay)
 
     def send_console(self, chars: str) -> None:
         assert self.process
@@ -869,7 +903,7 @@ class Machine:
         self.process.stdin.write(chars.encode())
         self.process.stdin.flush()
 
-    def start(self) -> None:
+    def start(self, allow_reboot: bool = False) -> None:
         if self.booted:
             return
 
@@ -893,6 +927,7 @@ class Machine:
             self.shared_dir,
             self.monitor_path,
             self.shell_path,
+            allow_reboot,
         )
         self.monitor, _ = monitor_socket.accept()
         self.shell, _ = shell_socket.accept()
@@ -918,7 +953,7 @@ class Machine:
         self.pid = self.process.pid
         self.booted = True
 
-        self.log("QEMU running (pid {})".format(self.pid))
+        self.log(f"QEMU running (pid {self.pid})")
 
     def cleanup_statedir(self) -> None:
         shutil.rmtree(self.state_dir)
@@ -941,6 +976,15 @@ class Machine:
         self.send_monitor_command("quit")
         self.wait_for_shutdown()
 
+    def reboot(self) -> None:
+        """Press Ctrl+Alt+Delete in the guest.
+
+        Prepares the machine to be reconnected which is useful if the
+        machine was started with `allow_reboot = True`
+        """
+        self.send_key("ctrl-alt-delete")
+        self.connected = False
+
     def wait_for_x(self) -> None:
         """Wait until it is possible to connect to the X server.  Note that
         testing the existence of /tmp/.X11-unix/X0 is insufficient.
@@ -972,7 +1016,7 @@ class Machine:
             names = self.get_window_names()
             if last_try:
                 self.log(
-                    "Last chance to match {} on the window list,".format(regexp)
+                    f"Last chance to match {regexp} on the window list,"
                     + " which currently contains: "
                     + ", ".join(names)
                 )
@@ -989,9 +1033,7 @@ class Machine:
         """Forward a TCP port on the host to a TCP port on the guest.
         Useful during interactive testing.
         """
-        self.send_monitor_command(
-            "hostfwd_add tcp::{}-:{}".format(host_port, guest_port)
-        )
+        self.send_monitor_command(f"hostfwd_add tcp::{host_port}-:{guest_port}")
 
     def block(self) -> None:
         """Make the machine unreachable by shutting down eth1 (the multicast
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/polling_condition.py b/nixpkgs/nixos/lib/test-driver/test_driver/polling_condition.py
index 459845452fa1..02ca0a03ab3d 100644
--- a/nixpkgs/nixos/lib/test-driver/test_driver/polling_condition.py
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/polling_condition.py
@@ -1,4 +1,5 @@
 from typing import Callable, Optional
+from math import isfinite
 import time
 
 from .logger import rootlog
@@ -14,7 +15,7 @@ class PollingCondition:
     description: Optional[str]
 
     last_called: float
-    entered: bool
+    entry_count: int
 
     def __init__(
         self,
@@ -34,14 +35,21 @@ class PollingCondition:
             self.description = str(description)
 
         self.last_called = float("-inf")
-        self.entered = False
+        self.entry_count = 0
 
-    def check(self) -> bool:
-        if self.entered or not self.overdue:
+    def check(self, force: bool = False) -> bool:
+        if (self.entered or not self.overdue) and not force:
             return True
 
         with self, rootlog.nested(self.nested_message):
-            rootlog.info(f"Time since last: {time.monotonic() - self.last_called:.2f}s")
+            time_since_last = time.monotonic() - self.last_called
+            last_message = (
+                f"Time since last: {time_since_last:.2f}s"
+                if isfinite(time_since_last)
+                else "(not called yet)"
+            )
+
+            rootlog.info(last_message)
             try:
                 res = self.condition()  # type: ignore
             except Exception:
@@ -69,9 +77,16 @@ class PollingCondition:
     def overdue(self) -> bool:
         return self.last_called + self.seconds_interval < time.monotonic()
 
+    @property
+    def entered(self) -> bool:
+        # entry_count should never dip *below* zero
+        assert self.entry_count >= 0
+        return self.entry_count > 0
+
     def __enter__(self) -> None:
-        self.entered = True
+        self.entry_count += 1
 
     def __exit__(self, exc_type, exc_value, traceback) -> None:  # type: ignore
-        self.entered = False
+        assert self.entered
+        self.entry_count -= 1
         self.last_called = time.monotonic()
diff --git a/nixpkgs/nixos/lib/testing-python.nix b/nixpkgs/nixos/lib/testing-python.nix
index 4bb1689ffd78..4904ad6e3591 100644
--- a/nixpkgs/nixos/lib/testing-python.nix
+++ b/nixpkgs/nixos/lib/testing-python.nix
@@ -1,3 +1,4 @@
+args@
 { system
 , pkgs ? import ../.. { inherit system config; }
   # Use a minimal kernel?
@@ -5,168 +6,37 @@
   # Ignored
 , config ? { }
   # !!! See comment about args in lib/modules.nix
-, specialArgs ? { }
+, specialArgs ? throw "legacy - do not use, see error below"
   # Modules to add to each VM
 , extraConfigurations ? [ ]
 }:
-
-with pkgs;
-
+let
+  nixos-lib = import ./default.nix { inherit (pkgs) lib; };
+in
+
+pkgs.lib.throwIf (args?specialArgs) ''
+  testing-python.nix: `specialArgs` is not supported anymore. If you're looking
+  for the public interface to the NixOS test framework, use `runTest`, and
+  `node.specialArgs`.
+  See https://nixos.org/manual/nixos/unstable/index.html#sec-calling-nixos-tests
+  and https://nixos.org/manual/nixos/unstable/index.html#test-opt-node.specialArgs
+''
 rec {
 
   inherit pkgs;
 
-  # Run an automated test suite in the given virtual network.
-  runTests = { driver, driverInteractive, pos }:
-    stdenv.mkDerivation {
-      name = "vm-test-run-${driver.testName}";
-
-      requiredSystemFeatures = [ "kvm" "nixos-test" ];
-
-      buildCommand =
-        ''
-          mkdir -p $out
-
-          # effectively mute the XMLLogger
-          export LOGFILE=/dev/null
+  evalTest = module: nixos-lib.evalTest { imports = [ extraTestModule module ]; };
+  runTest = module: nixos-lib.runTest { imports = [ extraTestModule module ]; };
 
-          ${driver}/bin/nixos-test-driver -o $out
-        '';
-
-      passthru = driver.passthru // {
-        inherit driver driverInteractive;
-      };
-
-      inherit pos; # for better debugging
+  extraTestModule = {
+    config = {
+      hostPkgs = pkgs;
     };
+  };
 
-  # Generate convenience wrappers for running the test driver
-  # has vlans, vms and test script defaulted through env variables
-  # also instantiates test script with nodes, if it's a function (contract)
-  setupDriverForTest = {
-      testScript
-    , testName
-    , nodes
-    , qemu_pkg ? pkgs.qemu_test
-    , enableOCR ? false
-    , skipLint ? false
-    , skipTypeCheck ? false
-    , passthru ? {}
-    , interactive ? false
-    , extraPythonPackages ? (_ :[])
-  }:
-    let
-      # 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 extraPythonPackages;
-        qemu_pkg = qemu_test;
-        imagemagick_light = imagemagick_light.override { inherit libtiff; };
-        tesseract4 = tesseract4.override { enableLanguages = [ "eng" ]; };
-      };
-
-
-      testDriverName =
-        let
-          # A standard store path to the vm monitor is built like this:
-          #   /tmp/nix-build-vm-test-run-$name.drv-0/vm-state-machine/monitor
-          # The max filename length of a unix domain socket is 108 bytes.
-          # This means $name can at most be 50 bytes long.
-          maxTestNameLen = 50;
-          testNameLen = builtins.stringLength testName;
-        in with builtins;
-          if testNameLen > maxTestNameLen then
-            abort
-              ("The name of the test '${testName}' must not be longer than ${toString maxTestNameLen} " +
-                "it's currently ${toString testNameLen} characters long.")
-          else
-            "nixos-test-driver-${testName}";
-
-      vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes);
-      vms = map (m: m.config.system.build.vm) (lib.attrValues nodes);
-
-      nodeHostNames = let
-        nodesList = map (c: c.config.system.name) (lib.attrValues nodes);
-      in nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
-
-      # TODO: This is an implementation error and needs fixing
-      # the testing famework cannot legitimately restrict hostnames further
-      # beyond RFC1035
-      invalidNodeNames = lib.filter
-        (node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null)
-        nodeHostNames;
-
-      testScript' =
-        # Call the test script with the computed nodes.
-        if lib.isFunction testScript
-        then testScript { inherit nodes; }
-        else testScript;
-
-      uniqueVlans = lib.unique (builtins.concatLists vlans);
-      vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
-      machineNames = map (name: "${name}: Machine;") nodeHostNames;
-    in
-    if lib.length invalidNodeNames > 0 then
-      throw ''
-        Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
-        All machines are referenced as python variables in the testing framework which will break the
-        script when special characters are used.
-
-        This is an IMPLEMENTATION ERROR and needs to be fixed. Meanwhile,
-        please stick to alphanumeric chars and underscores as separation.
-      ''
-    else lib.warnIf skipLint "Linting is disabled" (runCommand testDriverName
-      {
-        inherit testName;
-        nativeBuildInputs = [ makeWrapper mypy ];
-        buildInputs = [ testDriver ];
-        testScript = testScript';
-        preferLocalBuild = true;
-        passthru = passthru // {
-          inherit nodes;
-        };
-        meta.mainProgram = "nixos-test-driver";
-      }
-      ''
-        mkdir -p $out/bin
-
-        vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
-
-        ${lib.optionalString (!skipTypeCheck) ''
-          # prepend type hints so the test script can be type checked with mypy
-          cat "${./test-script-prepend.py}" >> testScriptWithTypes
-          echo "${builtins.toString machineNames}" >> testScriptWithTypes
-          echo "${builtins.toString vlanNames}" >> testScriptWithTypes
-          echo -n "$testScript" >> testScriptWithTypes
-
-          mypy  --no-implicit-optional \
-                --pretty \
-                --no-color-output \
-                testScriptWithTypes
-        ''}
-
-        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 "driver-symbols"}
-          )" ${python3Packages.pyflakes}/bin/pyflakes $out/test-script
-        ''}
-
-        # set defaults through environment
-        # see: ./test-driver/test-driver.py argparse implementation
-        wrapProgram $out/bin/nixos-test-driver \
-          --set startScripts "''${vmStartScripts[*]}" \
-          --set testScript "$out/test-script" \
-          --set vlans '${toString vlans}' \
-          ${lib.optionalString (interactive) "--add-flags --interactive"}
-      '');
-
-  # Make a full-blown test
+  # Make a full-blown test (legacy)
+  # For an official public interface to the tests, see
+  # https://nixos.org/manual/nixos/unstable/index.html#sec-calling-nixos-tests
   makeTest =
     { machine ? null
     , nodes ? {}
@@ -184,90 +54,23 @@ rec {
           then builtins.unsafeGetAttrPos "description" meta
           else builtins.unsafeGetAttrPos "testScript" t)
     , extraPythonPackages ? (_ : [])
-    } @ t:
-    let
-      mkNodes = qemu_pkg:
-        let
-          testScript' =
-            # Call the test script with the computed nodes.
-            if lib.isFunction testScript
-            then testScript { nodes = mkNodes 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;
-              }
-            )];
-          };
-        in
-          lib.warnIf (t?machine) "In test `${name}': The `machine' attribute in NixOS tests (pkgs.nixosTest / make-test-python.nix / testing-python.nix / makeTest) is deprecated. Please use the equivalent `nodes.machine'."
-          build-vms.buildVirtualNetwork (
-              nodes // lib.optionalAttrs (machine != null) { inherit machine; }
-          );
-
-      driver = setupDriverForTest {
-        inherit testScript enableOCR skipTypeCheck skipLint passthru extraPythonPackages;
-        testName = name;
-        qemu_pkg = pkgs.qemu_test;
-        nodes = mkNodes pkgs.qemu_test;
-      };
-      driverInteractive = setupDriverForTest {
-        inherit testScript enableOCR skipTypeCheck skipLint passthru extraPythonPackages;
-        testName = name;
-        qemu_pkg = pkgs.qemu;
-        nodes = mkNodes pkgs.qemu;
-        interactive = true;
-      };
-
-      test = lib.addMetaAttrs meta (runTests { inherit driver pos driverInteractive; });
-
+    , interactive ? {}
+    } @ t: let
+    testConfig =
+      (evalTest {
+        imports = [
+          { _file = "makeTest parameters"; config = t; }
+          {
+            defaults = {
+              _file = "makeTest: extraConfigurations";
+              imports = extraConfigurations;
+            };
+          }
+        ];
+      }).config;
     in
-      test // {
-        inherit test driver driverInteractive;
-        inherit (driver) nodes;
-      };
-
-  abortForFunction = functionName: abort ''The ${functionName} function was
-    removed because it is not an essential part of the NixOS testing
-    infrastructure. It had no usage in NixOS or Nixpkgs and it had no designated
-    maintainer. You are free to reintroduce it by documenting it in the manual
-    and adding yourself as maintainer. It was removed in
-    https://github.com/NixOS/nixpkgs/pull/137013
-  '';
-
-  runInMachine = abortForFunction "runInMachine";
-
-  runInMachineWithX = abortForFunction "runInMachineWithX";
+      testConfig.test   # For nix-build
+        // testConfig;  # For all-tests.nix
 
   simpleTest = as: (makeTest as).test;
 
diff --git a/nixpkgs/nixos/lib/testing/call-test.nix b/nixpkgs/nixos/lib/testing/call-test.nix
new file mode 100644
index 000000000000..9abcea07455e
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/call-test.nix
@@ -0,0 +1,12 @@
+{ config, lib, ... }:
+let
+  inherit (lib) mkOption types;
+in
+{
+  options = {
+    result = mkOption {
+      internal = true;
+      default = config;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/default.nix b/nixpkgs/nixos/lib/testing/default.nix
new file mode 100644
index 000000000000..a89f734b1e64
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/default.nix
@@ -0,0 +1,27 @@
+{ lib }:
+let
+
+  evalTest = module: lib.evalModules {
+    modules = testModules ++ [ module ];
+    class = "nixosTest";
+  };
+  runTest = module: (evalTest ({ config, ... }: { imports = [ module ]; result = config.test; })).config.result;
+
+  testModules = [
+    ./call-test.nix
+    ./driver.nix
+    ./interactive.nix
+    ./legacy.nix
+    ./meta.nix
+    ./name.nix
+    ./network.nix
+    ./nodes.nix
+    ./pkgs.nix
+    ./run.nix
+    ./testScript.nix
+  ];
+
+in
+{
+  inherit evalTest runTest testModules;
+}
diff --git a/nixpkgs/nixos/lib/testing/driver.nix b/nixpkgs/nixos/lib/testing/driver.nix
new file mode 100644
index 000000000000..444236efb1e7
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/driver.nix
@@ -0,0 +1,181 @@
+{ config, lib, hostPkgs, ... }:
+let
+  inherit (lib) mkOption types literalMD mdDoc;
+
+  # Reifies and correctly wraps the python test driver for
+  # the respective qemu version and with or without ocr support
+  testDriver = hostPkgs.callPackage ../test-driver {
+    inherit (config) enableOCR extraPythonPackages;
+    qemu_pkg = config.qemu.package;
+    imagemagick_light = hostPkgs.imagemagick_light.override { inherit (hostPkgs) libtiff; };
+    tesseract4 = hostPkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
+  };
+
+
+  vlans = map (m: (
+    m.virtualisation.vlans ++
+    (lib.mapAttrsToList (_: v: v.vlan) m.virtualisation.interfaces))) (lib.attrValues config.nodes);
+  vms = map (m: m.system.build.vm) (lib.attrValues config.nodes);
+
+  nodeHostNames =
+    let
+      nodesList = map (c: c.system.name) (lib.attrValues config.nodes);
+    in
+    nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
+
+  pythonizeName = name:
+    let
+      head = lib.substring 0 1 name;
+      tail = lib.substring 1 (-1) name;
+    in
+      (if builtins.match "[A-z_]" head == null then "_" else head) +
+      lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
+
+  uniqueVlans = lib.unique (builtins.concatLists vlans);
+  vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
+  pythonizedNames = map pythonizeName nodeHostNames;
+  machineNames = map (name: "${name}: Machine;") pythonizedNames;
+
+  withChecks = lib.warnIf config.skipLint "Linting is disabled";
+
+  driver =
+    hostPkgs.runCommand "nixos-test-driver-${config.name}"
+      {
+        # inherit testName; TODO (roberth): need this?
+        nativeBuildInputs = [
+          hostPkgs.makeWrapper
+        ] ++ lib.optionals (!config.skipTypeCheck) [ hostPkgs.mypy ];
+        buildInputs = [ testDriver ];
+        testScript = config.testScriptString;
+        preferLocalBuild = true;
+        passthru = config.passthru;
+        meta = config.meta // {
+          mainProgram = "nixos-test-driver";
+        };
+      }
+      ''
+        mkdir -p $out/bin
+
+        vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
+
+        ${lib.optionalString (!config.skipTypeCheck) ''
+          # prepend type hints so the test script can be type checked with mypy
+          cat "${../test-script-prepend.py}" >> testScriptWithTypes
+          echo "${builtins.toString machineNames}" >> testScriptWithTypes
+          echo "${builtins.toString vlanNames}" >> testScriptWithTypes
+          echo -n "$testScript" >> testScriptWithTypes
+
+          cat -n testScriptWithTypes
+
+          mypy  --no-implicit-optional \
+                --pretty \
+                --no-color-output \
+                testScriptWithTypes
+        ''}
+
+        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 (!config.skipLint) ''
+          PYFLAKES_BUILTINS="$(
+            echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
+            < ${lib.escapeShellArg "driver-symbols"}
+          )" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
+        ''}
+
+        # set defaults through environment
+        # see: ./test-driver/test-driver.py argparse implementation
+        wrapProgram $out/bin/nixos-test-driver \
+          --set startScripts "''${vmStartScripts[*]}" \
+          --set testScript "$out/test-script" \
+          --set vlans '${toString vlans}' \
+          ${lib.escapeShellArgs (lib.concatMap (arg: ["--add-flags" arg]) config.extraDriverArgs)}
+      '';
+
+in
+{
+  options = {
+
+    driver = mkOption {
+      description = mdDoc "Package containing a script that runs the test.";
+      type = types.package;
+      defaultText = literalMD "set by the test framework";
+    };
+
+    hostPkgs = mkOption {
+      description = mdDoc "Nixpkgs attrset used outside the nodes.";
+      type = types.raw;
+      example = lib.literalExpression ''
+        import nixpkgs { inherit system config overlays; }
+      '';
+    };
+
+    qemu.package = mkOption {
+      description = mdDoc "Which qemu package to use for the virtualisation of [{option}`nodes`](#test-opt-nodes).";
+      type = types.package;
+      default = hostPkgs.qemu_test;
+      defaultText = "hostPkgs.qemu_test";
+    };
+
+    enableOCR = mkOption {
+      description = mdDoc ''
+        Whether to enable Optical Character Recognition functionality for
+        testing graphical programs. See [Machine objects](`ssec-machine-objects`).
+      '';
+      type = types.bool;
+      default = false;
+    };
+
+    extraPythonPackages = mkOption {
+      description = mdDoc ''
+        Python packages to add to the test driver.
+
+        The argument is a Python package set, similar to `pkgs.pythonPackages`.
+      '';
+      example = lib.literalExpression ''
+        p: [ p.numpy ]
+      '';
+      type = types.functionTo (types.listOf types.package);
+      default = ps: [ ];
+    };
+
+    extraDriverArgs = mkOption {
+      description = mdDoc ''
+        Extra arguments to pass to the test driver.
+
+        They become part of [{option}`driver`](#test-opt-driver) via `wrapProgram`.
+      '';
+      type = types.listOf types.str;
+      default = [];
+    };
+
+    skipLint = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Do not run the linters. This may speed up your iteration cycle, but it is not something you should commit.
+      '';
+    };
+
+    skipTypeCheck = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Disable type checking. This must not be enabled for new NixOS tests.
+
+        This may speed up your iteration cycle, unless you're working on the [{option}`testScript`](#test-opt-testScript).
+      '';
+    };
+  };
+
+  config = {
+    _module.args.hostPkgs = config.hostPkgs;
+
+    driver = withChecks driver;
+
+    # make available on the test runner
+    passthru.driver = config.driver;
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/interactive.nix b/nixpkgs/nixos/lib/testing/interactive.nix
new file mode 100644
index 000000000000..317ed4241882
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/interactive.nix
@@ -0,0 +1,45 @@
+{ config, lib, moduleType, hostPkgs, ... }:
+let
+  inherit (lib) mkOption types mdDoc;
+in
+{
+  options = {
+    interactive = mkOption {
+      description = mdDoc ''
+        Tests [can be run interactively](#sec-running-nixos-tests-interactively)
+        using the program in the test derivation's `.driverInteractive` attribute.
+
+        When they are, the configuration will include anything set in this submodule.
+
+        You can set any top-level test option here.
+
+        Example test module:
+
+        ```nix
+        { config, lib, ... }: {
+
+          nodes.rabbitmq = {
+            services.rabbitmq.enable = true;
+          };
+
+          # When running interactively ...
+          interactive.nodes.rabbitmq = {
+            # ... enable the web ui.
+            services.rabbitmq.managementPlugin.enable = true;
+          };
+        }
+        ```
+
+        For details, see the section about [running tests interactively](#sec-running-nixos-tests-interactively).
+      '';
+      type = moduleType;
+      visible = "shallow";
+    };
+  };
+
+  config = {
+    interactive.qemu.package = hostPkgs.qemu;
+    interactive.extraDriverArgs = [ "--interactive" ];
+    passthru.driverInteractive = config.interactive.driver;
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/legacy.nix b/nixpkgs/nixos/lib/testing/legacy.nix
new file mode 100644
index 000000000000..b31057556601
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/legacy.nix
@@ -0,0 +1,26 @@
+{ config, options, lib, ... }:
+let
+  inherit (lib) mkIf mkOption types;
+in
+{
+  # This needs options.warnings and options.assertions, which we don't have (yet?).
+  # imports = [
+  #   (lib.mkRenamedOptionModule [ "machine" ] [ "nodes" "machine" ])
+  #   (lib.mkRemovedOptionModule [ "minimal" ] "The minimal kernel module was removed as it was broken and not used any more in nixpkgs.")
+  # ];
+
+  options = {
+    machine = mkOption {
+      internal = true;
+      type = types.raw;
+    };
+  };
+
+  config = {
+    nodes = mkIf options.machine.isDefined (
+      lib.warn
+        "In test `${config.name}': The `machine' attribute in NixOS tests (pkgs.nixosTest / make-test-python.nix / testing-python.nix / makeTest) is deprecated. Please set the equivalent `nodes.machine'."
+        { inherit (config) machine; }
+    );
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/meta.nix b/nixpkgs/nixos/lib/testing/meta.nix
new file mode 100644
index 000000000000..805b7520edff
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/meta.nix
@@ -0,0 +1,42 @@
+{ lib, ... }:
+let
+  inherit (lib) types mkOption mdDoc;
+in
+{
+  options = {
+    meta = lib.mkOption {
+      description = mdDoc ''
+        The [`meta`](https://nixos.org/manual/nixpkgs/stable/#chap-meta) attributes that will be set on the returned derivations.
+
+        Not all [`meta`](https://nixos.org/manual/nixpkgs/stable/#chap-meta) attributes are supported, but more can be added as desired.
+      '';
+      apply = lib.filterAttrs (k: v: v != null);
+      type = types.submodule {
+        options = {
+          maintainers = lib.mkOption {
+            type = types.listOf types.raw;
+            default = [];
+            description = mdDoc ''
+              The [list of maintainers](https://nixos.org/manual/nixpkgs/stable/#var-meta-maintainers) for this test.
+            '';
+          };
+          timeout = lib.mkOption {
+            type = types.nullOr types.int;
+            default = 3600;  # 1 hour
+            description = mdDoc ''
+              The [{option}`test`](#test-opt-test)'s [`meta.timeout`](https://nixos.org/manual/nixpkgs/stable/#var-meta-timeout) in seconds.
+            '';
+          };
+          broken = lib.mkOption {
+            type = types.bool;
+            default = false;
+            description = mdDoc ''
+              Sets the [`meta.broken`](https://nixos.org/manual/nixpkgs/stable/#var-meta-broken) attribute on the [{option}`test`](#test-opt-test) derivation.
+            '';
+          };
+        };
+      };
+      default = {};
+    };
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/name.nix b/nixpkgs/nixos/lib/testing/name.nix
new file mode 100644
index 000000000000..0af593169eec
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/name.nix
@@ -0,0 +1,14 @@
+{ lib, ... }:
+let
+  inherit (lib) mkOption types mdDoc;
+in
+{
+  options.name = mkOption {
+    description = mdDoc ''
+      The name of the test.
+
+      This is used in the derivation names of the [{option}`driver`](#test-opt-driver) and [{option}`test`](#test-opt-test) runner.
+    '';
+    type = types.str;
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/network.nix b/nixpkgs/nixos/lib/testing/network.nix
new file mode 100644
index 000000000000..1edc9e276530
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/network.nix
@@ -0,0 +1,131 @@
+{ lib, nodes, ... }:
+
+let
+  inherit (lib)
+    attrNames concatMap concatMapStrings flip forEach head
+    listToAttrs mkDefault mkOption nameValuePair optionalString
+    range toLower types zipListsWith zipLists
+    mdDoc
+    ;
+
+  nodeNumbers =
+    listToAttrs
+      (zipListsWith
+        nameValuePair
+        (attrNames nodes)
+        (range 1 254)
+      );
+
+  networkModule = { config, nodes, pkgs, ... }:
+    let
+      qemu-common = import ../qemu-common.nix { inherit lib pkgs; };
+
+      # Convert legacy VLANs to named interfaces and merge with explicit interfaces.
+      vlansNumbered = forEach (zipLists config.virtualisation.vlans (range 1 255)) (v: {
+        name = "eth${toString v.snd}";
+        vlan = v.fst;
+        assignIP = true;
+      });
+      explicitInterfaces = lib.mapAttrsToList (n: v: v // { name = n; }) config.virtualisation.interfaces;
+      interfaces = vlansNumbered ++ explicitInterfaces;
+      interfacesNumbered = zipLists interfaces (range 1 255);
+
+      # Automatically assign IP addresses to requested interfaces.
+      assignIPs = lib.filter (i: i.assignIP) interfaces;
+      ipInterfaces = forEach assignIPs (i:
+        nameValuePair i.name { ipv4.addresses =
+          [ { address = "192.168.${toString i.vlan}.${toString config.virtualisation.test.nodeNumber}";
+              prefixLength = 24;
+            }];
+        });
+
+      qemuOptions = lib.flatten (forEach interfacesNumbered ({ fst, snd }:
+        qemu-common.qemuNICFlags snd fst.vlan config.virtualisation.test.nodeNumber));
+      udevRules = forEach interfacesNumbered ({ fst, snd }:
+        # MAC Addresses for QEMU network devices are lowercase, and udev string comparison is case-sensitive.
+        ''SUBSYSTEM=="net",ACTION=="add",ATTR{address}=="${toLower(qemu-common.qemuNicMac fst.vlan config.virtualisation.test.nodeNumber)}",NAME="${fst.name}"'');
+
+      networkConfig =
+        {
+          networking.hostName = mkDefault config.virtualisation.test.nodeName;
+
+          networking.interfaces = listToAttrs ipInterfaces;
+
+          networking.primaryIPAddress =
+            optionalString (ipInterfaces != [ ]) (head (head ipInterfaces).value.ipv4.addresses).address;
+
+          # Put the IP addresses of all VMs in this machine's
+          # /etc/hosts file.  If a machine has multiple
+          # interfaces, use the IP address corresponding to
+          # the first interface (i.e. the first network in its
+          # virtualisation.vlans option).
+          networking.extraHosts = flip concatMapStrings (attrNames nodes)
+            (m':
+              let config = nodes.${m'}; in
+              optionalString (config.networking.primaryIPAddress != "")
+                ("${config.networking.primaryIPAddress} " +
+                  optionalString (config.networking.domain != null)
+                    "${config.networking.hostName}.${config.networking.domain} " +
+                  "${config.networking.hostName}\n"));
+
+          virtualisation.qemu.options = qemuOptions;
+          boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules;
+        };
+
+    in
+    {
+      key = "network-interfaces";
+      config = networkConfig // {
+        # Expose the networkConfig items for tests like nixops
+        # that need to recreate the network config.
+        system.build.networkConfig = networkConfig;
+      };
+    };
+
+  nodeNumberModule = (regular@{ config, name, ... }: {
+    options = {
+      virtualisation.test.nodeName = mkOption {
+        internal = true;
+        default = name;
+        # We need to force this in specilisations, otherwise it'd be
+        # readOnly = true;
+        description = mdDoc ''
+          The `name` in `nodes.<name>`; stable across `specialisations`.
+        '';
+      };
+      virtualisation.test.nodeNumber = mkOption {
+        internal = true;
+        type = types.int;
+        readOnly = true;
+        default = nodeNumbers.${config.virtualisation.test.nodeName};
+        description = mdDoc ''
+          A unique number assigned for each node in `nodes`.
+        '';
+      };
+
+      # specialisations override the `name` module argument,
+      # so we push the real `virtualisation.test.nodeName`.
+      specialisation = mkOption {
+        type = types.attrsOf (types.submodule {
+          options.configuration = mkOption {
+            type = types.submoduleWith {
+              modules = [
+                {
+                  config.virtualisation.test.nodeName =
+                    # assert regular.config.virtualisation.test.nodeName != "configuration";
+                    regular.config.virtualisation.test.nodeName;
+                }
+              ];
+            };
+          };
+        });
+      };
+    };
+  });
+
+in
+{
+  config = {
+    extraBaseModules = { imports = [ networkModule nodeNumberModule ]; };
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/nixos-test-base.nix b/nixpkgs/nixos/lib/testing/nixos-test-base.nix
new file mode 100644
index 000000000000..59e6e3843367
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/nixos-test-base.nix
@@ -0,0 +1,23 @@
+# A module containing the base imports and overrides that
+# are always applied in NixOS VM tests, unconditionally,
+# even in `inheritParentConfig = false` specialisations.
+{ lib, ... }:
+let
+  inherit (lib) mkForce;
+in
+{
+  imports = [
+    ../../modules/virtualisation/qemu-vm.nix
+    ../../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
+    { key = "no-manual"; documentation.nixos.enable = false; }
+    {
+      key = "no-revision";
+      # Make the revision metadata constant, in order to avoid needless retesting.
+      # The human version (e.g. 21.05-pre) is left as is, because it is useful
+      # for external modules that test with e.g. testers.nixosTest and rely on that
+      # version number.
+      config.system.nixos.revision = mkForce "constant-nixos-revision";
+    }
+
+  ];
+}
diff --git a/nixpkgs/nixos/lib/testing/nodes.nix b/nixpkgs/nixos/lib/testing/nodes.nix
new file mode 100644
index 000000000000..6e439fd814db
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/nodes.nix
@@ -0,0 +1,149 @@
+testModuleArgs@{ config, lib, hostPkgs, nodes, ... }:
+
+let
+  inherit (lib)
+    literalExpression
+    literalMD
+    mapAttrs
+    mdDoc
+    mkDefault
+    mkIf
+    mkOption mkForce
+    optional
+    optionalAttrs
+    types
+    ;
+
+  baseOS =
+    import ../eval-config.nix {
+      system = null; # use modularly defined system
+      inherit (config.node) specialArgs;
+      modules = [ config.defaults ];
+      baseModules = (import ../../modules/module-list.nix) ++
+        [
+          ./nixos-test-base.nix
+          { key = "nodes"; _module.args.nodes = config.nodesCompat; }
+          ({ config, ... }:
+            {
+              virtualisation.qemu.package = testModuleArgs.config.qemu.package;
+            })
+          (optionalAttrs (!config.node.pkgsReadOnly) {
+            key = "nodes.nix-pkgs";
+            config = {
+              # 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;
+              # TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates.
+              nixpkgs.system = hostPkgs.stdenv.hostPlatform.system;
+            };
+          })
+          testModuleArgs.config.extraBaseModules
+        ];
+    };
+
+
+in
+
+{
+
+  options = {
+    node.type = mkOption {
+      type = types.raw;
+      default = baseOS.type;
+      internal = true;
+    };
+
+    nodes = mkOption {
+      type = types.lazyAttrsOf config.node.type;
+      visible = "shallow";
+      description = mdDoc ''
+        An attribute set of NixOS configuration modules.
+
+        The configurations are augmented by the [`defaults`](#test-opt-defaults) option.
+
+        They are assigned network addresses according to the `nixos/lib/testing/network.nix` module.
+
+        A few special options are available, that aren't in a plain NixOS configuration. See [Configuring the nodes](#sec-nixos-test-nodes)
+      '';
+    };
+
+    defaults = mkOption {
+      description = mdDoc ''
+        NixOS configuration that is applied to all [{option}`nodes`](#test-opt-nodes).
+      '';
+      type = types.deferredModule;
+      default = { };
+    };
+
+    extraBaseModules = mkOption {
+      description = mdDoc ''
+        NixOS configuration that, like [{option}`defaults`](#test-opt-defaults), is applied to all [{option}`nodes`](#test-opt-nodes) and can not be undone with [`specialisation.<name>.inheritParentConfig`](https://search.nixos.org/options?show=specialisation.%3Cname%3E.inheritParentConfig&from=0&size=50&sort=relevance&type=packages&query=specialisation).
+      '';
+      type = types.deferredModule;
+      default = { };
+    };
+
+    node.pkgs = mkOption {
+      description = mdDoc ''
+        The Nixpkgs to use for the nodes.
+
+        Setting this will make the `nixpkgs.*` options read-only, to avoid mistakenly testing with a Nixpkgs configuration that diverges from regular use.
+      '';
+      type = types.nullOr types.pkgs;
+      default = null;
+      defaultText = literalMD ''
+        `null`, so construct `pkgs` according to the `nixpkgs.*` options as usual.
+      '';
+    };
+
+    node.pkgsReadOnly = mkOption {
+      description = mdDoc ''
+        Whether to make the `nixpkgs.*` options read-only. This is only relevant when [`node.pkgs`](#test-opt-node.pkgs) is set.
+
+        Set this to `false` when any of the [`nodes`](#test-opt-nodes) needs to configure any of the `nixpkgs.*` options. This will slow down evaluation of your test a bit.
+      '';
+      type = types.bool;
+      default = config.node.pkgs != null;
+      defaultText = literalExpression ''node.pkgs != null'';
+    };
+
+    node.specialArgs = mkOption {
+      type = types.lazyAttrsOf types.raw;
+      default = { };
+      description = mdDoc ''
+        An attribute set of arbitrary values that will be made available as module arguments during the resolution of module `imports`.
+
+        Note that it is not possible to override these from within the NixOS configurations. If you argument is not relevant to `imports`, consider setting {option}`defaults._module.args.<name>` instead.
+      '';
+    };
+
+    nodesCompat = mkOption {
+      internal = true;
+      description = mdDoc ''
+        Basically `_module.args.nodes`, but with backcompat and warnings added.
+
+        This will go away.
+      '';
+    };
+  };
+
+  config = {
+    _module.args.nodes = config.nodesCompat;
+    nodesCompat =
+      mapAttrs
+        (name: config: config // {
+          config = lib.warnIf (lib.isInOldestRelease 2211)
+            "Module argument `nodes.${name}.config` is deprecated. Use `nodes.${name}` instead."
+            config;
+        })
+        config.nodes;
+
+    passthru.nodes = config.nodesCompat;
+
+    defaults = mkIf config.node.pkgsReadOnly {
+      nixpkgs.pkgs = config.node.pkgs;
+      imports = [ ../../modules/misc/nixpkgs/read-only.nix ];
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/pkgs.nix b/nixpkgs/nixos/lib/testing/pkgs.nix
new file mode 100644
index 000000000000..22dd586868e3
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/pkgs.nix
@@ -0,0 +1,11 @@
+{ config, lib, hostPkgs, ... }:
+{
+  config = {
+    # default pkgs for use in VMs
+    _module.args.pkgs = hostPkgs;
+
+    defaults = {
+      # TODO: a module to set a shared pkgs, if options.nixpkgs.* is untouched by user (highestPrio) */
+    };
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/run.nix b/nixpkgs/nixos/lib/testing/run.nix
new file mode 100644
index 000000000000..0cd07d8afd21
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/run.nix
@@ -0,0 +1,57 @@
+{ config, hostPkgs, lib, ... }:
+let
+  inherit (lib) types mkOption mdDoc;
+in
+{
+  options = {
+    passthru = mkOption {
+      type = types.lazyAttrsOf types.raw;
+      description = mdDoc ''
+        Attributes to add to the returned derivations,
+        which are not necessarily part of the build.
+
+        This is a bit like doing `drv // { myAttr = true; }` (which would be lost by `overrideAttrs`).
+        It does not change the actual derivation, but adds the attribute nonetheless, so that
+        consumers of what would be `drv` have more information.
+      '';
+    };
+
+    test = mkOption {
+      type = types.package;
+      # TODO: can the interactive driver be configured to access the network?
+      description = mdDoc ''
+        Derivation that runs the test as its "build" process.
+
+        This implies that NixOS tests run isolated from the network, making them
+        more dependable.
+      '';
+    };
+  };
+
+  config = {
+    test = lib.lazyDerivation { # lazyDerivation improves performance when only passthru items and/or meta are used.
+      derivation = hostPkgs.stdenv.mkDerivation {
+        name = "vm-test-run-${config.name}";
+
+        requiredSystemFeatures = [ "kvm" "nixos-test" ];
+
+        buildCommand = ''
+          mkdir -p $out
+
+          # effectively mute the XMLLogger
+          export LOGFILE=/dev/null
+
+          ${config.driver}/bin/nixos-test-driver -o $out
+        '';
+
+        passthru = config.passthru;
+
+        meta = config.meta;
+      };
+      inherit (config) passthru meta;
+    };
+
+    # useful for inspection (debugging / exploration)
+    passthru.config = config;
+  };
+}
diff --git a/nixpkgs/nixos/lib/testing/testScript.nix b/nixpkgs/nixos/lib/testing/testScript.nix
new file mode 100644
index 000000000000..5c36d754d79d
--- /dev/null
+++ b/nixpkgs/nixos/lib/testing/testScript.nix
@@ -0,0 +1,84 @@
+testModuleArgs@{ config, lib, hostPkgs, nodes, moduleType, ... }:
+let
+  inherit (lib) mkOption types mdDoc;
+  inherit (types) either str functionTo;
+in
+{
+  options = {
+    testScript = mkOption {
+      type = either str (functionTo str);
+      description = mdDoc ''
+        A series of python declarations and statements that you write to perform
+        the test.
+      '';
+    };
+    testScriptString = mkOption {
+      type = str;
+      readOnly = true;
+      internal = true;
+    };
+
+    includeTestScriptReferences = mkOption {
+      type = types.bool;
+      default = true;
+      internal = true;
+    };
+    withoutTestScriptReferences = mkOption {
+      type = moduleType;
+      description = mdDoc ''
+        A parallel universe where the testScript is invalid and has no references.
+      '';
+      internal = true;
+      visible = false;
+    };
+  };
+  config = {
+    withoutTestScriptReferences.includeTestScriptReferences = false;
+    withoutTestScriptReferences.testScript = lib.mkForce "testscript omitted";
+
+    testScriptString =
+      if lib.isFunction config.testScript
+      then
+        config.testScript
+          {
+            nodes =
+              lib.mapAttrs
+                (k: v:
+                  if v.virtualisation.useNixStoreImage
+                  then
+                  # prevent infinite recursion when testScript would
+                  # reference v's toplevel
+                    config.withoutTestScriptReferences.nodesCompat.${k}
+                  else
+                  # reuse memoized config
+                    v
+                )
+                config.nodesCompat;
+          }
+      else config.testScript;
+
+    defaults = { config, name, ... }: {
+      # 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 testModuleArgs.config.testScriptString && testModuleArgs.config.includeTestScriptReferences)
+          (hostPkgs.writeStringReferencesToFile testModuleArgs.config.testScriptString);
+    };
+  };
+}
diff --git a/nixpkgs/nixos/lib/utils.nix b/nixpkgs/nixos/lib/utils.nix
index d7671a374999..def3aa13f320 100644
--- a/nixpkgs/nixos/lib/utils.nix
+++ b/nixpkgs/nixos/lib/utils.nix
@@ -39,11 +39,19 @@ rec {
     || hasPrefix a'.mountPoint b'.mountPoint
     || any (hasPrefix a'.mountPoint) b'.depends;
 
-  # Escape a path according to the systemd rules, e.g. /dev/xyzzy
-  # becomes dev-xyzzy.  FIXME: slow.
-  escapeSystemdPath = s:
-   replaceChars ["/" "-" " "] ["-" "\\x2d" "\\x20"]
-   (removePrefix "/" s);
+  # Escape a path according to the systemd rules. FIXME: slow
+  # The rules are described in systemd.unit(5) as follows:
+  # The escaping algorithm operates as follows: given a string, any "/" character is replaced by "-", and all other characters which are not ASCII alphanumerics, ":", "_" or "." are replaced by C-style "\x2d" escapes. In addition, "." is replaced with such a C-style escape when it would appear as the first character in the escaped string.
+  # When the input qualifies as absolute file system path, this algorithm is extended slightly: the path to the root directory "/" is encoded as single dash "-". In addition, any leading, trailing or duplicate "/" characters are removed from the string before transformation. Example: /foo//bar/baz/ becomes "foo-bar-baz".
+  escapeSystemdPath = s: let
+    replacePrefix = p: r: s: (if (hasPrefix p s) then r + (removePrefix p s) else s);
+    trim = s: removeSuffix "/" (removePrefix "/" s);
+    normalizedPath = strings.normalizePath s;
+  in
+    replaceStrings ["/"] ["-"]
+    (replacePrefix "." (strings.escapeC ["."] ".")
+    (strings.escapeC (stringToCharacters " !\"#$%&'()*+,;<=>=@[\\]^`{|}~-")
+    (if normalizedPath == "/" then normalizedPath else trim normalizedPath)));
 
   # Quotes an argument for use in Exec* service lines.
   # systemd accepts "-quoted strings with escape sequences, toJSON produces
@@ -59,7 +67,7 @@ rec {
         else if builtins.isInt arg || builtins.isFloat arg then toString arg
         else throw "escapeSystemdExecArg only allows strings, paths and numbers";
     in
-      replaceChars [ "%" "$" ] [ "%%" "$$" ] (builtins.toJSON s);
+      replaceStrings [ "%" "$" ] [ "%%" "$$" ] (builtins.toJSON s);
 
   # Quotes a list of arguments into a single string for use in a Exec*
   # line.
@@ -102,7 +110,11 @@ rec {
         if item ? ${attr} then
           nameValuePair prefix item.${attr}
         else if isAttrs item then
-          map (name: recurse (prefix + "." + name) item.${name}) (attrNames item)
+          map (name:
+            let
+              escapedName = ''"${replaceStrings [''"'' "\\"] [''\"'' "\\\\"] name}"'';
+            in
+              recurse (prefix + "." + escapedName) item.${name}) (attrNames item)
         else if isList item then
           imap0 (index: item: recurse (prefix + "[${toString index}]") item) item
         else
@@ -182,13 +194,13 @@ rec {
                 '')
                (attrNames secrets))
     + "\n"
-    + "${pkgs.jq}/bin/jq >'${output}' '"
-    + concatStringsSep
+    + "${pkgs.jq}/bin/jq >'${output}' "
+    + lib.escapeShellArg (concatStringsSep
       " | "
       (imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
-             (attrNames secrets))
+             (attrNames secrets)))
     + ''
-      ' <<'EOF'
+       <<'EOF'
       ${builtins.toJSON set}
       EOF
       (( ! $inherit_errexit_enabled )) && shopt -u inherit_errexit
diff --git a/nixpkgs/nixos/maintainers/scripts/cloudstack/cloudstack-image.nix b/nixpkgs/nixos/maintainers/scripts/cloudstack/cloudstack-image.nix
index 005f75476e9a..b66ee5d7b9bc 100644
--- a/nixpkgs/nixos/maintainers/scripts/cloudstack/cloudstack-image.nix
+++ b/nixpkgs/nixos/maintainers/scripts/cloudstack/cloudstack-image.nix
@@ -2,8 +2,6 @@
 
 { config, lib, pkgs, ... }:
 
-with lib;
-
 {
   imports =
     [ ../../../modules/virtualisation/cloudstack-config.nix ];
diff --git a/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix
index 2d89db0a7f34..d12339bca1f8 100644
--- a/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -10,7 +10,7 @@ in {
 
   imports = [ ../../../modules/virtualisation/amazon-image.nix ];
 
-  # Amazon recomments setting this to the highest possible value for a good EBS
+  # Amazon recommends setting this to the highest possible value for a good EBS
   # experience, which prior to 4.15 was 255.
   # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes
   config.boot.kernelParams =
@@ -23,7 +23,7 @@ in {
   options.amazonImage = {
     name = mkOption {
       type = types.str;
-      description = "The name of the generated derivation";
+      description = lib.mdDoc "The name of the generated derivation";
       default = "nixos-amazon-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
     };
 
@@ -35,7 +35,7 @@ in {
         ]
       '';
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         This option lists files to be copied to fixed locations in the
         generated image. Glob patterns work.
       '';
@@ -43,15 +43,15 @@ in {
 
     sizeMB = mkOption {
       type = with types; either (enum [ "auto" ]) int;
-      default = if config.ec2.hvm then 2048 else 8192;
+      default = 3072;
       example = 8192;
-      description = "The size in MB of the image";
+      description = lib.mdDoc "The size in MB of the image";
     };
 
     format = mkOption {
       type = types.enum [ "raw" "qcow2" "vpc" ];
       default = "vpc";
-      description = "The image format to output";
+      description = lib.mdDoc "The image format to output";
     };
   };
 
@@ -60,9 +60,6 @@ in {
       ''
         { modulesPath, ... }: {
           imports = [ "''${modulesPath}/virtualisation/amazon-image.nix" ];
-          ${optionalString config.ec2.hvm ''
-            ec2.hvm = true;
-          ''}
           ${optionalString config.ec2.efi ''
             ec2.efi = true;
           ''}
@@ -105,8 +102,8 @@ in {
        ${pkgs.jq}/bin/jq -n \
          --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
          --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-         --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$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 root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
          --arg boot_mode "${amiBootMode}" \
          --arg root "$rootDisk" \
          --arg boot "$bootDisk" \
@@ -129,9 +126,7 @@ in {
       pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
 
       fsType = "ext4";
-      partitionTableType = if config.ec2.efi then "efi"
-                           else if config.ec2.hvm then "legacy+gpt"
-                           else "none";
+      partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt";
 
       diskSize = cfg.sizeMB;
 
@@ -147,7 +142,7 @@ in {
        ${pkgs.jq}/bin/jq -n \
          --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 logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
          --arg boot_mode "${amiBootMode}" \
          --arg file "$diskImage" \
           '{}
diff --git a/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix b/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
index ead3d4e99401..c1a9b1aacd18 100644
--- a/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
+++ b/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
@@ -4,8 +4,6 @@
 
 { config, pkgs, lib, ... }:
 
-with lib;
-
 {
   imports =
     [ # Include the default lxd configuration.
@@ -89,14 +87,9 @@ with lib;
 
   # 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
+  # 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
index 6aa3f2f55847..07605c5c3120 100644
--- a/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image.nix
+++ b/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image.nix
@@ -1,7 +1,5 @@
 { lib, config, pkgs, ... }:
 
-with lib;
-
 {
   imports = [
     ../../../modules/virtualisation/lxc-container.nix
@@ -26,9 +24,4 @@ with lib;
   # Network
   networking.useDHCP = false;
   networking.interfaces.eth0.useDHCP = true;
-
-  # As this is intended as a standalone 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
index 307258ddc628..25ae1bc399f2 100644
--- a/nixpkgs/nixos/maintainers/scripts/lxd/nix.tpl
+++ b/nixpkgs/nixos/maintainers/scripts/lxd/nix.tpl
@@ -1,7 +1,5 @@
 { lib, config, pkgs, ... }:
 
-with lib;
-
 # WARNING: THIS CONFIGURATION IS AUTOGENERATED AND WILL BE OVERWRITTEN AUTOMATICALLY
 
 {
diff --git a/nixpkgs/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix b/nixpkgs/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
index d62a560642d0..936dcee12949 100644
--- a/nixpkgs/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
+++ b/nixpkgs/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
@@ -16,20 +16,20 @@ in
   options.openstackImage = {
     name = mkOption {
       type = types.str;
-      description = "The name of the generated derivation";
+      description = lib.mdDoc "The name of the generated derivation";
       default = "nixos-openstack-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
     };
 
     sizeMB = mkOption {
       type = types.int;
       default = 8192;
-      description = "The size in MB of the image";
+      description = lib.mdDoc "The size in MB of the image";
     };
 
     format = mkOption {
       type = types.enum [ "raw" "qcow2" ];
       default = "qcow2";
-      description = "The image format to output";
+      description = lib.mdDoc "The image format to output";
     };
   };
 
@@ -85,7 +85,7 @@ in
         ${pkgs.jq}/bin/jq -n \
           --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
           --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-          --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+          --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
           --arg boot_mode "${imageBootMode}" \
           --arg root "$rootDisk" \
          '{}
diff --git a/nixpkgs/nixos/modules/config/console.nix b/nixpkgs/nixos/modules/config/console.nix
index 493411d61836..454403cefbd1 100644
--- a/nixpkgs/nixos/modules/config/console.nix
+++ b/nixpkgs/nixos/modules/config/console.nix
@@ -30,7 +30,7 @@ let
   # Sadly, systemd-vconsole-setup doesn't support binary keymaps.
   vconsoleConf = pkgs.writeText "vconsole.conf" ''
     KEYMAP=${cfg.keyMap}
-    FONT=${cfg.font}
+    ${optionalString (cfg.font != null) "FONT=${cfg.font}"}
   '';
 
   consoleEnv = kbd: pkgs.buildEnv {
@@ -43,23 +43,30 @@ let
       "/share/unimaps"
     ];
   };
-
-  setVconsole = !config.boot.isContainer;
 in
 
 {
   ###### interface
 
   options.console  = {
+    enable = mkEnableOption (lib.mdDoc "virtual console") // {
+      default = true;
+    };
+
     font = mkOption {
-      type = with types; either str path;
-      default = "Lat2-Terminus16";
+      type = with types; nullOr (either str path);
+      default = null;
       example = "LatArCyrHeb-16";
       description = mdDoc ''
-        The font used for the virtual consoles.  Leave empty to use
-        whatever the {command}`setfont` program considers the
-        default font.
-        Can be either a font name or a path to a PSF font file.
+        The font used for the virtual consoles.
+        Can be `null`, a font name, or a path to a PSF font file.
+
+        Use `null` to let the kernel choose a built-in font.
+        The default is 8x16, and, as of Linux 5.3, Terminus 32 bold for display
+        resolutions of 2560x1080 and higher.
+        These fonts cover the [IBM437][] character set.
+
+        [IBM437]: https://en.wikipedia.org/wiki/Code_page_437
       '';
     };
 
@@ -73,8 +80,8 @@ in
     };
 
     colors = mkOption {
-      type = types.listOf types.str;
-      default = [];
+      type = with types; listOf (strMatching "[[:xdigit:]]{6}");
+      default = [ ];
       example = [
         "002b36" "dc322f" "859900" "b58900"
         "268bd2" "d33682" "2aa198" "eee8d5"
@@ -134,11 +141,17 @@ in
           '');
     }
 
-    (mkIf (!setVconsole) {
-      systemd.services.systemd-vconsole-setup.enable = false;
+    (mkIf (!cfg.enable) {
+      systemd.services = {
+        "serial-getty@ttyS0".enable = false;
+        "serial-getty@hvc0".enable = false;
+        "getty@tty1".enable = false;
+        "autovt@".enable = false;
+        systemd-vconsole-setup.enable = false;
+      };
     })
 
-    (mkIf setVconsole (mkMerge [
+    (mkIf cfg.enable (mkMerge [
       { environment.systemPackages = with pkgs; [ kbd locale ];
 
         # Let systemd-vconsole-setup.service do the work of setting up the
@@ -157,7 +170,7 @@ in
           fi
           loadkmap < ${optimizedKeymap}
 
-          ${optionalString cfg.earlySetup ''
+          ${optionalString (cfg.earlySetup && cfg.font != null) ''
             setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
           ''}
         '');
@@ -174,7 +187,7 @@ in
           "${config.boot.initrd.systemd.package.kbd}/bin/setfont"
           "${config.boot.initrd.systemd.package.kbd}/bin/loadkeys"
           "${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # Fonts and keyboard layouts are compressed
-        ] ++ optionals (hasPrefix builtins.storeDir cfg.font) [
+        ] ++ optionals (cfg.font != null && hasPrefix builtins.storeDir cfg.font) [
           "${cfg.font}"
         ] ++ optionals (hasPrefix builtins.storeDir cfg.keyMap) [
           "${cfg.keyMap}"
@@ -201,7 +214,7 @@ in
         ];
       })
 
-      (mkIf (cfg.earlySetup && !config.boot.initrd.systemd.enable) {
+      (mkIf (cfg.earlySetup && cfg.font != null && !config.boot.initrd.systemd.enable) {
         boot.initrd.extraUtilsCommands = ''
           mkdir -p $out/share/consolefonts
           ${if substring 0 1 cfg.font == "/" then ''
diff --git a/nixpkgs/nixos/modules/config/fonts/fontconfig.nix b/nixpkgs/nixos/modules/config/fonts/fontconfig.nix
index f86c0387e91d..5781679241ef 100644
--- a/nixpkgs/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixpkgs/nixos/modules/config/fonts/fontconfig.nix
@@ -7,6 +7,19 @@ This module generates a package containing configuration files and link it in /e
 Fontconfig reads files in folder name / file name order, so the number prepended to the configuration file name decide the order of parsing.
 Low number means high priority.
 
+NOTE: Please take extreme care when adjusting the default settings of this module.
+People care a lot, and I mean A LOT, about their font rendering, and you will be
+The Person That Broke It if it changes in a way people don't like.
+
+See prior art:
+- https://github.com/NixOS/nixpkgs/pull/194594
+- https://github.com/NixOS/nixpkgs/pull/222236
+- https://github.com/NixOS/nixpkgs/pull/222689
+
+And do not repeat our mistakes.
+
+- @K900, March 2023
+
 */
 
 { config, pkgs, lib, ... }:
@@ -218,6 +231,8 @@ let
     paths = cfg.confPackages;
     ignoreCollisions = true;
   };
+
+  fontconfigNote = "Consider manually configuring fonts.fontconfig according to personal preference.";
 in
 {
   imports = [
@@ -229,6 +244,8 @@ in
     (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "dpi" ] "Use display server-specific options")
+    (mkRemovedOptionModule [ "hardware" "video" "hidpi" "enable" ] fontconfigNote)
+    (mkRemovedOptionModule [ "fonts" "optimizeForVeryHighDPI" ] fontconfigNote)
   ] ++ lib.forEach [ "enable" "substitutions" "preset" ]
      (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
        The fonts.fontconfig.ultimate module and configuration is obsolete.
@@ -259,7 +276,7 @@ in
           internal = true;
           type     = with types; listOf path;
           default  = [ ];
-          description = ''
+          description = lib.mdDoc ''
             Fontconfig configuration packages.
           '';
         };
diff --git a/nixpkgs/nixos/modules/config/fonts/fonts.nix b/nixpkgs/nixos/modules/config/fonts/fonts.nix
index c0619fa31a32..87cf837e7c80 100644
--- a/nixpkgs/nixos/modules/config/fonts/fonts.nix
+++ b/nixpkgs/nixos/modules/config/fonts/fonts.nix
@@ -3,29 +3,7 @@
 with lib;
 
 let
-  # A scalable variant of the X11 "core" cursor
-  #
-  # If not running a fancy desktop environment, the cursor is likely set to
-  # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very
-  # small and almost invisible on 4K displays.
-  fontcursormisc_hidpi = pkgs.xorg.fontxfree86type1.overrideAttrs (old:
-    let
-      # The scaling constant is 230/96: the scalable `left_ptr` glyph at
-      # about 23 points is rendered as 17px, on a 96dpi display.
-      # Note: the XLFD font size is in decipoints.
-      size = 2.39583 * config.services.xserver.dpi;
-      sizeString = builtins.head (builtins.split "\\." (toString size));
-    in
-    {
-      postInstall = ''
-        alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific'
-        echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias
-      '';
-    });
-
-  hasHidpi =
-    config.hardware.video.hidpi.enable &&
-    config.services.xserver.dpi != null;
+  cfg = config.fonts;
 
   defaultFonts =
     [ pkgs.dejavu_fonts
@@ -35,14 +13,7 @@ let
       pkgs.unifont
       pkgs.noto-fonts-emoji
     ];
-
-  defaultXFonts =
-    [ (if hasHidpi then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc)
-      pkgs.xorg.fontmiscmisc
-    ];
-
 in
-
 {
   imports = [
     (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
@@ -68,14 +39,9 @@ in
           and families and reasonable coverage of Unicode.
         '';
       };
-
     };
 
   };
 
-  config = mkMerge [
-    { fonts.fonts = mkIf config.fonts.enableDefaultFonts defaultFonts; }
-    { fonts.fonts = mkIf config.services.xserver.enable defaultXFonts; }
-  ];
-
+  config = { fonts.fonts = mkIf cfg.enableDefaultFonts defaultFonts; };
 }
diff --git a/nixpkgs/nixos/modules/config/gnu.nix b/nixpkgs/nixos/modules/config/gnu.nix
index d06b479e2af5..a47d299b226b 100644
--- a/nixpkgs/nixos/modules/config/gnu.nix
+++ b/nixpkgs/nixos/modules/config/gnu.nix
@@ -29,7 +29,6 @@
 
     # GNU GRUB, where available.
     boot.loader.grub.enable = !pkgs.stdenv.isAarch32;
-    boot.loader.grub.version = 2;
 
     # GNU lsh.
     services.openssh.enable = false;
diff --git a/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
index 87d5483e36ab..62f0cc3f090f 100644
--- a/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
+++ b/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
@@ -52,10 +52,8 @@ with lib;
 
     environment.extraSetup = ''
       # For each icon theme directory ...
-
-      find $out/share/icons -mindepth 1 -maxdepth 1 -print0 | while read -d $'\0' themedir
+      find $out/share/icons -exec test -d {} ';' -mindepth 1 -maxdepth 1 -print0 | while read -d $'\0' themedir
       do
-
         # In order to build the cache, the theme dir should be
         # writable. When the theme dir is a symbolic link to somewhere
         # in the nix store it is not writable and it means that only
diff --git a/nixpkgs/nixos/modules/config/i18n.nix b/nixpkgs/nixos/modules/config/i18n.nix
index f73f10f0f369..b1efc00773dc 100644
--- a/nixpkgs/nixos/modules/config/i18n.nix
+++ b/nixpkgs/nixos/modules/config/i18n.nix
@@ -10,12 +10,12 @@ with lib;
     i18n = {
       glibcLocales = mkOption {
         type = types.path;
-        default = pkgs.buildPackages.glibcLocales.override {
+        default = pkgs.glibcLocales.override {
           allLocales = any (x: x == "all") config.i18n.supportedLocales;
           locales = config.i18n.supportedLocales;
         };
         defaultText = literalExpression ''
-          pkgs.buildPackages.glibcLocales.override {
+          pkgs.glibcLocales.override {
             allLocales = any (x: x == "all") config.i18n.supportedLocales;
             locales = config.i18n.supportedLocales;
           }
diff --git a/nixpkgs/nixos/modules/config/iproute2.nix b/nixpkgs/nixos/modules/config/iproute2.nix
index 2e059e281701..8f49e7dbf7de 100644
--- a/nixpkgs/nixos/modules/config/iproute2.nix
+++ b/nixpkgs/nixos/modules/config/iproute2.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.networking.iproute2 = {
-    enable = mkEnableOption "copy IP route configuration files";
+    enable = mkEnableOption (lib.mdDoc "copy IP route configuration files");
     rttablesExtraConfig = mkOption {
       type = types.lines;
       default = "";
diff --git a/nixpkgs/nixos/modules/config/krb5/default.nix b/nixpkgs/nixos/modules/config/krb5/default.nix
index 6cc30c47b7de..df7a3f48236f 100644
--- a/nixpkgs/nixos/modules/config/krb5/default.nix
+++ b/nixpkgs/nixos/modules/config/krb5/default.nix
@@ -78,12 +78,12 @@ in {
 
   options = {
     krb5 = {
-      enable = mkEnableOption "building krb5.conf, configuration file for Kerberos V";
+      enable = mkEnableOption (lib.mdDoc "building krb5.conf, configuration file for Kerberos V");
 
       kerberos = mkOption {
         type = types.package;
-        default = pkgs.krb5Full;
-        defaultText = literalExpression "pkgs.krb5Full";
+        default = pkgs.krb5;
+        defaultText = literalExpression "pkgs.krb5";
         example = literalExpression "pkgs.heimdal";
         description = lib.mdDoc ''
           The Kerberos implementation that will be present in
@@ -204,11 +204,11 @@ in {
             admin_server = SYSLOG:NOTICE
             default      = SYSLOG:NOTICE
         '';
-        description = ''
-          These lines go to the end of <literal>krb5.conf</literal> verbatim.
-          <literal>krb5.conf</literal> may include any of the relations that are
-          valid for <literal>kdc.conf</literal> (see <literal>man
-          kdc.conf</literal>), but it is not a recommended practice.
+        description = lib.mdDoc ''
+          These lines go to the end of `krb5.conf` verbatim.
+          `krb5.conf` may include any of the relations that are
+          valid for `kdc.conf` (see `man kdc.conf`),
+          but it is not a recommended practice.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/config/ldap.nix b/nixpkgs/nixos/modules/config/ldap.nix
index 0f54e4a8cf05..d2f01fb87d32 100644
--- a/nixpkgs/nixos/modules/config/ldap.nix
+++ b/nixpkgs/nixos/modules/config/ldap.nix
@@ -59,7 +59,7 @@ in
 
     users.ldap = {
 
-      enable = mkEnableOption "authentication against an LDAP server";
+      enable = mkEnableOption (lib.mdDoc "authentication against an LDAP server");
 
       loginPam = mkOption {
         type = types.bool;
@@ -186,16 +186,16 @@ in
         policy = mkOption {
           default = "hard_open";
           type = types.enum [ "hard_open" "hard_init" "soft" ];
-          description = ''
+          description = lib.mdDoc ''
             Specifies the policy to use for reconnecting to an unavailable
-            LDAP server. The default is <literal>hard_open</literal>, which
+            LDAP server. The default is `hard_open`, which
             reconnects if opening the connection to the directory server
-            failed. By contrast, <literal>hard_init</literal> reconnects if
+            failed. By contrast, `hard_init` reconnects if
             initializing the connection failed. Initializing may not
             actually contact the directory server, and it is possible that
             a malformed configuration file will trigger reconnection. If
-            <literal>soft</literal> is specified, then
-            <package>nss_ldap</package> will return immediately on server
+            `soft` is specified, then
+            `nss_ldap` will return immediately on server
             failure. All hard reconnect policies block with exponential
             backoff before retrying.
           '';
diff --git a/nixpkgs/nixos/modules/config/malloc.nix b/nixpkgs/nixos/modules/config/malloc.nix
index a3fed33afa18..ae0661f472f6 100644
--- a/nixpkgs/nixos/modules/config/malloc.nix
+++ b/nixpkgs/nixos/modules/config/malloc.nix
@@ -30,7 +30,7 @@ let
 
       systemPlatform = platformMap.${pkgs.stdenv.hostPlatform.system} or (throw "scudo not supported on ${pkgs.stdenv.hostPlatform.system}");
     in {
-      libPath = "${pkgs.llvmPackages_latest.compiler-rt}/lib/linux/libclang_rt.scudo-${systemPlatform}.so";
+      libPath = "${pkgs.llvmPackages_14.compiler-rt}/lib/linux/libclang_rt.scudo-${systemPlatform}.so";
       description = ''
         A user-mode allocator based on LLVM Sanitizer’s CombinedAllocator,
         which aims at providing additional mitigations against heap based
@@ -77,29 +77,27 @@ in
     environment.memoryAllocator.provider = mkOption {
       type = types.enum ([ "libc" ] ++ attrNames providers);
       default = "libc";
-      description = ''
+      description = lib.mdDoc ''
         The system-wide memory allocator.
 
         Briefly, the system-wide memory allocator providers are:
-        <itemizedlist>
-        <listitem><para><literal>libc</literal>: the standard allocator provided by libc</para></listitem>
-        ${toString (mapAttrsToList
-            (name: value: "<listitem><para><literal>${name}</literal>: ${value.description}</para></listitem>")
+
+        - `libc`: the standard allocator provided by libc
+        ${concatStringsSep "\n" (mapAttrsToList
+            (name: value: "- `${name}`: ${replaceStrings [ "\n" ] [ " " ] value.description}")
             providers)}
-        </itemizedlist>
 
-        <warning>
-        <para>
+        ::: {.warning}
         Selecting an alternative allocator (i.e., anything other than
-        <literal>libc</literal>) may result in instability, data loss,
+        `libc`) may result in instability, data loss,
         and/or service failure.
-        </para>
-        </warning>
+        :::
       '';
     };
   };
 
   config = mkIf (cfg.provider != "libc") {
+    boot.kernel.sysctl."vm.max_map_count" = mkIf (cfg.provider == "graphene-hardened") (mkDefault 1048576);
     environment.etc."ld-nix.so.preload".text = ''
       ${providerLibPath}
     '';
diff --git a/nixpkgs/nixos/modules/config/mysql.nix b/nixpkgs/nixos/modules/config/mysql.nix
index 8e7ce2a307e5..2f13c56f2ae5 100644
--- a/nixpkgs/nixos/modules/config/mysql.nix
+++ b/nixpkgs/nixos/modules/config/mysql.nix
@@ -8,83 +8,73 @@ in
 {
   options = {
     users.mysql = {
-      enable = mkEnableOption "Authentication against a MySQL/MariaDB database";
+      enable = mkEnableOption (lib.mdDoc "Authentication against a MySQL/MariaDB database");
       host = mkOption {
         type = types.str;
         example = "localhost";
-        description = "The hostname of the MySQL/MariaDB server";
+        description = lib.mdDoc "The hostname of the MySQL/MariaDB server";
       };
       database = mkOption {
         type = types.str;
         example = "auth";
-        description = "The name of the database containing the users";
+        description = lib.mdDoc "The name of the database containing the users";
       };
       user = mkOption {
         type = types.str;
         example = "nss-user";
-        description = "The username to use when connecting to the database";
+        description = lib.mdDoc "The username to use when connecting to the database";
       };
       passwordFile = mkOption {
         type = types.path;
         example = "/run/secrets/mysql-auth-db-passwd";
-        description = "The path to the file containing the password for the user";
+        description = lib.mdDoc "The path to the file containing the password for the user";
       };
       pam = mkOption {
-        description = "Settings for <literal>pam_mysql</literal>";
+        description = lib.mdDoc "Settings for `pam_mysql`";
         type = types.submodule {
           options = {
             table = mkOption {
               type = types.str;
               example = "users";
-              description = "The name of table that maps unique login names to the passwords.";
+              description = lib.mdDoc "The name of table that maps unique login names to the passwords.";
             };
             updateTable = mkOption {
               type = types.nullOr types.str;
               default = null;
               example = "users_updates";
-              description = ''
+              description = lib.mdDoc ''
                 The name of the table used for password alteration. If not defined, the value
-                of the <literal>table</literal> option will be used instead.
+                of the `table` option will be used instead.
               '';
             };
             userColumn = mkOption {
               type = types.str;
               example = "username";
-              description = "The name of the column that contains a unix login name.";
+              description = lib.mdDoc "The name of the column that contains a unix login name.";
             };
             passwordColumn = mkOption {
               type = types.str;
               example = "password";
-              description = "The name of the column that contains a (encrypted) password string.";
+              description = lib.mdDoc "The name of the column that contains a (encrypted) password string.";
             };
             statusColumn = mkOption {
               type = types.nullOr types.str;
               default = null;
               example = "status";
-              description = ''
+              description = lib.mdDoc ''
                 The name of the column or an SQL expression that indicates the status of
                 the user. The status is expressed by the combination of two bitfields
                 shown below:
 
-                <itemizedlist>
-                  <listitem>
-                    <para>
-                      <literal>bit 0 (0x01)</literal>:
-                      if flagged, <literal>pam_mysql</literal> deems the account to be expired and
-                      returns <literal>PAM_ACCT_EXPIRED</literal>. That is, the account is supposed
-                      to no longer be available. Note this doesn't mean that <literal>pam_mysql</literal>
-                      rejects further authentication operations.
-                    </para>
-                  </listitem>
-                  <listitem>
-                    <para>
-                      <literal>bit 1 (0x02)</literal>:
-                      if flagged, <literal>pam_mysql</literal> deems the authentication token
-                      (password) to be expired and returns <literal>PAM_NEW_AUTHTOK_REQD</literal>.
-                      This ends up requiring that the user enter a new password.
-                    </para>
-                  </listitem>
-                </itemizedlist>
+                - `bit 0 (0x01)`:
+                   if flagged, `pam_mysql` deems the account to be expired and
+                   returns `PAM_ACCT_EXPIRED`. That is, the account is supposed
+                   to no longer be available. Note this doesn't mean that `pam_mysql`
+                   rejects further authentication operations.
+                -  `bit 1 (0x02)`:
+                   if flagged, `pam_mysql` deems the authentication token
+                   (password) to be expired and returns `PAM_NEW_AUTHTOK_REQD`.
+                   This ends up requiring that the user enter a new password.
               '';
             };
             passwordCrypt = mkOption {
@@ -101,101 +91,59 @@ in
                 "8" "sha512"
                 "9" "sha256"
               ];
-              description = ''
+              description = lib.mdDoc ''
                 The method to encrypt the user's password:
 
-                <itemizedlist>
-                <listitem>
-                  <para>
-                    <literal>0</literal> (or <literal>"plain"</literal>):
-                    No encryption. Passwords are stored in plaintext. HIGHLY DISCOURAGED.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>1</literal> (or <literal>"Y"</literal>):
-                    Use crypt(3) function.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>2</literal> (or <literal>"mysql"</literal>):
-                    Use the MySQL PASSWORD() function. It is possible that the encryption function used
-                    by <literal>pam_mysql</literal> is different from that of the MySQL server, as
-                    <literal>pam_mysql</literal> uses the function defined in MySQL's C-client API
-                    instead of using PASSWORD() SQL function in the query.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>3</literal> (or <literal>"md5"</literal>):
-                    Use plain hex MD5.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>4</literal> (or <literal>"sha1"</literal>):
-                    Use plain hex SHA1.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>5</literal> (or <literal>"drupal7"</literal>):
-                    Use Drupal7 salted passwords.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>6</literal> (or <literal>"joomla15"</literal>):
-                    Use Joomla15 salted passwords.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>7</literal> (or <literal>"ssha"</literal>):
-                    Use ssha hashed passwords.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>8</literal> (or <literal>"sha512"</literal>):
-                    Use sha512 hashed passwords.
-                  </para>
-                </listitem>
-                <listitem>
-                  <para>
-                    <literal>9</literal> (or <literal>"sha256"</literal>):
-                    Use sha256 hashed passwords.
-                  </para>
-                </listitem>
-                </itemizedlist>
+                - `0` (or `"plain"`):
+                  No encryption. Passwords are stored in plaintext. HIGHLY DISCOURAGED.
+                - `1` (or `"Y"`):
+                  Use crypt(3) function.
+                - `2` (or `"mysql"`):
+                  Use the MySQL PASSWORD() function. It is possible that the encryption function used
+                  by `pam_mysql` is different from that of the MySQL server, as
+                  `pam_mysql` uses the function defined in MySQL's C-client API
+                  instead of using PASSWORD() SQL function in the query.
+                - `3` (or `"md5"`):
+                  Use plain hex MD5.
+                - `4` (or `"sha1"`):
+                  Use plain hex SHA1.
+                - `5` (or `"drupal7"`):
+                  Use Drupal7 salted passwords.
+                - `6` (or `"joomla15"`):
+                  Use Joomla15 salted passwords.
+                - `7` (or `"ssha"`):
+                  Use ssha hashed passwords.
+                - `8` (or `"sha512"`):
+                  Use sha512 hashed passwords.
+                - `9` (or `"sha256"`):
+                  Use sha256 hashed passwords.
               '';
             };
             cryptDefault = mkOption {
               type = types.nullOr (types.enum [ "md5" "sha256" "sha512" "blowfish" ]);
               default = null;
               example = "blowfish";
-              description = "The default encryption method to use for <literal>passwordCrypt = 1</literal>.";
+              description = lib.mdDoc "The default encryption method to use for `passwordCrypt = 1`.";
             };
             where = mkOption {
               type = types.nullOr types.str;
               default = null;
               example = "host.name='web' AND user.active=1";
-              description = "Additional criteria for the query.";
+              description = lib.mdDoc "Additional criteria for the query.";
             };
             verbose = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 If enabled, produces logs with detailed messages that describes what
-                <literal>pam_mysql</literal> is doing. May be useful for debugging.
+                `pam_mysql` is doing. May be useful for debugging.
               '';
             };
             disconnectEveryOperation = mkOption {
               type = types.bool;
               default = false;
-              description = ''
-                By default, <literal>pam_mysql</literal> keeps the connection to the MySQL
+              description = lib.mdDoc ''
+                By default, `pam_mysql` keeps the connection to the MySQL
                 database until the session is closed. If this option is set to true it
                 disconnects every time the PAM operation has finished. This option may
                 be useful in case the session lasts quite long.
@@ -205,17 +153,17 @@ in
               enable = mkOption {
                 type = types.bool;
                 default = false;
-                description = "Enables logging of authentication attempts in the MySQL database.";
+                description = lib.mdDoc "Enables logging of authentication attempts in the MySQL database.";
               };
               table = mkOption {
                 type = types.str;
                 example = "logs";
-                description = "The name of the table to which logs are written.";
+                description = lib.mdDoc "The name of the table to which logs are written.";
               };
               msgColumn = mkOption {
                 type = types.str;
                 example = "msg";
-                description = ''
+                description = lib.mdDoc ''
                   The name of the column in the log table to which the description
                   of the performed operation is stored.
                 '';
@@ -223,7 +171,7 @@ in
               userColumn = mkOption {
                 type = types.str;
                 example = "user";
-                description = ''
+                description = lib.mdDoc ''
                   The name of the column in the log table to which the name of the
                   user being authenticated is stored.
                 '';
@@ -231,16 +179,16 @@ in
               pidColumn = mkOption {
                 type = types.str;
                 example = "pid";
-                description = ''
+                description = lib.mdDoc ''
                   The name of the column in the log table to which the pid of the
-                  process utilising the <literal>pam_mysql's</literal> authentication
+                  process utilising the `pam_mysql` authentication
                   service is stored.
                 '';
               };
               hostColumn = mkOption {
                 type = types.str;
                 example = "host";
-                description = ''
+                description = lib.mdDoc ''
                   The name of the column in the log table to which the name of the user
                   being authenticated is stored.
                 '';
@@ -248,17 +196,16 @@ in
               rHostColumn = mkOption {
                 type = types.str;
                 example = "rhost";
-                description = ''
+                description = lib.mdDoc ''
                   The name of the column in the log table to which the name of the remote
                   host that initiates the session is stored. The value is supposed to be
-                  set by the PAM-aware application with <literal>pam_set_item(PAM_RHOST)
-                  </literal>.
+                  set by the PAM-aware application with `pam_set_item(PAM_RHOST)`.
                 '';
               };
               timeColumn = mkOption {
                 type = types.str;
                 example = "timestamp";
-                description = ''
+                description = lib.mdDoc ''
                   The name of the column in the log table to which the timestamp of the
                   log entry is stored.
                 '';
@@ -268,11 +215,11 @@ in
         };
       };
       nss = mkOption {
-        description = ''
-          Settings for <literal>libnss-mysql</literal>.
+        description = lib.mdDoc ''
+          Settings for `libnss-mysql`.
 
-          All examples are from the <link xlink:href="https://github.com/saknopper/libnss-mysql/tree/master/sample/minimal">minimal example</link>
-          of <literal>libnss-mysql</literal>, but they are modified with NixOS paths for bash.
+          All examples are from the [minimal example](https://github.com/saknopper/libnss-mysql/tree/master/sample/minimal)
+          of `libnss-mysql`, but they are modified with NixOS paths for bash.
         '';
         type = types.submodule {
           options = {
@@ -285,9 +232,8 @@ in
                 WHERE username='%1$s' \
                 LIMIT 1
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getpwnam.3.html">getpwnam</link>
+              description = lib.mdDoc ''
+                SQL query for the [getpwnam](https://man7.org/linux/man-pages/man3/getpwnam.3.html)
                 syscall.
               '';
             };
@@ -300,9 +246,8 @@ in
                 WHERE uid='%1$u' \
                 LIMIT 1
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getpwuid.3.html">getpwuid</link>
+              description = lib.mdDoc ''
+                SQL query for the [getpwuid](https://man7.org/linux/man-pages/man3/getpwuid.3.html)
                 syscall.
               '';
             };
@@ -315,9 +260,8 @@ in
                 WHERE username='%1$s' \
                 LIMIT 1
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getspnam.3.html">getspnam</link>
+              description = lib.mdDoc ''
+                SQL query for the [getspnam](https://man7.org/linux/man-pages/man3/getspnam.3.html)
                 syscall.
               '';
             };
@@ -327,9 +271,8 @@ in
               example = literalExpression ''
                 SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' FROM users
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getpwent.3.html">getpwent</link>
+              description = lib.mdDoc ''
+                SQL query for the [getpwent](https://man7.org/linux/man-pages/man3/getpwent.3.html)
                 syscall.
               '';
             };
@@ -339,9 +282,8 @@ in
               example = literalExpression ''
                 SELECT username,password,'1','0','99999','0','0','-1','0' FROM users
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getspent.3.html">getspent</link>
+              description = lib.mdDoc ''
+                SQL query for the [getspent](https://man7.org/linux/man-pages/man3/getspent.3.html)
                 syscall.
               '';
             };
@@ -351,9 +293,8 @@ in
               example = literalExpression ''
                 SELECT name,password,gid FROM groups WHERE name='%1$s' LIMIT 1
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getgrnam.3.html">getgrnam</link>
+              description = lib.mdDoc ''
+                SQL query for the [getgrnam](https://man7.org/linux/man-pages/man3/getgrnam.3.html)
                 syscall.
               '';
             };
@@ -363,9 +304,8 @@ in
               example = literalExpression ''
                 SELECT name,password,gid FROM groups WHERE gid='%1$u' LIMIT 1
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getgrgid.3.html">getgrgid</link>
+              description = lib.mdDoc ''
+                SQL query for the [getgrgid](https://man7.org/linux/man-pages/man3/getgrgid.3.html)
                 syscall.
               '';
             };
@@ -375,9 +315,8 @@ in
               example = literalExpression ''
                 SELECT name,password,gid FROM groups
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/getgrent.3.html">getgrent</link>
+              description = lib.mdDoc ''
+                SQL query for the [getgrent](https://man7.org/linux/man-pages/man3/getgrent.3.html)
                 syscall.
               '';
             };
@@ -387,9 +326,8 @@ in
               example = literalExpression ''
                 SELECT username FROM grouplist WHERE gid='%1$u'
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/memsbygid.3.html">memsbygid</link>
+              description = lib.mdDoc ''
+                SQL query for the [memsbygid](https://man7.org/linux/man-pages/man3/memsbygid.3.html)
                 syscall.
               '';
             };
@@ -399,9 +337,8 @@ in
               example = literalExpression ''
                 SELECT gid FROM grouplist WHERE username='%1$s'
               '';
-              description = ''
-                SQL query for the <link
-                xlink:href="https://man7.org/linux/man-pages/man3/gidsbymem.3.html">gidsbymem</link>
+              description = lib.mdDoc ''
+                SQL query for the [gidsbymem](https://man7.org/linux/man-pages/man3/gidsbymem.3.html)
                 syscall.
               '';
             };
diff --git a/nixpkgs/nixos/modules/config/networking.nix b/nixpkgs/nixos/modules/config/networking.nix
index 6f899d74db25..fc910fee94bf 100644
--- a/nixpkgs/nixos/modules/config/networking.nix
+++ b/nixpkgs/nixos/modules/config/networking.nix
@@ -35,7 +35,7 @@ in
 
     networking.hostFiles = lib.mkOption {
       type = types.listOf types.path;
-      defaultText = literalDocBook "Hosts from <option>networking.hosts</option> and <option>networking.extraHosts</option>";
+      defaultText = literalMD "Hosts from {option}`networking.hosts` and {option}`networking.extraHosts`";
       example = literalExpression ''[ "''${pkgs.my-blocklist-package}/share/my-blocklist/hosts" ]'';
       description = lib.mdDoc ''
         Files that should be concatenated together to form {file}`/etc/hosts`.
@@ -141,7 +141,7 @@ in
         type = types.attrs;
         internal = true;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Environment variables used for the network proxy.
         '';
       };
diff --git a/nixpkgs/nixos/modules/config/no-x-libs.nix b/nixpkgs/nixos/modules/config/no-x-libs.nix
index 42e68c2eadc1..0dce3b918458 100644
--- a/nixpkgs/nixos/modules/config/no-x-libs.nix
+++ b/nixpkgs/nixos/modules/config/no-x-libs.nix
@@ -27,9 +27,30 @@ with lib;
     fonts.fontconfig.enable = false;
 
     nixpkgs.overlays = singleton (const (super: {
+      beam = super.beam_nox;
       cairo = super.cairo.override { x11Support = false; };
       dbus = super.dbus.override { x11Support = false; };
-      beam = super.beam_nox;
+      ffmpeg_4 = super.ffmpeg_4.override { ffmpegVariant = "headless"; };
+      ffmpeg_5 = super.ffmpeg_5.override { ffmpegVariant = "headless"; };
+      # dep of graphviz, libXpm is optional for Xpm support
+      gd = super.gd.override { withXorg = false; };
+      gobject-introspection = super.gobject-introspection.override { x11Support = false; };
+      gpsd = super.gpsd.override { guiSupport = false; };
+      graphviz = super.graphviz-nox;
+      gst_all_1 = super.gst_all_1 // {
+        gst-plugins-bad = super.gst_all_1.gst-plugins-bad.override { guiSupport = false; };
+        gst-plugins-base = super.gst_all_1.gst-plugins-base.override { enableX11 = false; };
+      };
+      imagemagick = super.imagemagick.override { libX11Support = false; libXtSupport = false; };
+      imagemagickBig = super.imagemagickBig.override { libX11Support = false; libXtSupport = false; };
+      libdevil = super.libdevil-nox;
+      libextractor = super.libextractor.override { gtkSupport = false; };
+      libva = super.libva-minimal;
+      limesuite = super.limesuite.override { withGui = false; };
+      mc = super.mc.override { x11Support = false; };
+      mpv-unwrapped = super.mpv-unwrapped.override { sdl2Support = false; x11Support = false; waylandSupport = false; };
+      msmtp = super.msmtp.override { withKeyring = false; };
+      neofetch = super.neofetch.override { x11Support = false; };
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       networkmanager-l2tp = super.networkmanager-l2tp.override { withGnome = false; };
@@ -37,8 +58,19 @@ with lib;
       networkmanager-openvpn = super.networkmanager-openvpn.override { withGnome = false; };
       networkmanager-sstp = super.networkmanager-vpnc.override { withGnome = false; };
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
-      gobject-introspection = super.gobject-introspection.override { x11Support = false; };
+      pango = super.pango.override { x11Support = false; };
+      pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
+      pipewire = super.pipewire.override { x11Support = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
+      qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
+      qt5 = super.qt5.overrideScope (const (super': {
+        qtbase = super'.qtbase.override { withGtk3 = false; };
+      }));
+      stoken = super.stoken.override { withGTK3 = false; };
+      # translateManpages -> perlPackages.po4a -> texlive-combined-basic -> texlive-core-big -> libX11
+      util-linux = super.util-linux.override { translateManpages = false; };
+      vim-full = super.vim-full.override { guiSupport = false; };
+      zbar = super.zbar.override { enableVideo = false; withXorg = false; };
     }));
   };
 }
diff --git a/nixpkgs/nixos/modules/config/power-management.nix b/nixpkgs/nixos/modules/config/power-management.nix
index a4e8028cfbe9..e7fd02920e0d 100644
--- a/nixpkgs/nixos/modules/config/power-management.nix
+++ b/nixpkgs/nixos/modules/config/power-management.nix
@@ -94,7 +94,7 @@ in
         after = [ "suspend.target" "hibernate.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ];
         script =
           ''
-            /run/current-system/systemd/bin/systemctl try-restart post-resume.target
+            /run/current-system/systemd/bin/systemctl try-restart --no-block post-resume.target
             ${cfg.resumeCommands}
             ${cfg.powerUpCommands}
           '';
diff --git a/nixpkgs/nixos/modules/config/pulseaudio.nix b/nixpkgs/nixos/modules/config/pulseaudio.nix
index aa3ca549f09a..80ff6c1aabf7 100644
--- a/nixpkgs/nixos/modules/config/pulseaudio.nix
+++ b/nixpkgs/nixos/modules/config/pulseaudio.nix
@@ -102,7 +102,7 @@ in {
           each user that tries to use the sound system. The server runs
           with user privileges. If true, one system-wide PulseAudio
           server is launched on boot, running as the user "pulse", and
-          only users in the "audio" group will have access to the server.
+          only users in the "pulse-access" group will have access to the server.
           Please read the PulseAudio documentation for more details.
 
           Don't enable this option unless you know what you are doing.
@@ -190,17 +190,17 @@ in {
 
       zeroconf = {
         discovery.enable =
-          mkEnableOption "discovery of pulseaudio sinks in the local network";
+          mkEnableOption (lib.mdDoc "discovery of pulseaudio sinks in the local network");
         publish.enable =
-          mkEnableOption "publishing the pulseaudio sink in the local network";
+          mkEnableOption (lib.mdDoc "publishing the pulseaudio sink in the local network");
       };
 
       # TODO: enable by default?
       tcp = {
-        enable = mkEnableOption "tcp streaming support";
+        enable = mkEnableOption (lib.mdDoc "tcp streaming support");
 
         anonymousClients = {
-          allowAll = mkEnableOption "all anonymous clients to stream to the server";
+          allowAll = mkEnableOption (lib.mdDoc "all anonymous clients to stream to the server");
           allowedIpRanges = mkOption {
             type = types.listOf types.str;
             default = [];
@@ -263,7 +263,7 @@ in {
           (drv: drv.override { pulseaudio = overriddenPackage; })
           cfg.extraModules;
         modulePaths = builtins.map
-          (drv: "${drv}/${overriddenPackage.pulseDir}/modules")
+          (drv: "${drv}/lib/pulseaudio/modules")
           # User-provided extra modules take precedence
           (overriddenModules ++ [ overriddenPackage ]);
       in lib.concatStringsSep ":" modulePaths;
@@ -310,6 +310,7 @@ in {
       };
 
       users.groups.pulse.gid = gid;
+      users.groups.pulse-access = {};
 
       systemd.services.pulseaudio = {
         description = "PulseAudio System-Wide Server";
diff --git a/nixpkgs/nixos/modules/config/qt5.nix b/nixpkgs/nixos/modules/config/qt.nix
index 9e19774b582f..cf4e9621d70d 100644
--- a/nixpkgs/nixos/modules/config/qt5.nix
+++ b/nixpkgs/nixos/modules/config/qt.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
 
-  cfg = config.qt5;
+  cfg = config.qt;
 
   isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"];
   isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]);
@@ -12,22 +12,34 @@ let
   isLxqt = cfg.platformTheme == "lxqt";
   isKde = cfg.platformTheme == "kde";
 
-  packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ]
+  packages =
+    if isQGnome then [
+      pkgs.qgnomeplatform
+      pkgs.adwaita-qt
+      pkgs.qgnomeplatform-qt6
+      pkgs.adwaita-qt6
+    ]
     else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ]
-    else if isQt5ct then [ pkgs.libsForQt5.qt5ct ]
+    else if isQt5ct then [ pkgs.libsForQt5.qt5ct pkgs.qt6Packages.qt6ct ]
     else if isLxqt then [ pkgs.lxqt.lxqt-qtplugin pkgs.lxqt.lxqt-config ]
     else if isKde then [ pkgs.libsForQt5.plasma-integration pkgs.libsForQt5.systemsettings ]
-    else throw "`qt5.platformTheme` ${cfg.platformTheme} and `qt5.style` ${cfg.style} are not compatible.";
+    else throw "`qt.platformTheme` ${cfg.platformTheme} and `qt.style` ${cfg.style} are not compatible.";
 
 in
 
 {
   meta.maintainers = [ maintainers.romildo ];
 
+  imports = [
+    (mkRenamedOptionModule ["qt5" "enable" ] ["qt" "enable" ])
+    (mkRenamedOptionModule ["qt5" "platformTheme" ] ["qt" "platformTheme" ])
+    (mkRenamedOptionModule ["qt5" "style" ] ["qt" "style" ])
+  ];
+
   options = {
-    qt5 = {
+    qt = {
 
-      enable = mkEnableOption "Qt5 theming configuration";
+      enable = mkEnableOption (lib.mdDoc "Qt theming configuration");
 
       platformTheme = mkOption {
         type = types.enum [
@@ -40,13 +52,14 @@ in
         example = "gnome";
         relatedPackages = [
           "qgnomeplatform"
+          "qgnomeplatform-qt6"
           ["libsForQt5" "qtstyleplugins"]
           ["libsForQt5" "qt5ct"]
           ["lxqt" "lxqt-qtplugin"]
           ["libsForQt5" "plasma-integration"]
         ];
         description = lib.mdDoc ''
-          Selects the platform theme to use for Qt5 applications.
+          Selects the platform theme to use for Qt applications.
 
           The options are
           - `gtk`: Use GTK theme with [qtstyleplugins](https://github.com/qt/qtstyleplugins)
@@ -71,10 +84,11 @@ in
         example = "adwaita";
         relatedPackages = [
           "adwaita-qt"
+          "adwaita-qt6"
           ["libsForQt5" "qtstyleplugins"]
         ];
         description = lib.mdDoc ''
-          Selects the style to use for Qt5 applications.
+          Selects the style to use for Qt applications.
 
           The options are
           - `adwaita`, `adwaita-dark`: Use Adwaita Qt style with
@@ -88,9 +102,17 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.variables.QT_QPA_PLATFORMTHEME = cfg.platformTheme;
+    environment.variables = {
+      QT_QPA_PLATFORMTHEME = cfg.platformTheme;
+      QT_STYLE_OVERRIDE = mkIf (! (isQt5ct || isLxqt || isKde)) cfg.style;
+    };
 
-    environment.variables.QT_STYLE_OVERRIDE = mkIf (! (isQt5ct || isLxqt || isKde)) cfg.style;
+    environment.profileRelativeSessionVariables = let
+      qtVersions = with pkgs; [ qt5 qt6 ];
+    in {
+      QT_PLUGIN_PATH = map (qt: "/${qt.qtbase.qtPluginPrefix}") qtVersions;
+      QML2_IMPORT_PATH = map (qt: "/${qt.qtbase.qtQmlPrefix}") qtVersions;
+    };
 
     environment.systemPackages = packages;
 
diff --git a/nixpkgs/nixos/modules/config/resolvconf.nix b/nixpkgs/nixos/modules/config/resolvconf.nix
index 76605a063a47..e9ae4d651d26 100644
--- a/nixpkgs/nixos/modules/config/resolvconf.nix
+++ b/nixpkgs/nixos/modules/config/resolvconf.nix
@@ -132,13 +132,13 @@ in
             exit 1
           ''
         else configText;
-
-      environment.systemPackages = [ cfg.package ];
     }
 
     (mkIf cfg.enable {
       networking.resolvconf.package = pkgs.openresolv;
 
+      environment.systemPackages = [ cfg.package ];
+
       systemd.services.resolvconf = {
         description = "resolvconf update";
 
diff --git a/nixpkgs/nixos/modules/config/shells-environment.nix b/nixpkgs/nixos/modules/config/shells-environment.nix
index 2fda71749c09..bc6583442edf 100644
--- a/nixpkgs/nixos/modules/config/shells-environment.nix
+++ b/nixpkgs/nixos/modules/config/shells-environment.nix
@@ -42,8 +42,8 @@ in
         strings.  The latter is concatenated, interspersed with colon
         characters.
       '';
-      type = with types; attrsOf (either str (listOf str));
-      apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
+      type = with types; attrsOf (oneOf [ (listOf str) str path ]);
+      apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else "${v}");
     };
 
     environment.profiles = mkOption {
@@ -140,9 +140,9 @@ in
       example = literalExpression ''"''${pkgs.dash}/bin/dash"'';
       type = types.path;
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         The shell executable that is linked system-wide to
-        <literal>/bin/sh</literal>. Please note that NixOS assumes all
+        `/bin/sh`. Please note that NixOS assumes all
         over the place that shell to be Bash, so override the default
         setting only if you know exactly what you're doing.
       '';
diff --git a/nixpkgs/nixos/modules/config/stevenblack.nix b/nixpkgs/nixos/modules/config/stevenblack.nix
new file mode 100644
index 000000000000..07a0aa339a56
--- /dev/null
+++ b/nixpkgs/nixos/modules/config/stevenblack.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) optionals mkOption mkEnableOption types mkIf elem concatStringsSep maintainers mdDoc;
+  cfg = config.networking.stevenblack;
+
+  # needs to be in a specific order
+  activatedHosts = with cfg; [ ]
+    ++ optionals (elem "fakenews" block) [ "fakenews" ]
+    ++ optionals (elem "gambling" block) [ "gambling" ]
+    ++ optionals (elem "porn" block) [ "porn" ]
+    ++ optionals (elem "social" block) [ "social" ];
+
+  hostsPath = "${pkgs.stevenblack-blocklist}/alternates/" + concatStringsSep "-" activatedHosts + "/hosts";
+in
+{
+  options.networking.stevenblack = {
+    enable = mkEnableOption (mdDoc "Enable the stevenblack hosts file blocklist");
+
+    block = mkOption {
+      type = types.listOf (types.enum [ "fakenews" "gambling" "porn" "social" ]);
+      default = [ ];
+      description = mdDoc "Additional blocklist extensions.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.hostFiles = [ ]
+      ++ optionals (activatedHosts != [ ]) [ hostsPath ]
+      ++ optionals (activatedHosts == [ ]) [ "${pkgs.stevenblack-blocklist}/hosts" ];
+  };
+
+  meta.maintainers = [ maintainers.fortuneteller2k maintainers.artturin ];
+}
diff --git a/nixpkgs/nixos/modules/config/swap.nix b/nixpkgs/nixos/modules/config/swap.nix
index 3216590d8f95..0a7e45bffb26 100644
--- a/nixpkgs/nixos/modules/config/swap.nix
+++ b/nixpkgs/nixos/modules/config/swap.nix
@@ -38,6 +38,34 @@ let
         '';
       };
 
+      keySize = mkOption {
+        default = null;
+        example = "512";
+        type = types.nullOr types.int;
+        description = lib.mdDoc ''
+          Set the encryption key size for the plain device.
+
+          If not specified, the amount of data to read from `source` will be
+          determined by cryptsetup.
+
+          See `cryptsetup-open(8)` for details.
+        '';
+      };
+
+      sectorSize = mkOption {
+        default = null;
+        example = "4096";
+        type = types.nullOr types.int;
+        description = lib.mdDoc ''
+          Set the sector size for the plain encrypted device type.
+
+          If not specified, the default sector size is determined from the
+          underlying block device.
+
+          See `cryptsetup-open(8)` for details.
+        '';
+      };
+
       source = mkOption {
         default = "/dev/urandom";
         example = "/dev/random";
@@ -66,15 +94,15 @@ let
 
       device = mkOption {
         example = "/dev/sda3";
-        type = types.str;
+        type = types.nonEmptyStr;
         description = lib.mdDoc "Path of the device or swap file.";
       };
 
       label = mkOption {
         example = "swap";
         type = types.str;
-        description = ''
-          Label of the device.  Can be used instead of <varname>device</varname>.
+        description = lib.mdDoc ''
+          Label of the device.  Can be used instead of {var}`device`.
         '';
       };
 
@@ -157,11 +185,11 @@ let
 
     };
 
-    config = rec {
+    config = {
       device = mkIf options.label.isDefined
         "/dev/disk/by-label/${config.label}";
-      deviceName = lib.replaceChars ["\\"] [""] (escapeSystemdPath config.device);
-      realDevice = if config.randomEncryption.enable then "/dev/mapper/${deviceName}" else config.device;
+      deviceName = lib.replaceStrings ["\\"] [""] (escapeSystemdPath config.device);
+      realDevice = if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
     };
 
   };
@@ -197,6 +225,21 @@ in
   };
 
   config = mkIf ((length config.swapDevices) != 0) {
+    assertions = map (sw: {
+      assertion = sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null;
+      message = ''
+        You cannot use swap device "${sw.device}" with randomEncryption enabled.
+        The UUIDs and labels will get erased on every boot when the partition is encrypted.
+        Use /dev/disk/by-partuuid/… instead.
+      '';
+    }) config.swapDevices;
+
+    warnings =
+      concatMap (sw:
+        if sw.size != null && hasPrefix "/dev/" sw.device
+        then [ "Setting the swap size of block device ${sw.device} has no effect" ]
+        else [ ])
+      config.swapDevices;
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isYes "SWAP")
@@ -205,31 +248,38 @@ in
     # Create missing swapfiles.
     systemd.services =
       let
-
         createSwapDevice = sw:
-          assert sw.device != "";
-          assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-uuid"  sw.device);
-          assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-label" sw.device);
           let realDevice' = escapeSystemdPath sw.realDevice;
           in nameValuePair "mkswap-${sw.deviceName}"
           { description = "Initialisation of swap device ${sw.device}";
             wantedBy = [ "${realDevice'}.swap" ];
             before = [ "${realDevice'}.swap" ];
-            path = [ pkgs.util-linux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
+            path = [ pkgs.util-linux pkgs.e2fsprogs ]
+              ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
+
+            environment.DEVICE = sw.device;
 
             script =
               ''
                 ${optionalString (sw.size != null) ''
-                  currentSize=$(( $(stat -c "%s" "${sw.device}" 2>/dev/null || echo 0) / 1024 / 1024 ))
-                  if [ "${toString sw.size}" != "$currentSize" ]; then
-                    dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
+                  currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
+                  if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
+                    # Disable CoW for CoW based filesystems like BTRFS.
+                    truncate --size 0 "$DEVICE"
+                    chattr +C "$DEVICE" 2>/dev/null || true
+
+                    dd if=/dev/zero of="$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.randomEncryption.allowDiscards "--allow-discards"} ${sw.device} ${sw.deviceName}
+                  ${concatStringsSep " \\\n" (flatten [
+                    (optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}")
+                    (optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}")
+                    (optional sw.randomEncryption.allowDiscards "--allow-discards")
+                  ])} ${sw.device} ${sw.deviceName}
                   mkswap ${sw.realDevice}
                 ''}
               '';
diff --git a/nixpkgs/nixos/modules/config/sysctl.nix b/nixpkgs/nixos/modules/config/sysctl.nix
index b4b2d0452c4f..4346c88f7688 100644
--- a/nixpkgs/nixos/modules/config/sysctl.nix
+++ b/nixpkgs/nixos/modules/config/sysctl.nix
@@ -21,11 +21,24 @@ in
   options = {
 
     boot.kernel.sysctl = mkOption {
+      type = types.submodule {
+        freeformType = types.attrsOf sysctlOption;
+        options."net.core.rmem_max" = mkOption {
+          type = types.nullOr types.ints.unsigned // {
+            merge = loc: defs:
+              foldl
+                (a: b: if b.value == null then null else lib.max a b.value)
+                0
+                (filterOverrides defs);
+          };
+          default = null;
+          description = lib.mdDoc "The maximum socket receive buffer size. In case of conflicting values, the highest will be used.";
+        };
+      };
       default = {};
       example = literalExpression ''
         { "net.ipv4.tcp_syncookies" = false; "vm.swappiness" = 60; }
       '';
-      type = types.attrsOf sysctlOption;
       description = lib.mdDoc ''
         Runtime parameters of the Linux kernel, as set by
         {manpage}`sysctl(8)`.  Note that sysctl
@@ -35,6 +48,7 @@ in
         parameter may be a string, integer, boolean, or null
         (signifying the option will not appear at all).
       '';
+
     };
 
   };
diff --git a/nixpkgs/nixos/modules/config/system-environment.nix b/nixpkgs/nixos/modules/config/system-environment.nix
index 5b226d5079b0..399304185223 100644
--- a/nixpkgs/nixos/modules/config/system-environment.nix
+++ b/nixpkgs/nixos/modules/config/system-environment.nix
@@ -1,6 +1,6 @@
 # This module defines a system-wide environment that will be
 # initialised by pam_env (that is, not only in shells).
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -32,8 +32,7 @@ in
         therefore not possible to use PAM style variables such as
         `@{HOME}`.
       '';
-      type = with types; attrsOf (either str (listOf str));
-      apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
+      inherit (options.environment.variables) type apply;
     };
 
     environment.profileRelativeSessionVariables = mkOption {
diff --git a/nixpkgs/nixos/modules/config/system-path.nix b/nixpkgs/nixos/modules/config/system-path.nix
index de980e7383b0..e8bbeac4f720 100644
--- a/nixpkgs/nixos/modules/config/system-path.nix
+++ b/nixpkgs/nixos/modules/config/system-path.nix
@@ -78,25 +78,26 @@ in
       defaultPackages = mkOption {
         type = types.listOf types.package;
         default = defaultPackages;
-        defaultText = literalDocBook ''
-          these packages, with their <literal>meta.priority</literal> numerically increased
+        defaultText = literalMD ''
+          these packages, with their `meta.priority` numerically increased
           (thus lowering their installation priority):
-          <programlisting>${defaultPackagesText}</programlisting>
+
+              ${defaultPackagesText}
         '';
         example = [];
-        description = ''
+        description = lib.mdDoc ''
           Set of default packages that aren't strictly necessary
           for a running system, entries can be removed for a more
           minimal NixOS installation.
 
-          Note: If <package>pkgs.nano</package> is removed from this list,
+          Note: If `pkgs.nano` is removed from this list,
           make sure another editor is installed and the
-          <literal>EDITOR</literal> environment variable is set to it.
+          `EDITOR` environment variable is set to it.
           Environment variables can be set using
-          <option>environment.variables</option>.
+          {option}`environment.variables`.
 
           Like with systemPackages, packages are installed to
-          <filename>/run/current-system/sw</filename>. They are
+          {file}`/run/current-system/sw`. They are
           automatically available to all users, and are
           automatically updated every time you rebuild the system
           configuration.
@@ -131,7 +132,7 @@ in
 
       path = mkOption {
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           The packages you want in the boot environment.
         '';
       };
diff --git a/nixpkgs/nixos/modules/config/update-users-groups.pl b/nixpkgs/nixos/modules/config/update-users-groups.pl
index 51e57b050064..7dd2b858bb2a 100644
--- a/nixpkgs/nixos/modules/config/update-users-groups.pl
+++ b/nixpkgs/nixos/modules/config/update-users-groups.pl
@@ -187,7 +187,7 @@ foreach my $name (keys %groupsCur) {
 # Rewrite /etc/group. FIXME: acquire lock.
 my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members}) . "\n" }
     (sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
-updateFile($gidMapFile, to_json($gidMap));
+updateFile($gidMapFile, to_json($gidMap, {canonical => 1}));
 updateFile("/etc/group", \@lines);
 nscdInvalidate("group");
 
@@ -216,10 +216,12 @@ foreach my $u (@{$spec->{users}}) {
     } else {
         $u->{uid} = allocUid($name, $u->{isSystemUser}) if !defined $u->{uid};
 
-        if (defined $u->{initialPassword}) {
-            $u->{hashedPassword} = hashPassword($u->{initialPassword});
-        } elsif (defined $u->{initialHashedPassword}) {
-            $u->{hashedPassword} = $u->{initialHashedPassword};
+        if (!defined $u->{hashedPassword}) {
+            if (defined $u->{initialPassword}) {
+                $u->{hashedPassword} = hashPassword($u->{initialPassword});
+            } elsif (defined $u->{initialHashedPassword}) {
+                $u->{hashedPassword} = $u->{initialHashedPassword};
+            }
         }
     }
 
@@ -274,7 +276,7 @@ foreach my $name (keys %usersCur) {
 # Rewrite /etc/passwd. FIXME: acquire lock.
 @lines = map { join(":", $_->{name}, $_->{fakePassword}, $_->{uid}, $_->{gid}, $_->{description}, $_->{home}, $_->{shell}) . "\n" }
     (sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
-updateFile($uidMapFile, to_json($uidMap));
+updateFile($uidMapFile, to_json($uidMap, {canonical => 1}));
 updateFile("/etc/passwd", \@lines);
 nscdInvalidate("passwd");
 
diff --git a/nixpkgs/nixos/modules/config/users-groups.nix b/nixpkgs/nixos/modules/config/users-groups.nix
index 915687d1799a..4640a0f3d6be 100644
--- a/nixpkgs/nixos/modules/config/users-groups.nix
+++ b/nixpkgs/nixos/modules/config/users-groups.nix
@@ -17,35 +17,35 @@ let
       ]);
 
   passwordDescription = ''
-    The options <option>hashedPassword</option>,
-    <option>password</option> and <option>passwordFile</option>
+    The options {option}`hashedPassword`,
+    {option}`password` and {option}`passwordFile`
     controls what password is set for the user.
-    <option>hashedPassword</option> overrides both
-    <option>password</option> and <option>passwordFile</option>.
-    <option>password</option> overrides <option>passwordFile</option>.
+    {option}`hashedPassword` overrides both
+    {option}`password` and {option}`passwordFile`.
+    {option}`password` overrides {option}`passwordFile`.
     If none of these three options are set, no password is assigned to
     the user, and the user will not be able to do password logins.
-    If the option <option>users.mutableUsers</option> is true, the
+    If the option {option}`users.mutableUsers` is true, the
     password defined in one of the three options will only be set when
     the user is created for the first time. After that, you are free to
     change the password with the ordinary user management commands. If
-    <option>users.mutableUsers</option> is false, you cannot change
+    {option}`users.mutableUsers` is false, you cannot change
     user passwords, they will always be set according to the password
     options.
   '';
 
   hashedPasswordDescription = ''
-    To generate a hashed password run <literal>mkpasswd -m sha-512</literal>.
+    To generate a hashed password run `mkpasswd`.
 
-    If set to an empty string (<literal>""</literal>), this user will
+    If set to an empty string (`""`), this user will
     be able to log in without being asked for a password (but not via remote
-    services such as SSH, or indirectly via <command>su</command> or
-    <command>sudo</command>). This should only be used for e.g. bootable
+    services such as SSH, or indirectly via {command}`su` or
+    {command}`sudo`). This should only be used for e.g. bootable
     live systems. Note: this is different from setting an empty password,
-    which can be achieved using <option>users.users.&lt;name?&gt;.password</option>.
+    which can be achieved using {option}`users.users.<name?>.password`.
 
-    If set to <literal>null</literal> (default) this user will not
-    be able to log in using a password (i.e. via <command>login</command>
+    If set to `null` (default) this user will not
+    be able to log in using a password (i.e. via {command}`login`
     command).
   '';
 
@@ -90,7 +90,7 @@ let
           only has an effect if {option}`uid` is
           {option}`null`, in which case it determines whether
           the user's UID is allocated in the range for system users
-          (below 500) or in the range for normal users (starting at
+          (below 1000) or in the range for normal users (starting at
           1000).
           Exactly one of `isNormalUser` and
           `isSystemUser` must be true.
@@ -101,16 +101,13 @@ let
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Indicates whether this is an account for a “real” user. This
-          automatically sets {option}`group` to
-          `users`, {option}`createHome` to
-          `true`, {option}`home` to
-          {file}`/home/«username»`,
+          Indicates whether this is an account for a “real” user.
+          This automatically sets {option}`group` to `users`,
+          {option}`createHome` to `true`,
+          {option}`home` to {file}`/home/«username»`,
           {option}`useDefaultShell` to `true`,
-          and {option}`isSystemUser` to
-          `false`.
-          Exactly one of `isNormalUser` and
-          `isSystemUser` must be true.
+          and {option}`isSystemUser` to `false`.
+          Exactly one of `isNormalUser` and `isSystemUser` must be true.
         '';
       };
 
@@ -234,7 +231,7 @@ let
       hashedPassword = mkOption {
         type = with types; nullOr (passwdEntry str);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the hashed password for the user.
           ${passwordDescription}
           ${hashedPasswordDescription}
@@ -244,7 +241,7 @@ let
       password = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the (clear text) password for the user.
           Warning: do not set confidential information here
           because it is world-readable in the Nix store. This option
@@ -256,11 +253,11 @@ let
       passwordFile = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The full path to a file that contains the user's password. The password
           file is read on each system activation. The file should contain
           exactly one line, which should be the password in an encrypted form
-          that is suitable for the <literal>chpasswd -e</literal> command.
+          that is suitable for the `chpasswd -e` command.
           ${passwordDescription}
         '';
       };
@@ -268,13 +265,16 @@ let
       initialHashedPassword = mkOption {
         type = with types; nullOr (passwdEntry str);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the initial hashed password for the user, i.e. the
           hashed password assigned if the user does not already
-          exist. If <option>users.mutableUsers</option> is true, the
+          exist. If {option}`users.mutableUsers` is true, the
           password can be changed subsequently using the
-          <command>passwd</command> command. Otherwise, it's
-          equivalent to setting the <option>hashedPassword</option> option.
+          {command}`passwd` command. Otherwise, it's
+          equivalent to setting the {option}`hashedPassword` option.
+
+          Note that the {option}`hashedPassword` option will override
+          this option if both are set.
 
           ${hashedPasswordDescription}
         '';
@@ -294,6 +294,9 @@ let
           is world-readable in the Nix store, so it should only be
           used for guest accounts or passwords that will be changed
           promptly.
+
+          Note that the {option}`password` option will override this
+          option if both are set.
         '';
       };
 
@@ -425,6 +428,8 @@ let
 
   uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
+  sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
+  sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
 
   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
     inherit (cfg) mutableUsers;
@@ -447,8 +452,8 @@ let
 
 in {
   imports = [
-    (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ])
-    (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ])
+    (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ])
+    (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ])
     (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"])
   ];
 
@@ -458,25 +463,25 @@ in {
     users.mutableUsers = mkOption {
       type = types.bool;
       default = true;
-      description = ''
-        If set to <literal>true</literal>, you are free to add new users and groups to the system
-        with the ordinary <literal>useradd</literal> and
-        <literal>groupadd</literal> commands. On system activation, the
-        existing contents of the <literal>/etc/passwd</literal> and
-        <literal>/etc/group</literal> files will be merged with the
-        contents generated from the <literal>users.users</literal> and
-        <literal>users.groups</literal> options.
+      description = lib.mdDoc ''
+        If set to `true`, you are free to add new users and groups to the system
+        with the ordinary `useradd` and
+        `groupadd` commands. On system activation, the
+        existing contents of the `/etc/passwd` and
+        `/etc/group` files will be merged with the
+        contents generated from the `users.users` and
+        `users.groups` options.
         The initial password for a user will be set
-        according to <literal>users.users</literal>, but existing passwords
+        according to `users.users`, but existing passwords
         will not be changed.
 
-        <warning><para>
-        If set to <literal>false</literal>, the contents of the user and
+        ::: {.warning}
+        If set to `false`, the contents of the user and
         group files will simply be replaced on system activation. This also
         holds for the user passwords; all changed
         passwords will be reset according to the
-        <literal>users.users</literal> configuration on activation.
-        </para></warning>
+        `users.users` configuration on activation.
+        :::
       '';
     };
 
@@ -531,12 +536,62 @@ in {
         WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing.
       '';
     };
+
+    # systemd initrd
+    boot.initrd.systemd.users = mkOption {
+      visible = false;
+      description = ''
+        Users to include in initrd.
+      '';
+      default = {};
+      type = types.attrsOf (types.submodule ({ name, ... }: {
+        options.uid = mkOption {
+          visible = false;
+          type = types.int;
+          description = ''
+            ID of the user in initrd.
+          '';
+          defaultText = literalExpression "config.users.users.\${name}.uid";
+          default = cfg.users.${name}.uid;
+        };
+        options.group = mkOption {
+          visible = false;
+          type = types.singleLineStr;
+          description = ''
+            Group the user belongs to in initrd.
+          '';
+          defaultText = literalExpression "config.users.users.\${name}.group";
+          default = cfg.users.${name}.group;
+        };
+      }));
+    };
+
+    boot.initrd.systemd.groups = mkOption {
+      visible = false;
+      description = ''
+        Groups to include in initrd.
+      '';
+      default = {};
+      type = types.attrsOf (types.submodule ({ name, ... }: {
+        options.gid = mkOption {
+          visible = false;
+          type = types.int;
+          description = ''
+            ID of the group in initrd.
+          '';
+          defaultText = literalExpression "config.users.groups.\${name}.gid";
+          default = cfg.groups.${name}.gid;
+        };
+      }));
+    };
   };
 
 
   ###### implementation
 
-  config = {
+  config = let
+    cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})";
+  in {
 
     users.users = {
       root = {
@@ -592,13 +647,34 @@ in {
       '';
     };
 
+    # Warn about user accounts with deprecated password hashing schemes
+    system.activationScripts.hashes = {
+      deps = [ "users" ];
+      text = ''
+        users=()
+        while IFS=: read -r user hash _; do
+          if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then
+            users+=("$user")
+          fi
+        done </etc/shadow
+
+        if (( "''${#users[@]}" )); then
+          echo "
+        WARNING: The following user accounts rely on password hashing algorithms
+        that have been removed. They need to be renewed as soon as possible, as
+        they do prevent their users from logging in."
+          printf ' - %s\n' "''${users[@]}"
+        fi
+      '';
+    };
+
     # for backwards compatibility
     system.activationScripts.groups = stringAfter [ "users" ] "";
 
     # Install all the user shells
     environment.systemPackages = systemShells;
 
-    environment.etc = (mapAttrs' (_: { packages, name, ... }: {
+    environment.etc = mapAttrs' (_: { packages, name, ... }: {
       name = "profiles/per-user/${name}";
       value.source = pkgs.buildEnv {
         name = "user-environment";
@@ -606,17 +682,59 @@ in {
         inherit (config.environment) pathsToLink extraOutputsToInstall;
         inherit (config.system.path) ignoreCollisions postBuild;
       };
-    }) (filterAttrs (_: u: u.packages != []) cfg.users));
+    }) (filterAttrs (_: u: u.packages != []) cfg.users);
 
     environment.profiles = [
       "$HOME/.nix-profile"
       "/etc/profiles/per-user/$USER"
     ];
 
+    # systemd initrd
+    boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
+      contents = {
+        "/etc/passwd".text = ''
+          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group }: let
+            g = config.boot.initrd.systemd.groups.${group};
+          in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:") config.boot.initrd.systemd.users)}
+        '';
+        "/etc/group".text = ''
+          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
+        '';
+      };
+
+      users = {
+        root = {};
+        nobody = {};
+      };
+
+      groups = {
+        root = {};
+        nogroup = {};
+        systemd-journal = {};
+        tty = {};
+        dialout = {};
+        kmem = {};
+        input = {};
+        video = {};
+        render = {};
+        sgx = {};
+        audio = {};
+        video = {};
+        lp = {};
+        disk = {};
+        cdrom = {};
+        tape = {};
+        kvm = {};
+      };
+    };
+
     assertions = [
       { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
         message = "UIDs and GIDs must be unique!";
       }
+      { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
+        message = "systemd initrd UIDs and GIDs must be unique!";
+      }
       { # If mutableUsers is false, to prevent users creating a
         # configuration that locks them out of the system, ensure that
         # there is at least one "privileged" account that has a
@@ -660,7 +778,7 @@ in {
           {
             assertion = let
               xor = a: b: a && !b || b && !a;
-              isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 500);
+              isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
             in xor isEffectivelySystemUser user.isNormalUser;
             message = ''
               Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
@@ -676,7 +794,20 @@ in {
               users.groups.${user.name} = {};
             '';
           }
-        ]
+        ] ++ (map (shell: {
+            assertion = (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
+            message = ''
+              users.users.${user.name}.shell is set to ${shell}, but
+              programs.${shell}.enable is not true. This will cause the ${shell}
+              shell to lack the basic nix directories in its PATH and might make
+              logging in as that user impossible. You can fix it with:
+              programs.${shell}.enable = true;
+            '';
+          }) [
+          "fish"
+          "xonsh"
+          "zsh"
+        ])
     ));
 
     warnings =
@@ -693,11 +824,12 @@ in {
         let
           sep = "\\$";
           base64 = "[a-zA-Z0-9./]+";
-          id = "[a-z0-9-]+";
+          id = cryptSchemeIdPatternGroup;
+          name = "[a-z0-9-]+";
           value = "[a-zA-Z0-9/+.-]+";
-          options = "${id}(=${value})?(,${id}=${value})*";
+          options = "${name}(=${value})?(,${name}=${value})*";
           scheme  = "${id}(${sep}${options})?";
-          content = "${base64}${sep}${base64}";
+          content = "${base64}${sep}${base64}(${sep}${base64})?";
           mcf = "^${sep}${scheme}${sep}${content}$";
         in
         if (allowsLogin user.hashedPassword
diff --git a/nixpkgs/nixos/modules/config/xdg/portal.nix b/nixpkgs/nixos/modules/config/xdg/portal.nix
index 689abf267f09..e19e5cf28b3b 100644
--- a/nixpkgs/nixos/modules/config/xdg/portal.nix
+++ b/nixpkgs/nixos/modules/config/xdg/portal.nix
@@ -21,7 +21,7 @@ in
       in
       {
         warnings = lib.mkIf config.xdg.portal.gtkUsePortal [
-          "The option `${lib.showOption from}' defined in ${lib.showFiles fromOpt.files} has been deprecated. Setting the variable globally with `environment.sessionVariables' NixOS option can have unforseen side-effects."
+          "The option `${lib.showOption from}' defined in ${lib.showFiles fromOpt.files} has been deprecated. Setting the variable globally with `environment.sessionVariables' NixOS option can have unforeseen side-effects."
         ];
       }
     )
@@ -33,19 +33,19 @@ in
 
   options.xdg.portal = {
     enable =
-      mkEnableOption ''<link xlink:href="https://github.com/flatpak/xdg-desktop-portal">xdg desktop integration</link>'' // {
+      mkEnableOption (lib.mdDoc ''[xdg desktop integration](https://github.com/flatpak/xdg-desktop-portal)'') // {
         default = false;
       };
 
     extraPortals = mkOption {
       type = types.listOf types.package;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         List of additional portals to add to path. Portals allow interaction
         with system, like choosing files or taking screenshots. At minimum,
         a desktop portal implementation should be listed. GNOME and KDE already
-        adds <package>xdg-desktop-portal-gtk</package>; and
-        <package>xdg-desktop-portal-kde</package> respectively. On other desktop
+        adds `xdg-desktop-portal-gtk`; and
+        `xdg-desktop-portal-kde` respectively. On other desktop
         environments you probably want to add them yourself.
       '';
     };
@@ -54,11 +54,22 @@ in
       type = types.bool;
       visible = false;
       default = false;
-      description = ''
-        Sets environment variable <literal>GTK_USE_PORTAL</literal> to <literal>1</literal>.
+      description = lib.mdDoc ''
+        Sets environment variable `GTK_USE_PORTAL` to `1`.
         This will force GTK-based programs ran outside Flatpak to respect and use XDG Desktop Portals
         for features like file chooser but it is an unsupported hack that can easily break things.
-        Defaults to <literal>false</literal> to respect its opt-in nature.
+        Defaults to `false` to respect its opt-in nature.
+      '';
+    };
+
+    xdgOpenUsePortal = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Sets environment variable `NIXOS_XDG_OPEN_USE_PORTAL` to `1`
+        This will make `xdg-open` use the portal to open programs, which resolves bugs involving
+        programs opening inside FHS envs or with unexpected env vars set from wrappers.
+        See [#160923](https://github.com/NixOS/nixpkgs/issues/160923) for more info.
       '';
     };
   };
@@ -95,6 +106,7 @@ in
 
         sessionVariables = {
           GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
+          NIXOS_XDG_OPEN_USE_PORTAL = mkIf cfg.xdgOpenUsePortal "1";
           XDG_DESKTOP_PORTAL_DIR = "${joinedPortals}/share/xdg-desktop-portal/portals";
         };
       };
diff --git a/nixpkgs/nixos/modules/config/xdg/portals/lxqt.nix b/nixpkgs/nixos/modules/config/xdg/portals/lxqt.nix
index e85e2cc32693..18fcf3d81c02 100644
--- a/nixpkgs/nixos/modules/config/xdg/portals/lxqt.nix
+++ b/nixpkgs/nixos/modules/config/xdg/portals/lxqt.nix
@@ -12,13 +12,13 @@ in
   };
 
   options.xdg.portal.lxqt = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       the desktop portal for the LXQt desktop environment.
 
-      This will add the <package>lxqt.xdg-desktop-portal-lxqt</package>
+      This will add the `lxqt.xdg-desktop-portal-lxqt`
       package (with the extra Qt styles) into the
-      <option>xdg.portal.extraPortals</option> option
-    '';
+      {option}`xdg.portal.extraPortals` option
+    '');
 
     styles = mkOption {
       type = types.listOf types.package;
@@ -29,9 +29,9 @@ in
         pkgs.qtcurve
       ];
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra Qt styles that will be available to the
-        <package>lxqt.xdg-desktop-portal-lxqt</package>.
+        `lxqt.xdg-desktop-portal-lxqt`.
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/config/xdg/portals/wlr.nix b/nixpkgs/nixos/modules/config/xdg/portals/wlr.nix
index aba1d8dbc00e..d84ae794e3bc 100644
--- a/nixpkgs/nixos/modules/config/xdg/portals/wlr.nix
+++ b/nixpkgs/nixos/modules/config/xdg/portals/wlr.nix
@@ -14,19 +14,19 @@ in
   };
 
   options.xdg.portal.wlr = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       desktop portal for wlroots-based desktops
 
-      This will add the <package>xdg-desktop-portal-wlr</package> package into
-      the <option>xdg.portal.extraPortals</option> option, and provide the
+      This will add the `xdg-desktop-portal-wlr` package into
+      the {option}`xdg.portal.extraPortals` option, and provide the
       configuration file
-    '';
+    '');
 
     settings = mkOption {
-      description = ''
-        Configuration for <package>xdg-desktop-portal-wlr</package>.
+      description = lib.mdDoc ''
+        Configuration for `xdg-desktop-portal-wlr`.
 
-        See <literal>xdg-desktop-portal-wlr(5)</literal> for supported
+        See `xdg-desktop-portal-wlr(5)` for supported
         values.
       '';
 
diff --git a/nixpkgs/nixos/modules/config/zram.nix b/nixpkgs/nixos/modules/config/zram.nix
index 34e80df47a40..991387ea9b2b 100644
--- a/nixpkgs/nixos/modules/config/zram.nix
+++ b/nixpkgs/nixos/modules/config/zram.nix
@@ -1,45 +1,27 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
 
   cfg = config.zramSwap;
-
-  # don't set swapDevices as mkDefault, so we can detect user had read our warning
-  # (see below) and made an action (or not)
-  devicesCount = if cfg.swapDevices != null then cfg.swapDevices else cfg.numDevices;
-
-  devices = map (nr: "zram${toString nr}") (range 0 (devicesCount - 1));
-
-  modprobe = "${pkgs.kmod}/bin/modprobe";
-
-  warnings =
-  assert cfg.swapDevices != null -> cfg.numDevices >= cfg.swapDevices;
-  flatten [
-    (optional (cfg.numDevices > 1 && cfg.swapDevices == null) ''
-      Using several small zram devices as swap is no better than using one large.
-      Set either zramSwap.numDevices = 1 or explicitly set zramSwap.swapDevices.
-
-      Previously multiple zram devices were used to enable multithreaded
-      compression. Linux supports multithreaded compression for 1 device
-      since 3.15. See https://lkml.org/lkml/2014/2/28/404 for details.
-    '')
-  ];
+  devices = map (nr: "zram${toString nr}") (lib.range 0 (cfg.swapDevices - 1));
 
 in
 
 {
 
+  imports = [
+    (lib.mkRemovedOptionModule [ "zramSwap" "numDevices" ] "Using ZRAM devices as general purpose ephemeral block devices is no longer supported")
+  ];
+
   ###### interface
 
   options = {
 
     zramSwap = {
 
-      enable = mkOption {
+      enable = lib.mkOption {
         default = false;
-        type = types.bool;
+        type = lib.types.bool;
         description = lib.mdDoc ''
           Enable in-memory compressed devices and swap space provided by the zram
           kernel module.
@@ -49,49 +31,38 @@ in
         '';
       };
 
-      numDevices = mkOption {
+      swapDevices = lib.mkOption {
         default = 1;
-        type = types.int;
-        description = lib.mdDoc ''
-          Number of zram devices to create. See also
-          `zramSwap.swapDevices`
-        '';
-      };
-
-      swapDevices = mkOption {
-        default = null;
-        example = 1;
-        type = with types; nullOr int;
+        type = lib.types.int;
         description = lib.mdDoc ''
-          Number of zram devices to be used as swap. Must be
-          `<= zramSwap.numDevices`.
-          Default is same as `zramSwap.numDevices`, recommended is 1.
+          Number of zram devices to be used as swap, recommended is 1.
         '';
       };
 
-      memoryPercent = mkOption {
+      memoryPercent = lib.mkOption {
         default = 50;
-        type = types.int;
+        type = lib.types.int;
         description = lib.mdDoc ''
-          Maximum amount of memory that can be used by the zram swap devices
+          Maximum total amount of memory that can be stored in the zram swap devices
           (as a percentage of your total memory). Defaults to 1/2 of your total
-          RAM. Run `zramctl` to check how good memory is
-          compressed.
+          RAM. Run `zramctl` to check how good memory is compressed.
+          This doesn't define how much memory will be used by the zram swap devices.
         '';
       };
 
-      memoryMax = mkOption {
+      memoryMax = lib.mkOption {
         default = null;
-        type = with types; nullOr int;
+        type = with lib.types; nullOr int;
         description = lib.mdDoc ''
-          Maximum total amount of memory (in bytes) that can be used by the zram
+          Maximum total amount of memory (in bytes) that can be stored in the zram
           swap devices.
+          This doesn't define how much memory will be used by the zram swap devices.
         '';
       };
 
-      priority = mkOption {
+      priority = lib.mkOption {
         default = 5;
-        type = types.int;
+        type = lib.types.int;
         description = lib.mdDoc ''
           Priority of the zram swap devices. It should be a number higher than
           the priority of your disk-based swap devices (so that the system will
@@ -99,25 +70,41 @@ in
         '';
       };
 
-      algorithm = mkOption {
+      algorithm = lib.mkOption {
         default = "zstd";
         example = "lz4";
-        type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str;
-        description = ''
-          Compression algorithm. <literal>lzo</literal> has good compression,
-          but is slow. <literal>lz4</literal> has bad compression, but is fast.
-          <literal>zstd</literal> is both good compression and fast, but requires newer kernel.
+        type = with lib.types; either (enum [ "lzo" "lz4" "zstd" ]) str;
+        description = lib.mdDoc ''
+          Compression algorithm. `lzo` has good compression,
+          but is slow. `lz4` has bad compression, but is fast.
+          `zstd` is both good compression and fast, but requires newer kernel.
           You can check what other algorithms are supported by your zram device with
-          <programlisting>cat /sys/class/block/zram*/comp_algorithm</programlisting>
+          {command}`cat /sys/class/block/zram*/comp_algorithm`
+        '';
+      };
+
+      writebackDevice = lib.mkOption {
+        default = null;
+        example = "/dev/zvol/tarta-zoot/swap-writeback";
+        type = lib.types.nullOr lib.types.path;
+        description = lib.mdDoc ''
+          Write incompressible pages to this device,
+          as there's no gain from keeping them in RAM.
         '';
       };
     };
 
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.writebackDevice == null || cfg.swapDevices <= 1;
+        message = "A single writeback device cannot be shared among multiple zram devices";
+      }
+    ];
 
-    inherit warnings;
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isModule "ZRAM")
@@ -127,76 +114,27 @@ in
     # once in stage 2 boot, and again when the zram-reloader service starts.
     # boot.kernelModules = [ "zram" ];
 
-    boot.extraModprobeConfig = ''
-      options zram num_devices=${toString cfg.numDevices}
-    '';
-
-    services.udev.extraRules = ''
-      KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd"
-    '';
-
-    systemd.services =
-      let
-        createZramInitService = dev:
-          nameValuePair "zram-init-${dev}" {
-            description = "Init swap on zram-based device ${dev}";
-            after = [ "dev-${dev}.device" "zram-reloader.service" ];
-            requires = [ "dev-${dev}.device" "zram-reloader.service" ];
-            before = [ "dev-${dev}.swap" ];
-            requiredBy = [ "dev-${dev}.swap" ];
-            unitConfig.DefaultDependencies = false; # needed to prevent a cycle
-            serviceConfig = {
-              Type = "oneshot";
-              RemainAfterExit = true;
-              ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'";
-            };
-            script = ''
-              set -euo pipefail
-
-              # Calculate memory to use for zram
-              mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / {
-                  value=int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024);
-                    ${lib.optionalString (cfg.memoryMax != null) ''
-                      memory_max=int(${toString cfg.memoryMax}/${toString devicesCount});
-                      if (value > memory_max) { value = memory_max }
-                    ''}
-                  print value
-              }' /proc/meminfo)
-
-              ${pkgs.util-linux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev}
-              ${pkgs.util-linux}/sbin/mkswap /dev/${dev}
-            '';
-            restartIfChanged = false;
-          };
-      in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader"
-        {
-          description = "Reload zram kernel module when number of devices changes";
-          wants = [ "systemd-udevd.service" ];
-          after = [ "systemd-udevd.service" ];
-          unitConfig.DefaultDependencies = false; # needed to prevent a cycle
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = true;
-            ExecStartPre = "${modprobe} -r zram";
-            ExecStart = "${modprobe} zram";
-            ExecStop = "${modprobe} -r zram";
-          };
-          restartTriggers = [
-            cfg.numDevices
-            cfg.algorithm
-            cfg.memoryPercent
-          ];
-          restartIfChanged = true;
-        })]);
-
-    swapDevices =
-      let
-        useZramSwap = dev:
-          {
-            device = "/dev/${dev}";
-            priority = cfg.priority;
-          };
-      in map useZramSwap devices;
+    systemd.packages = [ pkgs.zram-generator ];
+    systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap
+
+    environment.etc."systemd/zram-generator.conf".source =
+      (pkgs.formats.ini { }).generate "zram-generator.conf" (lib.listToAttrs
+        (builtins.map
+          (dev: {
+            name = dev;
+            value =
+              let
+                size = "${toString cfg.memoryPercent} / 100 * ram";
+              in
+              {
+                zram-size = if cfg.memoryMax != null then "min(${size}, ${toString cfg.memoryMax} / 1024 / 1024)" else size;
+                compression-algorithm = cfg.algorithm;
+                swap-priority = cfg.priority;
+              } // lib.optionalAttrs (cfg.writebackDevice != null) {
+                writeback-device = cfg.writebackDevice;
+              };
+          })
+          devices));
 
   };
 
diff --git a/nixpkgs/nixos/modules/hardware/all-firmware.nix b/nixpkgs/nixos/modules/hardware/all-firmware.nix
index 2d5a0007ff01..75247286368b 100644
--- a/nixpkgs/nixos/modules/hardware/all-firmware.nix
+++ b/nixpkgs/nixos/modules/hardware/all-firmware.nix
@@ -65,8 +65,6 @@ in {
       ] ++ optional pkgs.stdenv.hostPlatform.isAarch raspberrypiWirelessFirmware
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
         rtl8723bs-firmware
-      ] ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "5.16") [
-        rtw89-firmware
       ];
       hardware.wirelessRegulatoryDatabase = true;
     })
diff --git a/nixpkgs/nixos/modules/hardware/brillo.nix b/nixpkgs/nixos/modules/hardware/brillo.nix
index e970c9480998..612061718fad 100644
--- a/nixpkgs/nixos/modules/hardware/brillo.nix
+++ b/nixpkgs/nixos/modules/hardware/brillo.nix
@@ -7,14 +7,13 @@ in
 {
   options = {
     hardware.brillo = {
-      enable = mkEnableOption ''
-        Enable brillo in userspace.
-        This will allow brightness control from users in the video group.
-      '';
+      enable = mkEnableOption (lib.mdDoc ''
+        brillo in userspace.
+        This will allow brightness control from users in the video group
+      '');
     };
   };
 
-
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.brillo ];
     environment.systemPackages = [ pkgs.brillo ];
diff --git a/nixpkgs/nixos/modules/hardware/ckb-next.nix b/nixpkgs/nixos/modules/hardware/ckb-next.nix
index 287d287a775c..79977939eec8 100644
--- a/nixpkgs/nixos/modules/hardware/ckb-next.nix
+++ b/nixpkgs/nixos/modules/hardware/ckb-next.nix
@@ -13,7 +13,7 @@ in
     ];
 
     options.hardware.ckb-next = {
-      enable = mkEnableOption "the Corsair keyboard/mouse driver";
+      enable = mkEnableOption (lib.mdDoc "the Corsair keyboard/mouse driver");
 
       gid = mkOption {
         type = types.nullOr types.int;
@@ -48,6 +48,6 @@ in
     };
 
     meta = {
-      maintainers = with lib.maintainers; [ superherointj ];
+      maintainers = with lib.maintainers; [ ];
     };
   }
diff --git a/nixpkgs/nixos/modules/hardware/corectrl.nix b/nixpkgs/nixos/modules/hardware/corectrl.nix
index 3185f6486c71..965cbe0267e0 100644
--- a/nixpkgs/nixos/modules/hardware/corectrl.nix
+++ b/nixpkgs/nixos/modules/hardware/corectrl.nix
@@ -7,20 +7,20 @@ let
 in
 {
   options.programs.corectrl = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       A tool to overclock amd graphics cards and processors.
       Add your user to the corectrl group to run corectrl without needing to enter your password
-    '';
+    '');
 
     gpuOverclock = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         true
-      '';
+      '');
       ppfeaturemask = mkOption {
         type = types.str;
         default = "0xfffd7fff";
         example = "0xffffffff";
-        description = ''
+        description = lib.mdDoc ''
           Sets the `amdgpu.ppfeaturemask` kernel option.
           In particular, it is used here to set the overdrive bit.
           Default is `0xfffd7fff` as it is less likely to cause flicker issues.
diff --git a/nixpkgs/nixos/modules/hardware/cpu/amd-sev.nix b/nixpkgs/nixos/modules/hardware/cpu/amd-sev.nix
index 32fed2c484d4..28ee07f005ba 100644
--- a/nixpkgs/nixos/modules/hardware/cpu/amd-sev.nix
+++ b/nixpkgs/nixos/modules/hardware/cpu/amd-sev.nix
@@ -6,19 +6,19 @@ let
 in
   with lib; {
     options.hardware.cpu.amd.sev = {
-      enable = mkEnableOption "access to the AMD SEV device";
+      enable = mkEnableOption (lib.mdDoc "access to the AMD SEV device");
       user = mkOption {
-        description = "Owner to assign to the SEV device.";
+        description = lib.mdDoc "Owner to assign to the SEV device.";
         type = types.str;
         default = "root";
       };
       group = mkOption {
-        description = "Group to assign to the SEV device.";
+        description = lib.mdDoc "Group to assign to the SEV device.";
         type = types.str;
         default = defaultGroup;
       };
       mode = mkOption {
-        description = "Mode to set for the SEV device.";
+        description = lib.mdDoc "Mode to set for the SEV device.";
         type = types.str;
         default = "0660";
       };
diff --git a/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix b/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix
index 76664133a08f..38a484cb126e 100644
--- a/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix
+++ b/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix
@@ -20,7 +20,7 @@ in
   };
 
   options.hardware.cpu.intel.sgx.provision = {
-    enable = mkEnableOption "access to the Intel SGX provisioning device";
+    enable = mkEnableOption (lib.mdDoc "access to the Intel SGX provisioning device");
     user = mkOption {
       description = lib.mdDoc "Owner to assign to the SGX provisioning device.";
       type = types.str;
diff --git a/nixpkgs/nixos/modules/hardware/device-tree.nix b/nixpkgs/nixos/modules/hardware/device-tree.nix
index 55852776220d..c568f52ab677 100644
--- a/nixpkgs/nixos/modules/hardware/device-tree.nix
+++ b/nixpkgs/nixos/modules/hardware/device-tree.nix
@@ -14,6 +14,15 @@ let
         '';
       };
 
+      filter = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "*rpi*.dtb";
+        description = lib.mdDoc ''
+          Only apply to .dtb files matching glob expression.
+        '';
+      };
+
       dtsFile = mkOption {
         type = types.nullOr types.path;
         description = lib.mdDoc ''
@@ -56,24 +65,7 @@ let
     };
   };
 
-  # this requires kernel package
-  dtbsWithSymbols = pkgs.stdenv.mkDerivation {
-    name = "dtbs-with-symbols";
-    inherit (cfg.kernelPackage) src nativeBuildInputs depsBuildBuild;
-    patches = map (patch: patch.patch) cfg.kernelPackage.kernelPatches;
-    buildPhase = ''
-      patchShebangs scripts/*
-      substituteInPlace scripts/Makefile.lib \
-        --replace 'DTC_FLAGS += $(DTC_FLAGS_$(basetarget))' 'DTC_FLAGS += $(DTC_FLAGS_$(basetarget)) -@'
-      make ${pkgs.stdenv.hostPlatform.linux-kernel.baseConfig} ARCH="${pkgs.stdenv.hostPlatform.linuxArch}"
-      make dtbs ARCH="${pkgs.stdenv.hostPlatform.linuxArch}"
-    '';
-    installPhase = ''
-      make dtbs_install INSTALL_DTBS_PATH=$out/dtbs  ARCH="${pkgs.stdenv.hostPlatform.linuxArch}"
-    '';
-  };
-
-  filterDTBs = src: if isNull cfg.filter
+  filterDTBs = src: if cfg.filter == null
     then "${src}/dtbs"
     else
       pkgs.runCommand "dtbs-filtered" {} ''
@@ -83,6 +75,8 @@ let
           | xargs -0 cp -v --no-preserve=mode --target-directory $out --parents
       '';
 
+  filteredDTBs = filterDTBs cfg.kernelPackage;
+
   # Compile single Device Tree overlay source
   # file (.dts) into its compiled variant (.dtbo)
   compileDTS = name: f: pkgs.callPackage({ stdenv, dtc }: stdenv.mkDerivation {
@@ -99,8 +93,8 @@ let
   # Fill in `dtboFile` for each overlay if not set already.
   # Existence of one of these is guarded by assertion below
   withDTBOs = xs: flip map xs (o: o // { dtboFile =
-    if isNull o.dtboFile then
-      if !isNull o.dtsFile then compileDTS o.name o.dtsFile
+    if o.dtboFile == null then
+      if o.dtsFile != null then compileDTS o.name o.dtsFile
       else compileDTS o.name (pkgs.writeText "dts" o.dtsText)
     else o.dtboFile; } );
 
@@ -165,6 +159,7 @@ in
           '';
           type = types.listOf (types.coercedTo types.path (path: {
             name = baseNameOf path;
+            filter = null;
             dtboFile = path;
           }) overlayType);
           description = lib.mdDoc ''
@@ -176,7 +171,7 @@ in
           default = null;
           type = types.nullOr types.path;
           internal = true;
-          description = ''
+          description = lib.mdDoc ''
             A path containing the result of applying `overlays` to `kernelPackage`.
           '';
         };
@@ -186,7 +181,7 @@ in
   config = mkIf (cfg.enable) {
 
     assertions = let
-      invalidOverlay = o: isNull o.dtsFile && isNull o.dtsText && isNull o.dtboFile;
+      invalidOverlay = o: (o.dtsFile == null) && (o.dtsText == null) && (o.dtboFile == null);
     in lib.singleton {
       assertion = lib.all (o: !invalidOverlay o) cfg.overlays;
       message = ''
@@ -197,7 +192,7 @@ in
     };
 
     hardware.deviceTree.package = if (cfg.overlays != [])
-      then pkgs.deviceTree.applyOverlays (filterDTBs dtbsWithSymbols) (withDTBOs cfg.overlays)
-      else (filterDTBs cfg.kernelPackage);
+      then pkgs.deviceTree.applyOverlays filteredDTBs (withDTBOs cfg.overlays)
+      else filteredDTBs;
   };
 }
diff --git a/nixpkgs/nixos/modules/hardware/flipperzero.nix b/nixpkgs/nixos/modules/hardware/flipperzero.nix
new file mode 100644
index 000000000000..82f9b76fa3a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/flipperzero.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.flipperzero;
+
+in
+
+{
+  options.hardware.flipperzero.enable = mkEnableOption (mdDoc "udev rules and software for Flipper Zero devices");
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.qFlipper ];
+    services.udev.packages = [ pkgs.qFlipper ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/hardware/flirc.nix b/nixpkgs/nixos/modules/hardware/flirc.nix
index 94ec715b9fa5..2fe40db947e4 100644
--- a/nixpkgs/nixos/modules/hardware/flirc.nix
+++ b/nixpkgs/nixos/modules/hardware/flirc.nix
@@ -3,7 +3,7 @@ let
   cfg = config.hardware.flirc;
 in
 {
-  options.hardware.flirc.enable = lib.mkEnableOption "software to configure a Flirc USB device";
+  options.hardware.flirc.enable = lib.mkEnableOption (lib.mdDoc "software to configure a Flirc USB device");
 
   config = lib.mkIf cfg.enable {
     environment.systemPackages = [ pkgs.flirc ];
diff --git a/nixpkgs/nixos/modules/hardware/gkraken.nix b/nixpkgs/nixos/modules/hardware/gkraken.nix
index 97d15369db0a..f427fec0a7cc 100644
--- a/nixpkgs/nixos/modules/hardware/gkraken.nix
+++ b/nixpkgs/nixos/modules/hardware/gkraken.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.hardware.gkraken = {
-    enable = mkEnableOption "gkraken's udev rules for NZXT AIO liquid coolers";
+    enable = mkEnableOption (lib.mdDoc "gkraken's udev rules for NZXT AIO liquid coolers");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix b/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix
index 4c1d0cc5b2a3..68e1e5f74e2e 100644
--- a/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix
+++ b/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix
@@ -8,7 +8,7 @@ let
   # 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):
+  # the scdaemon.udev file was last committed on 2021-01-05 (7817a03):
   scdaemonUdevRev = "01898735a015541e3ffb43c7245ac1e612f40836";
 
   scdaemonRules = pkgs.fetchurl {
@@ -28,7 +28,7 @@ let
   cfg = config.hardware.gpgSmartcards;
 in {
   options.hardware.gpgSmartcards = {
-    enable = mkEnableOption "udev rules for gnupg smart cards";
+    enable = mkEnableOption (lib.mdDoc "udev rules for gnupg smart cards");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/hardware/i2c.nix b/nixpkgs/nixos/modules/hardware/i2c.nix
index 0b57cd1c28df..9a5a2e44813e 100644
--- a/nixpkgs/nixos/modules/hardware/i2c.nix
+++ b/nixpkgs/nixos/modules/hardware/i2c.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -8,11 +8,11 @@ in
 
 {
   options.hardware.i2c = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       i2c devices support. By default access is granted to users in the "i2c"
       group (will be created if non-existent) and any user with a seat, meaning
       logged on the computer locally.
-    '';
+    '');
 
     group = mkOption {
       type = types.str;
@@ -31,10 +31,14 @@ in
       i2c = { };
     };
 
-    services.udev.extraRules = ''
-      # allow group ${cfg.group} and users with a seat use of i2c devices
-      ACTION=="add", KERNEL=="i2c-[0-9]*", TAG+="uaccess", GROUP="${cfg.group}", MODE="660"
-    '';
+    services.udev.packages = lib.singleton (pkgs.writeTextFile
+      { name = "i2c-udev-rules";
+        text = ''
+          # allow group ${cfg.group} and users with a seat use of i2c devices
+          ACTION=="add", KERNEL=="i2c-[0-9]*", TAG+="uaccess", GROUP="${cfg.group}", MODE="660"
+        '';
+        destination = "/etc/udev/rules.d/70-i2c.rules";
+      });
 
   };
 
diff --git a/nixpkgs/nixos/modules/hardware/keyboard/qmk.nix b/nixpkgs/nixos/modules/hardware/keyboard/qmk.nix
new file mode 100644
index 000000000000..df3bcaeccd2e
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/keyboard/qmk.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.keyboard.qmk;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
+in
+{
+  options.hardware.keyboard.qmk = {
+    enable = mkEnableOption (mdDoc "non-root access to the firmware of QMK keyboards");
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.qmk-udev-rules ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/hardware/keyboard/teck.nix b/nixpkgs/nixos/modules/hardware/keyboard/teck.nix
index 091ddb81962e..8376c6b9c50b 100644
--- a/nixpkgs/nixos/modules/hardware/keyboard/teck.nix
+++ b/nixpkgs/nixos/modules/hardware/keyboard/teck.nix
@@ -1,16 +1,16 @@
 { config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.hardware.keyboard.teck;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
 in
 {
   options.hardware.keyboard.teck = {
-    enable = mkEnableOption "non-root access to the firmware of TECK keyboards";
+    enable = mkEnableOption (mdDoc "non-root access to the firmware of TECK keyboards");
   };
 
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.teck-udev-rules ];
   };
 }
-
diff --git a/nixpkgs/nixos/modules/hardware/keyboard/uhk.nix b/nixpkgs/nixos/modules/hardware/keyboard/uhk.nix
index bf2d739c3a97..17baff83d886 100644
--- a/nixpkgs/nixos/modules/hardware/keyboard/uhk.nix
+++ b/nixpkgs/nixos/modules/hardware/keyboard/uhk.nix
@@ -1,17 +1,18 @@
 { config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.hardware.keyboard.uhk;
+  inherit (lib) mdDoc mkEnableOption mkIf;
+
 in
 {
   options.hardware.keyboard.uhk = {
-    enable = mkEnableOption ''
-    non-root access to the firmware of UHK keyboards.
+    enable = mkEnableOption (mdDoc ''
+      non-root access to the firmware of UHK keyboards.
       You need it when you want to flash a new firmware on the keyboard.
       Access to the keyboard is granted to users in the "input" group.
       You may want to install the uhk-agent package.
-    '';
+    '');
 
   };
 
diff --git a/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix b/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix
index 5bf4022cdc43..a04b67b5c8d0 100644
--- a/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix
@@ -1,21 +1,18 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkOption mkIf types;
   cfg = config.hardware.keyboard.zsa;
+  inherit (lib) mkEnableOption mkIf mdDoc;
+
 in
 {
   options.hardware.keyboard.zsa = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        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.
-        You may want to install the wally-cli package.
-      '';
-    };
+    enable = mkEnableOption (mdDoc ''
+      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.
+      You may want to install the wally-cli package.
+    '');
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/hardware/ksm.nix b/nixpkgs/nixos/modules/hardware/ksm.nix
index ba7a1c12169f..82d94e6ab57c 100644
--- a/nixpkgs/nixos/modules/hardware/ksm.nix
+++ b/nixpkgs/nixos/modules/hardware/ksm.nix
@@ -11,7 +11,7 @@ in {
   ];
 
   options.hardware.ksm = {
-    enable = mkEnableOption "Kernel Same-Page Merging";
+    enable = mkEnableOption (lib.mdDoc "Kernel Same-Page Merging");
     sleep = mkOption {
       type = types.nullOr types.int;
       default = null;
diff --git a/nixpkgs/nixos/modules/hardware/ledger.nix b/nixpkgs/nixos/modules/hardware/ledger.nix
index 41abe74315a0..fcce4f61a870 100644
--- a/nixpkgs/nixos/modules/hardware/ledger.nix
+++ b/nixpkgs/nixos/modules/hardware/ledger.nix
@@ -6,7 +6,7 @@ let
   cfg = config.hardware.ledger;
 
 in {
-  options.hardware.ledger.enable = mkEnableOption "udev rules for Ledger devices";
+  options.hardware.ledger.enable = mkEnableOption (lib.mdDoc "udev rules for Ledger devices");
 
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.ledger-udev-rules ];
diff --git a/nixpkgs/nixos/modules/hardware/logitech.nix b/nixpkgs/nixos/modules/hardware/logitech.nix
index 70ca59a7dd3f..9b06eb8a8b01 100644
--- a/nixpkgs/nixos/modules/hardware/logitech.nix
+++ b/nixpkgs/nixos/modules/hardware/logitech.nix
@@ -19,7 +19,7 @@ in
   options.hardware.logitech = {
 
     lcd = {
-      enable = mkEnableOption "Logitech LCD Devices";
+      enable = mkEnableOption (lib.mdDoc "Logitech LCD Devices");
 
       startWhenNeeded = mkOption {
         type = types.bool;
@@ -41,7 +41,7 @@ in
     };
 
     wireless = {
-      enable = mkEnableOption "Logitech Wireless Devices";
+      enable = mkEnableOption (lib.mdDoc "Logitech Wireless Devices");
 
       enableGraphical = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/hardware/nitrokey.nix b/nixpkgs/nixos/modules/hardware/nitrokey.nix
index fa9dd4d6d8f9..e2e88a8eade4 100644
--- a/nixpkgs/nixos/modules/hardware/nitrokey.nix
+++ b/nixpkgs/nixos/modules/hardware/nitrokey.nix
@@ -22,6 +22,6 @@ in
   };
 
   config = mkIf cfg.enable {
-    services.udev.packages = [ pkgs.nitrokey-udev-rules ];
+    services.udev.packages = [ pkgs.libnitrokey ];
   };
 }
diff --git a/nixpkgs/nixos/modules/hardware/opengl.nix b/nixpkgs/nixos/modules/hardware/opengl.nix
index dd30bd92b457..9108bcbd1652 100644
--- a/nixpkgs/nixos/modules/hardware/opengl.nix
+++ b/nixpkgs/nixos/modules/hardware/opengl.nix
@@ -26,9 +26,7 @@ in
 
   imports = [
     (mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ])
-    (mkRemovedOptionModule [ "hardware" "opengl" "s3tcSupport" ] ''
-      S3TC support is now always enabled in Mesa.
-    '')
+    (mkRemovedOptionModule [ "hardware" "opengl" "s3tcSupport" ] "S3TC support is now always enabled in Mesa.")
   ];
 
   options = {
@@ -71,7 +69,7 @@ in
       package = mkOption {
         type = types.package;
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           The package that provides the OpenGL implementation.
         '';
       };
@@ -79,9 +77,9 @@ in
       package32 = mkOption {
         type = types.package;
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           The package that provides the 32-bit OpenGL implementation on
-          64-bit systems. Used when <option>driSupport32Bit</option> is
+          64-bit systems. Used when {option}`driSupport32Bit` is
           set.
         '';
       };
@@ -89,21 +87,28 @@ in
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExpression "with pkgs; [ vaapiIntel libvdpau-va-gl vaapiVdpau intel-ocl ]";
+        example = literalExpression "with pkgs; [ intel-media-driver intel-ocl vaapiIntel ]";
         description = lib.mdDoc ''
-          Additional packages to add to OpenGL drivers. This can be used
-          to add OpenCL drivers, VA-API/VDPAU drivers etc.
+          Additional packages to add to OpenGL drivers.
+          This can be used to add OpenCL drivers, VA-API/VDPAU drivers etc.
+
+          ::: {.note}
+          intel-media-driver supports hardware Broadwell (2014) or newer. Older hardware should use the mostly unmaintained vaapiIntel driver.
+          :::
         '';
       };
 
       extraPackages32 = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExpression "with pkgs.pkgsi686Linux; [ vaapiIntel libvdpau-va-gl vaapiVdpau ]";
+        example = literalExpression "with pkgs.pkgsi686Linux; [ intel-media-driver vaapiIntel ]";
         description = lib.mdDoc ''
-          Additional packages to add to 32-bit OpenGL drivers on
-          64-bit systems. Used when {option}`driSupport32Bit` is
-          set. This can be used to add OpenCL drivers, VA-API/VDPAU drivers etc.
+          Additional packages to add to 32-bit OpenGL drivers on 64-bit systems.
+          Used when {option}`driSupport32Bit` is set. This can be used to add OpenCL drivers, VA-API/VDPAU drivers etc.
+
+          ::: {.note}
+          intel-media-driver supports hardware Broadwell (2014) or newer. Older hardware should use the mostly unmaintained vaapiIntel driver.
+          :::
         '';
       };
 
@@ -111,11 +116,11 @@ in
         type = types.bool;
         internal = true;
         default = false;
-        description = ''
-          Whether the <literal>LD_LIBRARY_PATH</literal> environment variable
+        description = lib.mdDoc ''
+          Whether the `LD_LIBRARY_PATH` environment variable
           should be set to the locations of driver libraries. Drivers which
           rely on overriding libraries should set this to true. Drivers which
-          support <literal>libglvnd</literal> and other dispatch libraries
+          support `libglvnd` and other dispatch libraries
           instead of overriding libraries should not set this.
         '';
       };
@@ -124,7 +129,6 @@ in
   };
 
   config = mkIf cfg.enable {
-
     assertions = [
       { assertion = cfg.driSupport32Bit -> pkgs.stdenv.isx86_64;
         message = "Option driSupport32Bit only makes sense on a 64-bit system.";
diff --git a/nixpkgs/nixos/modules/hardware/openrazer.nix b/nixpkgs/nixos/modules/hardware/openrazer.nix
index 315a4a6824bb..aaa4000e758f 100644
--- a/nixpkgs/nixos/modules/hardware/openrazer.nix
+++ b/nixpkgs/nixos/modules/hardware/openrazer.nix
@@ -49,9 +49,9 @@ in
 {
   options = {
     hardware.openrazer = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         OpenRazer drivers and userspace daemon.
-      '';
+      '');
 
       verboseLogging = mkOption {
         type = types.bool;
@@ -110,7 +110,7 @@ in
     boot.extraModulePackages = [ kernelPackages.openrazer ];
     boot.kernelModules = drivers;
 
-    # Makes the man pages available so you can succesfully run
+    # Makes the man pages available so you can successfully run
     # > systemctl --user help openrazer-daemon
     environment.systemPackages = [ pkgs.python3Packages.openrazer-daemon.man ];
 
diff --git a/nixpkgs/nixos/modules/hardware/opentabletdriver.nix b/nixpkgs/nixos/modules/hardware/opentabletdriver.nix
index 6c5ca3d949e8..e3f418abce4f 100644
--- a/nixpkgs/nixos/modules/hardware/opentabletdriver.nix
+++ b/nixpkgs/nixos/modules/hardware/opentabletdriver.nix
@@ -61,7 +61,7 @@ in
 
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${cfg.package}/bin/otd-daemon -c ${cfg.package}/lib/OpenTabletDriver/Configurations";
+        ExecStart = "${cfg.package}/bin/otd-daemon";
         Restart = "on-failure";
       };
     };
diff --git a/nixpkgs/nixos/modules/hardware/printers.nix b/nixpkgs/nixos/modules/hardware/printers.nix
index 64c29bb0a5b3..846ff6f3fb4f 100644
--- a/nixpkgs/nixos/modules/hardware/printers.nix
+++ b/nixpkgs/nixos/modules/hardware/printers.nix
@@ -100,7 +100,7 @@ in {
               default = {};
               description = lib.mdDoc ''
                 Sets PPD options for the printer.
-                {command}`lpoptions [-p printername] -l` shows suported PPD options for the given printer.
+                {command}`lpoptions [-p printername] -l` shows supported PPD options for the given printer.
               '';
             };
           };
@@ -110,21 +110,26 @@ in {
   };
 
   config = mkIf (cfg.ensurePrinters != [] && config.services.printing.enable) {
-    systemd.services.ensure-printers = let
-      cupsUnit = if config.services.printing.startWhenNeeded then "cups.socket" else "cups.service";
-    in {
+    systemd.services.ensure-printers = {
       description = "Ensure NixOS-configured CUPS printers";
       wantedBy = [ "multi-user.target" ];
-      requires = [ cupsUnit ];
-      after = [ cupsUnit ];
+      wants = [ "cups.service" ];
+      after = [ "cups.service" ];
 
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
       };
 
-      script = concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters
-        + optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter);
+      script = concatStringsSep "\n" [
+        (concatMapStrings ensurePrinter cfg.ensurePrinters)
+        (optionalString (cfg.ensureDefaultPrinter != null)
+          (ensureDefaultPrinter cfg.ensureDefaultPrinter))
+        # Note: if cupsd is "stateless" the service can't be stopped,
+        # otherwise the configuration will be wiped on the next start.
+        (optionalString (with config.services.printing; startWhenNeeded && !stateless)
+          "systemctl stop cups.service")
+      ];
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/hardware/raid/hpsa.nix b/nixpkgs/nixos/modules/hardware/raid/hpsa.nix
index 120348a74bfb..2934cd19a8c1 100644
--- a/nixpkgs/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixpkgs/nixos/modules/hardware/raid/hpsa.nix
@@ -48,7 +48,7 @@ in {
 
   options = {
     hardware.raid.HPSmartArray = {
-      enable = mkEnableOption "HP Smart Array kernel modules and CLI utility";
+      enable = mkEnableOption (lib.mdDoc "HP Smart Array kernel modules and CLI utility");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/hardware/saleae-logic.nix b/nixpkgs/nixos/modules/hardware/saleae-logic.nix
index 02d234cd3f05..f144814a06b7 100644
--- a/nixpkgs/nixos/modules/hardware/saleae-logic.nix
+++ b/nixpkgs/nixos/modules/hardware/saleae-logic.nix
@@ -5,7 +5,7 @@ let
 in
 {
   options.hardware.saleae-logic = {
-    enable = lib.mkEnableOption "udev rules for Saleae Logic devices";
+    enable = lib.mkEnableOption (lib.mdDoc "udev rules for Saleae Logic devices");
 
     package = lib.mkOption {
       type = lib.types.package;
diff --git a/nixpkgs/nixos/modules/hardware/sata.nix b/nixpkgs/nixos/modules/hardware/sata.nix
index bac24236f7dc..5330ba9268b5 100644
--- a/nixpkgs/nixos/modules/hardware/sata.nix
+++ b/nixpkgs/nixos/modules/hardware/sata.nix
@@ -36,7 +36,7 @@ in
   meta.maintainers = with lib.maintainers; [ peterhoeg ];
 
   options.hardware.sata.timeout = {
-    enable = mkEnableOption "SATA drive timeouts";
+    enable = mkEnableOption (lib.mdDoc "SATA drive timeouts");
 
     deciSeconds = mkOption {
       example = 70;
diff --git a/nixpkgs/nixos/modules/hardware/sensor/hddtemp.nix b/nixpkgs/nixos/modules/hardware/sensor/hddtemp.nix
index b69d012b4d09..1a3d211b858b 100644
--- a/nixpkgs/nixos/modules/hardware/sensor/hddtemp.nix
+++ b/nixpkgs/nixos/modules/hardware/sensor/hddtemp.nix
@@ -43,7 +43,7 @@ in
       };
 
       unit = mkOption {
-        description = lib.mdDoc "Celcius or Fahrenheit";
+        description = lib.mdDoc "Celsius or Fahrenheit";
         type = types.enum [ "C" "F" ];
         default = "C";
       };
diff --git a/nixpkgs/nixos/modules/hardware/system-76.nix b/nixpkgs/nixos/modules/hardware/system-76.nix
index 21cab4a3787c..3fb2c10a6e3b 100644
--- a/nixpkgs/nixos/modules/hardware/system-76.nix
+++ b/nixpkgs/nixos/modules/hardware/system-76.nix
@@ -57,7 +57,7 @@ let
 in {
   options = {
     hardware.system76 = {
-      enableAll = mkEnableOption "all recommended configuration for system76 systems";
+      enableAll = mkEnableOption (lib.mdDoc "all recommended configuration for system76 systems");
 
       firmware-daemon.enable = mkOption {
         default = cfg.enableAll;
diff --git a/nixpkgs/nixos/modules/hardware/tuxedo-keyboard.nix b/nixpkgs/nixos/modules/hardware/tuxedo-keyboard.nix
index 7bcabde48900..3ae876bd1f18 100644
--- a/nixpkgs/nixos/modules/hardware/tuxedo-keyboard.nix
+++ b/nixpkgs/nixos/modules/hardware/tuxedo-keyboard.nix
@@ -8,23 +8,23 @@ let
 in
   {
     options.hardware.tuxedo-keyboard = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
           Enables the tuxedo-keyboard driver.
 
-          To configure the driver, pass the options to the <option>boot.kernelParams</option> configuration.
+          To configure the driver, pass the options to the {option}`boot.kernelParams` configuration.
           There are several parameters you can change. It's best to check at the source code description which options are supported.
-          You can find all the supported parameters at: <link xlink:href="https://github.com/tuxedocomputers/tuxedo-keyboard#kernelparam"/>
+          You can find all the supported parameters at: <https://github.com/tuxedocomputers/tuxedo-keyboard#kernelparam>
 
-          In order to use the <literal>custom</literal> lighting with the maximumg brightness and a color of <literal>0xff0a0a</literal> one would put pass <option>boot.kernelParams</option> like this:
+          In order to use the `custom` lighting with the maximumg brightness and a color of `0xff0a0a` one would put pass {option}`boot.kernelParams` like this:
 
-          <programlisting>
+          ```
           boot.kernelParams = [
            "tuxedo_keyboard.mode=0"
            "tuxedo_keyboard.brightness=255"
            "tuxedo_keyboard.color_left=0xff0a0a"
           ];
-          </programlisting>
-      '';
+          ```
+      '');
     };
 
     config = mkIf cfg.enable
diff --git a/nixpkgs/nixos/modules/hardware/ubertooth.nix b/nixpkgs/nixos/modules/hardware/ubertooth.nix
index e76fa45fea51..e2db2068d900 100644
--- a/nixpkgs/nixos/modules/hardware/ubertooth.nix
+++ b/nixpkgs/nixos/modules/hardware/ubertooth.nix
@@ -10,7 +10,7 @@ let
   };
 in {
   options.hardware.ubertooth = {
-    enable = mkEnableOption "Enable the Ubertooth software and its udev rules.";
+    enable = mkEnableOption (lib.mdDoc "Ubertooth software and its udev rules");
 
     group = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/hardware/uinput.nix b/nixpkgs/nixos/modules/hardware/uinput.nix
index 55e86bfa6bdb..15fa66b8d83c 100644
--- a/nixpkgs/nixos/modules/hardware/uinput.nix
+++ b/nixpkgs/nixos/modules/hardware/uinput.nix
@@ -4,7 +4,7 @@ let
   cfg = config.hardware.uinput;
 in {
   options.hardware.uinput = {
-    enable = lib.mkEnableOption "uinput support";
+    enable = lib.mkEnableOption (lib.mdDoc "uinput support");
   };
 
   config = lib.mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/hardware/usb-storage.nix b/nixpkgs/nixos/modules/hardware/usb-storage.nix
new file mode 100644
index 000000000000..9c1b7a125fd1
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/usb-storage.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+{
+  options.hardware.usbStorage.manageStartStop = mkOption {
+    type = types.bool;
+    default = true;
+    description = lib.mdDoc ''
+      Enable this option to gracefully spin-down external storage during shutdown.
+      If you suspect improper head parking after poweroff, install `smartmontools` and check
+      for the `Power-Off_Retract_Count` field for an increment.
+    '';
+  };
+
+  config = mkIf config.hardware.usbStorage.manageStartStop {
+    services.udev.extraRules = ''
+      ACTION=="add|change", SUBSYSTEM=="scsi_disk", DRIVERS=="usb-storage", ATTR{manage_start_stop}="1"
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix b/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix
index 76cb4c6ee9bf..ddd3f3ec7f32 100644
--- a/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix
+++ b/nixpkgs/nixos/modules/hardware/video/capture/mwprocapture.nix
@@ -12,7 +12,7 @@ in
 
 {
 
-  options.hardware.mwProCapture.enable = mkEnableOption "Magewell Pro Capture family kernel module";
+  options.hardware.mwProCapture.enable = mkEnableOption (lib.mdDoc "Magewell Pro Capture family kernel module");
 
   config = mkIf cfg.enable {
 
diff --git a/nixpkgs/nixos/modules/hardware/video/hidpi.nix b/nixpkgs/nixos/modules/hardware/video/hidpi.nix
deleted file mode 100644
index 1cb4470f1b3a..000000000000
--- a/nixpkgs/nixos/modules/hardware/video/hidpi.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-{ lib, pkgs, config, ...}:
-with lib;
-
-{
-  options.hardware.video.hidpi.enable = mkEnableOption "Font/DPI configuration optimized for HiDPI displays";
-
-  config = mkIf config.hardware.video.hidpi.enable {
-    console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-v32n.psf.gz";
-
-    # Needed when typing in passwords for full disk encryption
-    console.earlySetup = mkDefault true;
-    boot.loader.systemd-boot.consoleMode = mkDefault "1";
-
-
-    # Grayscale anti-aliasing for fonts
-    fonts.fontconfig.antialias = mkDefault true;
-    fonts.fontconfig.subpixel = {
-      rgba = mkDefault "none";
-      lcdfilter = mkDefault "none";
-    };
-
-    # TODO Find reasonable defaults X11 & wayland
-  };
-}
diff --git a/nixpkgs/nixos/modules/hardware/video/nvidia.nix b/nixpkgs/nixos/modules/hardware/video/nvidia.nix
index 8c6c97f9b21d..592d11d6476b 100644
--- a/nixpkgs/nixos/modules/hardware/video/nvidia.nix
+++ b/nixpkgs/nixos/modules/hardware/video/nvidia.nix
@@ -21,17 +21,21 @@ let
   pCfg = cfg.prime;
   syncCfg = pCfg.sync;
   offloadCfg = pCfg.offload;
-  primeEnabled = syncCfg.enable || offloadCfg.enable;
+  reverseSyncCfg = pCfg.reverseSync;
+  primeEnabled = syncCfg.enable || reverseSyncCfg.enable || offloadCfg.enable;
   nvidiaPersistencedEnabled =  cfg.nvidiaPersistenced;
   nvidiaSettings = cfg.nvidiaSettings;
   busIDType = types.strMatching "([[:print:]]+[\:\@][0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
+
+  ibtSupport = cfg.open || (nvidia_x11.ibtSupport or false);
 in
 
 {
   imports =
     [
       (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "enable" ] [ "hardware" "nvidia" "prime" "sync" "enable" ])
-      (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "sync" "allowExternalGpu" ])
+      (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "allowExternalGpu" ])
+      (mkRenamedOptionModule [ "hardware" "nvidia" "prime" "sync" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "allowExternalGpu" ])
       (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "nvidiaBusId" ] [ "hardware" "nvidia" "prime" "nvidiaBusId" ])
       (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "intelBusId" ] [ "hardware" "nvidia" "prime" "intelBusId" ])
     ];
@@ -104,16 +108,17 @@ in
       description = lib.mdDoc ''
         Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME.
         If enabled, the NVIDIA GPU will be always on and used for all rendering,
-        while enabling output to displays attached only to the integrated Intel GPU
-        without a multiplexer.
+        while enabling output to displays attached only to the integrated Intel/AMD
+        GPU without a multiplexer.
 
         Note that this option only has any effect if the "nvidia" driver is specified
         in {option}`services.xserver.videoDrivers`, and it should preferably
         be the only driver there.
 
-        If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
-        specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
-        {option}`hardware.nvidia.prime.intelBusId`).
+        If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
+        be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId` or
+        {option}`hardware.nvidia.prime.amdgpuBusId`).
 
         If you enable this, you may want to also enable kernel modesetting for the
         NVIDIA driver ({option}`hardware.nvidia.modesetting.enable`) in order
@@ -125,11 +130,11 @@ in
       '';
     };
 
-    hardware.nvidia.prime.sync.allowExternalGpu = mkOption {
+    hardware.nvidia.prime.allowExternalGpu = mkOption {
       type = types.bool;
       default = false;
       description = lib.mdDoc ''
-        Configure X to allow external NVIDIA GPUs when using optimus.
+        Configure X to allow external NVIDIA GPUs when using Prime [Reverse] sync optimus.
       '';
     };
 
@@ -139,9 +144,54 @@ in
       description = lib.mdDoc ''
         Enable render offload support using the NVIDIA proprietary driver via PRIME.
 
-        If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
-        specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
-        {option}`hardware.nvidia.prime.intelBusId`).
+        If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
+        be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId` or
+        {option}`hardware.nvidia.prime.amdgpuBusId`).
+      '';
+    };
+
+    hardware.nvidia.prime.offload.enableOffloadCmd = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Adds a `nvidia-offload` convenience script to {option}`environment.systemPackages`
+        for offloading programs to an nvidia device. To work, should have also enabled
+        {option}`hardware.nvidia.prime.offload.enable` or {option}`hardware.nvidia.prime.reverseSync.enable`.
+
+        Example usage `nvidia-offload sauerbraten_client`.
+      '';
+    };
+
+    hardware.nvidia.prime.reverseSync.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Warning: This feature is relatively new, depending on your system this might
+        work poorly. AMD support, especially so.
+        See: https://forums.developer.nvidia.com/t/the-all-new-outputsink-feature-aka-reverse-prime/129828
+
+        Enable NVIDIA Optimus support using the NVIDIA proprietary driver via reverse
+        PRIME. If enabled, the Intel/AMD GPU will be used for all rendering, while
+        enabling output to displays attached only to the NVIDIA GPU without a
+        multiplexer.
+
+        Note that this option only has any effect if the "nvidia" driver is specified
+        in {option}`services.xserver.videoDrivers`, and it should preferably
+        be the only driver there.
+
+        If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
+        be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId` or
+        {option}`hardware.nvidia.prime.amdgpuBusId`).
+
+        If you enable this, you may want to also enable kernel modesetting for the
+        NVIDIA driver ({option}`hardware.nvidia.modesetting.enable`) in order
+        to prevent tearing.
+
+        Note that this configuration will only be successful when a display manager
+        for which the {option}`services.xserver.displayManager.setupCommands`
+        option is supported is used.
       '';
     };
 
@@ -206,6 +256,13 @@ in
       }
 
       {
+        assertion = offloadCfg.enableOffloadCmd -> offloadCfg.enable || reverseSyncCfg.enable;
+        message = ''
+          Offload command requires offloading or reverse prime sync to be enabled.
+        '';
+      }
+
+      {
         assertion = primeEnabled -> pCfg.nvidiaBusId != "" && (pCfg.intelBusId != "" || pCfg.amdgpuBusId != "");
         message = ''
           When NVIDIA PRIME is enabled, the GPU bus IDs must configured.
@@ -218,8 +275,18 @@ in
       }
 
       {
+        assertion = (reverseSyncCfg.enable && pCfg.amdgpuBusId != "") -> versionAtLeast nvidia_x11.version "470.0";
+        message = "NVIDIA PRIME render offload for AMD APUs is currently only supported on versions >= 470 beta.";
+      }
+
+      {
         assertion = !(syncCfg.enable && offloadCfg.enable);
-        message = "Only one NVIDIA PRIME solution may be used at a time.";
+        message = "PRIME Sync and Offload cannot be both enabled";
+      }
+
+      {
+        assertion = !(syncCfg.enable && reverseSyncCfg.enable);
+        message = "PRIME Sync and PRIME Reverse Sync cannot be both enabled";
       }
 
       {
@@ -233,11 +300,8 @@ in
       }
 
       {
-        assertion = cfg.powerManagement.enable -> (
-          builtins.pathExists (cfg.package.out + "/bin/nvidia-sleep.sh") &&
-          builtins.pathExists (cfg.package.out + "/lib/systemd/system-sleep/nvidia")
-        );
-        message = "Required files for driver based power management don't exist.";
+        assertion = cfg.powerManagement.enable -> versionAtLeast nvidia_x11.version "430.09";
+        message = "Required files for driver based power management only exist on versions >= 430.09.";
       }
 
       {
@@ -260,13 +324,13 @@ in
     # - Configure the display manager to run specific `xrandr` commands which will
     #   configure/enable displays connected to the Intel iGPU / AMD APU.
 
-    services.xserver.useGlamor = mkDefault offloadCfg.enable;
+    # reverse sync implies offloading
+    hardware.nvidia.prime.offload.enable = mkDefault reverseSyncCfg.enable;
 
-    services.xserver.drivers = let
-    in optional primeEnabled {
+    services.xserver.drivers = optional primeEnabled {
       name = igpuDriver;
       display = offloadCfg.enable;
-      modules = optional (igpuDriver == "amdgpu") [ pkgs.xorg.xf86videoamdgpu ];
+      modules = optionals (igpuDriver == "amdgpu") [ pkgs.xorg.xf86videoamdgpu ];
       deviceSection = ''
         BusID "${igpuBusId}"
         ${optionalString (syncCfg.enable && igpuDriver != "amdgpu") ''Option "AccelMethod" "none"''}
@@ -278,7 +342,7 @@ in
       deviceSection = optionalString primeEnabled
         ''
           BusID "${pCfg.nvidiaBusId}"
-          ${optionalString syncCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
+          ${optionalString pCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
         '';
       screenSection =
         ''
@@ -295,19 +359,22 @@ in
 
     services.xserver.serverLayoutSection = optionalString syncCfg.enable ''
       Inactive "Device-${igpuDriver}[0]"
+    '' + optionalString reverseSyncCfg.enable ''
+      Inactive "Device-nvidia[0]"
     '' + optionalString offloadCfg.enable ''
       Option "AllowNVIDIAGPUScreens"
     '';
 
     services.xserver.displayManager.setupCommands = let
-      sinkGpuProviderName = if igpuDriver == "amdgpu" then
+      gpuProviderName = if igpuDriver == "amdgpu" then
         # find the name of the provider if amdgpu
         "`${pkgs.xorg.xrandr}/bin/xrandr --listproviders | ${pkgs.gnugrep}/bin/grep -i AMD | ${pkgs.gnused}/bin/sed -n 's/^.*name://p'`"
       else
         igpuDriver;
-    in optionalString syncCfg.enable ''
+      providerCmdParams = if syncCfg.enable then "\"${gpuProviderName}\" NVIDIA-0" else "NVIDIA-G0 \"${gpuProviderName}\"";
+    in optionalString (syncCfg.enable || reverseSyncCfg.enable) ''
       # Added by nvidia configuration module for Optimus/PRIME.
-      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource "${sinkGpuProviderName}" NVIDIA-0
+      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource ${providerCmdParams}
       ${pkgs.xorg.xrandr}/bin/xrandr --auto
     '';
 
@@ -330,7 +397,16 @@ in
 
     environment.systemPackages = [ nvidia_x11.bin ]
       ++ optionals cfg.nvidiaSettings [ nvidia_x11.settings ]
-      ++ optionals nvidiaPersistencedEnabled [ nvidia_x11.persistenced ];
+      ++ optionals nvidiaPersistencedEnabled [ nvidia_x11.persistenced ]
+      ++ optionals offloadCfg.enableOffloadCmd [
+        (pkgs.writeShellScriptBin "nvidia-offload" ''
+          export __NV_PRIME_RENDER_OFFLOAD=1
+          export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
+          export __GLX_VENDOR_LIBRARY_NAME=nvidia
+          export __VK_LAYER_NV_optimus=NVIDIA_only
+          exec "$@"
+        '')
+      ];
 
     systemd.packages = optional cfg.powerManagement.enable nvidia_x11.out;
 
@@ -387,7 +463,8 @@ in
     # If requested enable modesetting via kernel parameter.
     boot.kernelParams = optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
       ++ optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
-      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1";
+      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
+      ++ optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
 
     services.udev.extraRules =
       ''
diff --git a/nixpkgs/nixos/modules/hardware/video/switcheroo-control.nix b/nixpkgs/nixos/modules/hardware/video/switcheroo-control.nix
index 199adb2ad8f5..982388f8e5f4 100644
--- a/nixpkgs/nixos/modules/hardware/video/switcheroo-control.nix
+++ b/nixpkgs/nixos/modules/hardware/video/switcheroo-control.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.switcherooControl;
 in {
   options.services.switcherooControl = {
-    enable = mkEnableOption "switcheroo-control, a D-Bus service to check the availability of dual-GPU";
+    enable = mkEnableOption (lib.mdDoc "switcheroo-control, a D-Bus service to check the availability of dual-GPU");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix b/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
index a808429c9996..8dadbd53b989 100644
--- a/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
+++ b/nixpkgs/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
@@ -23,8 +23,10 @@ in
 runCommand "uvcdynctrl-udev-rules-${version}"
 {
   inherit dataPath;
-  buildInputs = [
+  nativeBuildInputs = [
     makeWrapper
+  ];
+  buildInputs = [
     libwebcam
   ];
   dontPatchELF = true;
diff --git a/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix b/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix
index 8940674ce53b..480c636aa0d9 100644
--- a/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix
+++ b/nixpkgs/nixos/modules/hardware/video/webcam/facetimehd.nix
@@ -12,7 +12,7 @@ in
 
 {
 
-  options.hardware.facetimehd.enable = mkEnableOption "facetimehd kernel module";
+  options.hardware.facetimehd.enable = mkEnableOption (lib.mdDoc "facetimehd kernel module");
 
   options.hardware.facetimehd.withCalibration = mkOption {
     default = false;
diff --git a/nixpkgs/nixos/modules/hardware/video/webcam/ipu6.nix b/nixpkgs/nixos/modules/hardware/video/webcam/ipu6.nix
new file mode 100644
index 000000000000..fce78cda34c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/video/webcam/ipu6.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+let
+
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption optional types;
+
+  cfg = config.hardware.ipu6;
+
+in
+{
+
+  options.hardware.ipu6 = {
+
+    enable = mkEnableOption (lib.mdDoc "support for Intel IPU6/MIPI cameras");
+
+    platform = mkOption {
+      type = types.enum [ "ipu6" "ipu6ep" ];
+      description = lib.mdDoc ''
+        Choose the version for your hardware platform.
+
+        Use `ipu6` for Tiger Lake and `ipu6ep` for Alder Lake respectively.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    boot.extraModulePackages = with config.boot.kernelPackages; [
+      ipu6-drivers
+    ];
+
+    hardware.firmware = with pkgs; [ ]
+      ++ optional (cfg.platform == "ipu6") ipu6-camera-bin
+      ++ optional (cfg.platform == "ipu6ep") ipu6ep-camera-bin;
+
+    services.udev.extraRules = ''
+      SUBSYSTEM=="intel-ipu6-psys", MODE="0660", GROUP="video"
+    '';
+
+    services.v4l2-relayd.instances.ipu6 = {
+      enable = mkDefault true;
+
+      cardLabel = mkDefault "Intel MIPI Camera";
+
+      extraPackages = with pkgs.gst_all_1; [ ]
+        ++ optional (cfg.platform == "ipu6") icamerasrc-ipu6
+        ++ optional (cfg.platform == "ipu6ep") icamerasrc-ipu6ep;
+
+      input = {
+        pipeline = "icamerasrc";
+        format = mkIf (cfg.platform == "ipu6ep") (mkDefault "NV12");
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/hardware/wooting.nix b/nixpkgs/nixos/modules/hardware/wooting.nix
index ee550cbbf6b8..90d046d49f4e 100644
--- a/nixpkgs/nixos/modules/hardware/wooting.nix
+++ b/nixpkgs/nixos/modules/hardware/wooting.nix
@@ -3,7 +3,7 @@
 with lib;
 {
   options.hardware.wooting.enable =
-    mkEnableOption "Enable support for Wooting keyboards";
+    mkEnableOption (lib.mdDoc "support for Wooting keyboards");
 
   config = mkIf config.hardware.wooting.enable {
     environment.systemPackages = [ pkgs.wootility ];
diff --git a/nixpkgs/nixos/modules/hardware/xone.nix b/nixpkgs/nixos/modules/hardware/xone.nix
index 89690d8c6fb1..211d3fce8679 100644
--- a/nixpkgs/nixos/modules/hardware/xone.nix
+++ b/nixpkgs/nixos/modules/hardware/xone.nix
@@ -6,7 +6,7 @@ let
 in
 {
   options.hardware.xone = {
-    enable = mkEnableOption "the xone driver for Xbox One and Xbobx Series X|S accessories";
+    enable = mkEnableOption (lib.mdDoc "the xone driver for Xbox One and Xbobx Series X|S accessories");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/hardware/xpadneo.nix b/nixpkgs/nixos/modules/hardware/xpadneo.nix
index 092f36729b31..a66e81d8b15b 100644
--- a/nixpkgs/nixos/modules/hardware/xpadneo.nix
+++ b/nixpkgs/nixos/modules/hardware/xpadneo.nix
@@ -6,7 +6,7 @@ let
 in
 {
   options.hardware.xpadneo = {
-    enable = mkEnableOption "the xpadneo driver for Xbox One wireless controllers";
+    enable = mkEnableOption (lib.mdDoc "the xpadneo driver for Xbox One wireless controllers");
   };
 
   config = mkIf cfg.enable {
@@ -16,7 +16,7 @@ in
       extraModprobeConfig =
         mkIf
           (config.hardware.bluetooth.enable &&
-           (lib.versionOlder config.boot.kernelPackages.kernel.version "5.12"))
+            (lib.versionOlder config.boot.kernelPackages.kernel.version "5.12"))
           "options bluetooth disable_ertm=1";
 
       extraModulePackages = with config.boot.kernelPackages; [ xpadneo ];
diff --git a/nixpkgs/nixos/modules/i18n/input-method/default.md b/nixpkgs/nixos/modules/i18n/input-method/default.md
new file mode 100644
index 000000000000..42cb8a8d7b6a
--- /dev/null
+++ b/nixpkgs/nixos/modules/i18n/input-method/default.md
@@ -0,0 +1,160 @@
+# Input Methods {#module-services-input-methods}
+
+Input methods are an operating system component that allows any data, such as
+keyboard strokes or mouse movements, to be received as input. In this way
+users can enter characters and symbols not found on their input devices.
+Using an input method is obligatory for any language that has more graphemes
+than there are keys on the keyboard.
+
+The following input methods are available in NixOS:
+
+  - IBus: The intelligent input bus.
+  - Fcitx5: The next generation of fcitx, addons (including engines, dictionaries, skins) can be added using `i18n.inputMethod.fcitx5.addons`.
+  - Nabi: A Korean input method based on XIM.
+  - Uim: The universal input method, is a library with a XIM bridge.
+  - Hime: An extremely easy-to-use input method framework.
+  - Kime: Korean IME
+
+## IBus {#module-services-input-methods-ibus}
+
+IBus is an Intelligent Input Bus. It provides full featured and user
+friendly input method user interface.
+
+The following snippet can be used to configure IBus:
+
+```
+i18n.inputMethod = {
+  enabled = "ibus";
+  ibus.engines = with pkgs.ibus-engines; [ anthy hangul mozc ];
+};
+```
+
+`i18n.inputMethod.ibus.engines` is optional and can be used
+to add extra IBus engines.
+
+Available extra IBus engines are:
+
+  - Anthy (`ibus-engines.anthy`): Anthy is a system for
+    Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
+  - Hangul (`ibus-engines.hangul`): Korean input method.
+  - m17n (`ibus-engines.m17n`): m17n is an input method that
+    uses input methods and corresponding icons in the m17n database.
+  - mozc (`ibus-engines.mozc`): A Japanese input method from
+    Google.
+  - Table (`ibus-engines.table`): An input method that load
+    tables of input methods.
+  - table-others (`ibus-engines.table-others`): Various
+    table-based input methods. To use this, and any other table-based input
+    methods, it must appear in the list of engines along with
+    `table`. For example:
+
+    ```
+    ibus.engines = with pkgs.ibus-engines; [ table table-others ];
+    ```
+
+To use any input method, the package must be added in the configuration, as
+shown above, and also (after running `nixos-rebuild`) the
+input method must be added from IBus' preference dialog.
+
+### Troubleshooting {#module-services-input-methods-troubleshooting}
+
+If IBus works in some applications but not others, a likely cause of this
+is that IBus is depending on a different version of `glib`
+to what the applications are depending on. This can be checked by running
+`nix-store -q --requisites <path> | grep glib`,
+where `<path>` is the path of either IBus or an
+application in the Nix store. The `glib` packages must
+match exactly. If they do not, uninstalling and reinstalling the
+application is a likely fix.
+
+## Fcitx5 {#module-services-input-methods-fcitx}
+
+Fcitx5 is an input method framework with extension support. It has three
+built-in Input Method Engine, Pinyin, QuWei and Table-based input methods.
+
+The following snippet can be used to configure Fcitx:
+
+```
+i18n.inputMethod = {
+  enabled = "fcitx5";
+  fcitx5.addons = with pkgs; [ fcitx5-mozc fcitx5-hangul fcitx5-m17n ];
+};
+```
+
+`i18n.inputMethod.fcitx5.addons` is optional and can be
+used to add extra Fcitx5 addons.
+
+Available extra Fcitx5 addons are:
+
+  - Anthy (`fcitx5-anthy`): Anthy is a system for
+    Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
+  - Chewing (`fcitx5-chewing`): Chewing is an
+    intelligent Zhuyin input method. It is one of the most popular input
+    methods among Traditional Chinese Unix users.
+  - Hangul (`fcitx5-hangul`): Korean input method.
+  - Unikey (`fcitx5-unikey`): Vietnamese input method.
+  - m17n (`fcitx5-m17n`): m17n is an input method that
+    uses input methods and corresponding icons in the m17n database.
+  - mozc (`fcitx5-mozc`): A Japanese input method from
+    Google.
+  - table-others (`fcitx5-table-other`): Various
+    table-based input methods.
+  - chinese-addons (`fcitx5-chinese-addons`): Various chinese input methods.
+  - rime (`fcitx5-rime`): RIME support for fcitx5.
+
+## Nabi {#module-services-input-methods-nabi}
+
+Nabi is an easy to use Korean X input method. It allows you to enter
+phonetic Korean characters (hangul) and pictographic Korean characters
+(hanja).
+
+The following snippet can be used to configure Nabi:
+
+```
+i18n.inputMethod = {
+  enabled = "nabi";
+};
+```
+
+## Uim {#module-services-input-methods-uim}
+
+Uim (short for "universal input method") is a multilingual input method
+framework. Applications can use it through so-called bridges.
+
+The following snippet can be used to configure uim:
+
+```
+i18n.inputMethod = {
+  enabled = "uim";
+};
+```
+
+Note: The [](#opt-i18n.inputMethod.uim.toolbar) option can be
+used to choose uim toolbar.
+
+## Hime {#module-services-input-methods-hime}
+
+Hime is an extremely easy-to-use input method framework. It is lightweight,
+stable, powerful and supports many commonly used input methods, including
+Cangjie, Zhuyin, Dayi, Rank, Shrimp, Greek, Korean Pinyin, Latin Alphabet,
+etc...
+
+The following snippet can be used to configure Hime:
+
+```
+i18n.inputMethod = {
+  enabled = "hime";
+};
+```
+
+## Kime {#module-services-input-methods-kime}
+
+Kime is Korean IME. it's built with Rust language and let you get simple, safe, fast Korean typing
+
+The following snippet can be used to configure Kime:
+
+```
+i18n.inputMethod = {
+  enabled = "kime";
+};
+```
diff --git a/nixpkgs/nixos/modules/i18n/input-method/default.nix b/nixpkgs/nixos/modules/i18n/input-method/default.nix
index bbc5783565a3..d967d4335c70 100644
--- a/nixpkgs/nixos/modules/i18n/input-method/default.nix
+++ b/nixpkgs/nixos/modules/i18n/input-method/default.nix
@@ -29,25 +29,22 @@ in
   options.i18n = {
     inputMethod = {
       enabled = mkOption {
-        type    = types.nullOr (types.enum [ "ibus" "fcitx" "fcitx5" "nabi" "uim" "hime" "kime" ]);
+        type    = types.nullOr (types.enum [ "ibus" "fcitx5" "nabi" "uim" "hime" "kime" ]);
         default = null;
-        example = "fcitx";
-        description = ''
+        example = "fcitx5";
+        description = lib.mdDoc ''
           Select the enabled input method. Input methods is a software to input symbols that are not available on standard input devices.
 
           Input methods are specially used to input Chinese, Japanese and Korean characters.
 
           Currently the following input methods are available in NixOS:
 
-          <itemizedlist>
-          <listitem><para>ibus: The intelligent input bus, extra input engines can be added using <literal>i18n.inputMethod.ibus.engines</literal>.</para></listitem>
-          <listitem><para>fcitx: A customizable lightweight input method, extra input engines can be added using <literal>i18n.inputMethod.fcitx.engines</literal>.</para></listitem>
-          <listitem><para>fcitx5: The next generation of fcitx, addons (including engines, dictionaries, skins) can be added using <literal>i18n.inputMethod.fcitx5.addons</literal>.</para></listitem>
-          <listitem><para>nabi: A Korean input method based on XIM. Nabi doesn't support Qt 5.</para></listitem>
-          <listitem><para>uim: The universal input method, is a library with a XIM bridge. uim mainly support Chinese, Japanese and Korean.</para></listitem>
-          <listitem><para>hime: An extremely easy-to-use input method framework.</para></listitem>
-          <listitem><para>kime: Koream IME.</para></listitem>
-          </itemizedlist>
+          - ibus: The intelligent input bus, extra input engines can be added using `i18n.inputMethod.ibus.engines`.
+          - fcitx5: The next generation of fcitx, addons (including engines, dictionaries, skins) can be added using `i18n.inputMethod.fcitx5.addons`.
+          - nabi: A Korean input method based on XIM. Nabi doesn't support Qt 5.
+          - uim: The universal input method, is a library with a XIM bridge. uim mainly support Chinese, Japanese and Korean.
+          - hime: An extremely easy-to-use input method framework.
+          - kime: Koream IME.
         '';
       };
 
@@ -55,7 +52,7 @@ in
         internal = true;
         type     = types.nullOr types.path;
         default  = null;
-        description = ''
+        description = lib.mdDoc ''
           The input method method package.
         '';
       };
@@ -68,7 +65,7 @@ in
 
   meta = {
     maintainers = with lib.maintainers; [ ericsagnes ];
-    doc = ./default.xml;
+    doc = ./default.md;
   };
 
 }
diff --git a/nixpkgs/nixos/modules/i18n/input-method/default.xml b/nixpkgs/nixos/modules/i18n/input-method/default.xml
deleted file mode 100644
index dd66316c7308..000000000000
--- a/nixpkgs/nixos/modules/i18n/input-method/default.xml
+++ /dev/null
@@ -1,291 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-input-methods">
- <title>Input Methods</title>
- <para>
-  Input methods are an operating system component that allows any data, such as
-  keyboard strokes or mouse movements, to be received as input. In this way
-  users can enter characters and symbols not found on their input devices.
-  Using an input method is obligatory for any language that has more graphemes
-  than there are keys on the keyboard.
- </para>
- <para>
-  The following input methods are available in NixOS:
- </para>
- <itemizedlist>
-  <listitem>
-   <para>
-    IBus: The intelligent input bus.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    Fcitx: A customizable lightweight input method.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    Nabi: A Korean input method based on XIM.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    Uim: The universal input method, is a library with a XIM bridge.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    Hime: An extremely easy-to-use input method framework.
-   </para>
-  </listitem>
-  <listitem>
-    <para>
-     Kime: Korean IME
-    </para>
-  </listitem>
- </itemizedlist>
- <section xml:id="module-services-input-methods-ibus">
-  <title>IBus</title>
-
-  <para>
-   IBus is an Intelligent Input Bus. It provides full featured and user
-   friendly input method user interface.
-  </para>
-
-  <para>
-   The following snippet can be used to configure IBus:
-  </para>
-
-<programlisting>
-i18n.inputMethod = {
-  <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "ibus";
-  <link linkend="opt-i18n.inputMethod.ibus.engines">ibus.engines</link> = with pkgs.ibus-engines; [ anthy hangul mozc ];
-};
-</programlisting>
-
-  <para>
-   <literal>i18n.inputMethod.ibus.engines</literal> is optional and can be used
-   to add extra IBus engines.
-  </para>
-
-  <para>
-   Available extra IBus engines are:
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     Anthy (<literal>ibus-engines.anthy</literal>): Anthy is a system for
-     Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Hangul (<literal>ibus-engines.hangul</literal>): Korean input method.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     m17n (<literal>ibus-engines.m17n</literal>): m17n is an input method that
-     uses input methods and corresponding icons in the m17n database.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     mozc (<literal>ibus-engines.mozc</literal>): A Japanese input method from
-     Google.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Table (<literal>ibus-engines.table</literal>): An input method that load
-     tables of input methods.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     table-others (<literal>ibus-engines.table-others</literal>): Various
-     table-based input methods. To use this, and any other table-based input
-     methods, it must appear in the list of engines along with
-     <literal>table</literal>. For example:
-<programlisting>
-ibus.engines = with pkgs.ibus-engines; [ table table-others ];
-</programlisting>
-    </para>
-   </listitem>
-  </itemizedlist>
-
-  <para>
-   To use any input method, the package must be added in the configuration, as
-   shown above, and also (after running <literal>nixos-rebuild</literal>) the
-   input method must be added from IBus' preference dialog.
-  </para>
-
-  <simplesect xml:id="module-services-input-methods-troubleshooting">
-   <title>Troubleshooting</title>
-   <para>
-    If IBus works in some applications but not others, a likely cause of this
-    is that IBus is depending on a different version of <literal>glib</literal>
-    to what the applications are depending on. This can be checked by running
-    <literal>nix-store -q --requisites &lt;path&gt; | grep glib</literal>,
-    where <literal>&lt;path&gt;</literal> is the path of either IBus or an
-    application in the Nix store. The <literal>glib</literal> packages must
-    match exactly. If they do not, uninstalling and reinstalling the
-    application is a likely fix.
-   </para>
-  </simplesect>
- </section>
- <section xml:id="module-services-input-methods-fcitx">
-  <title>Fcitx</title>
-
-  <para>
-   Fcitx is an input method framework with extension support. It has three
-   built-in Input Method Engine, Pinyin, QuWei and Table-based input methods.
-  </para>
-
-  <para>
-   The following snippet can be used to configure Fcitx:
-  </para>
-
-<programlisting>
-i18n.inputMethod = {
-  <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "fcitx";
-  <link linkend="opt-i18n.inputMethod.fcitx.engines">fcitx.engines</link> = with pkgs.fcitx-engines; [ mozc hangul m17n ];
-};
-</programlisting>
-
-  <para>
-   <literal>i18n.inputMethod.fcitx.engines</literal> is optional and can be
-   used to add extra Fcitx engines.
-  </para>
-
-  <para>
-   Available extra Fcitx engines are:
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     Anthy (<literal>fcitx-engines.anthy</literal>): Anthy is a system for
-     Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Chewing (<literal>fcitx-engines.chewing</literal>): Chewing is an
-     intelligent Zhuyin input method. It is one of the most popular input
-     methods among Traditional Chinese Unix users.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Hangul (<literal>fcitx-engines.hangul</literal>): Korean input method.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Unikey (<literal>fcitx-engines.unikey</literal>): Vietnamese input method.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     m17n (<literal>fcitx-engines.m17n</literal>): m17n is an input method that
-     uses input methods and corresponding icons in the m17n database.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     mozc (<literal>fcitx-engines.mozc</literal>): A Japanese input method from
-     Google.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     table-others (<literal>fcitx-engines.table-others</literal>): Various
-     table-based input methods.
-    </para>
-   </listitem>
-  </itemizedlist>
- </section>
- <section xml:id="module-services-input-methods-nabi">
-  <title>Nabi</title>
-
-  <para>
-   Nabi is an easy to use Korean X input method. It allows you to enter
-   phonetic Korean characters (hangul) and pictographic Korean characters
-   (hanja).
-  </para>
-
-  <para>
-   The following snippet can be used to configure Nabi:
-  </para>
-
-<programlisting>
-i18n.inputMethod = {
-  <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "nabi";
-};
-</programlisting>
- </section>
- <section xml:id="module-services-input-methods-uim">
-  <title>Uim</title>
-
-  <para>
-   Uim (short for "universal input method") is a multilingual input method
-   framework. Applications can use it through so-called bridges.
-  </para>
-
-  <para>
-   The following snippet can be used to configure uim:
-  </para>
-
-<programlisting>
-i18n.inputMethod = {
-  <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "uim";
-};
-</programlisting>
-
-  <para>
-   Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar"/> option can be
-   used to choose uim toolbar.
-  </para>
- </section>
- <section xml:id="module-services-input-methods-hime">
-  <title>Hime</title>
-
-  <para>
-   Hime is an extremely easy-to-use input method framework. It is lightweight,
-   stable, powerful and supports many commonly used input methods, including
-   Cangjie, Zhuyin, Dayi, Rank, Shrimp, Greek, Korean Pinyin, Latin Alphabet,
-   etc...
-  </para>
-
-  <para>
-   The following snippet can be used to configure Hime:
-  </para>
-
-<programlisting>
-i18n.inputMethod = {
-  <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "hime";
-};
-</programlisting>
- </section>
- <section xml:id="module-services-input-methods-kime">
-  <title>Kime</title>
-
-  <para>
-   Kime is Korean IME. it's built with Rust language and let you get simple, safe, fast Korean typing
-  </para>
-
-  <para>
-   The following snippet can be used to configure Kime:
-  </para>
-
-<programlisting>
-i18n.inputMethod = {
-  <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "kime";
-};
-</programlisting>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/i18n/input-method/fcitx.nix b/nixpkgs/nixos/modules/i18n/input-method/fcitx.nix
deleted file mode 100644
index 043ec3d55c1f..000000000000
--- a/nixpkgs/nixos/modules/i18n/input-method/fcitx.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.i18n.inputMethod.fcitx;
-  fcitxPackage = pkgs.fcitx.override { plugins = cfg.engines; };
-  fcitxEngine = types.package // {
-    name  = "fcitx-engine";
-    check = x: (lib.types.package.check x) && (attrByPath ["meta" "isFcitxEngine"] false x);
-  };
-in
-{
-  options = {
-
-    i18n.inputMethod.fcitx = {
-      engines = mkOption {
-        type    = with types; listOf fcitxEngine;
-        default = [];
-        example = literalExpression "with pkgs.fcitx-engines; [ mozc hangul ]";
-        description =
-          let
-            enginesDrv = filterAttrs (const isDerivation) pkgs.fcitx-engines;
-            engines = concatStringsSep ", "
-              (map (name: "`${name}`") (attrNames enginesDrv));
-          in
-            lib.mdDoc "Enabled Fcitx engines. Available engines are: ${engines}.";
-      };
-    };
-
-  };
-
-  config = mkIf (config.i18n.inputMethod.enabled == "fcitx") {
-    i18n.inputMethod.package = fcitxPackage;
-
-    environment.variables = {
-      GTK_IM_MODULE = "fcitx";
-      QT_IM_MODULE  = "fcitx";
-      XMODIFIERS    = "@im=fcitx";
-    };
-    services.xserver.displayManager.sessionCommands = "${fcitxPackage}/bin/fcitx";
-  };
-
-  # uses attributes of the linked package
-  meta.buildDocsInSandbox = false;
-}
diff --git a/nixpkgs/nixos/modules/i18n/input-method/fcitx5.nix b/nixpkgs/nixos/modules/i18n/input-method/fcitx5.nix
index 7cdef9ae9321..7251240d26ac 100644
--- a/nixpkgs/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixpkgs/nixos/modules/i18n/input-method/fcitx5.nix
@@ -5,10 +5,9 @@ with lib;
 let
   im = config.i18n.inputMethod;
   cfg = im.fcitx5;
-  addons = cfg.addons ++ optional cfg.enableRimeData pkgs.rime-data;
-  fcitx5Package = pkgs.fcitx5-with-addons.override { inherit addons; };
-  whetherRimeDataDir = any (p: p.pname == "fcitx5-rime") cfg.addons;
-in {
+  fcitx5Package = pkgs.fcitx5-with-addons.override { inherit (cfg) addons; };
+in
+{
   options = {
     i18n.inputMethod.fcitx5 = {
       addons = mkOption {
@@ -19,30 +18,23 @@ in {
           Enabled Fcitx5 addons.
         '';
       };
-
-      enableRimeData = mkEnableOption "default rime-data with fcitx5-rime";
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx5" "enableRimeData" ] ''
+      RIME data is now included in `fcitx5-rime` by default, and can be customized using `fcitx5-rime.override { rimeDataPkgs = ...; }`
+    '')
+  ];
+
   config = mkIf (im.enabled == "fcitx5") {
     i18n.inputMethod.package = fcitx5Package;
 
-    environment = mkMerge [{
-      variables = {
-        GTK_IM_MODULE = "fcitx";
-        QT_IM_MODULE = "fcitx";
-        XMODIFIERS = "@im=fcitx";
-        QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
-      };
-    }
-    (mkIf whetherRimeDataDir {
-      pathsToLink = [
-        "/share/rime-data"
-      ];
-
-      variables =  {
-        NIX_RIME_DATA_DIR = "/run/current-system/sw/share/rime-data";
-      };
-    })];
+    environment.variables = {
+      GTK_IM_MODULE = "fcitx";
+      QT_IM_MODULE = "fcitx";
+      XMODIFIERS = "@im=fcitx";
+      QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
+    };
   };
 }
diff --git a/nixpkgs/nixos/modules/i18n/input-method/ibus.nix b/nixpkgs/nixos/modules/i18n/input-method/ibus.nix
index 520db128acd9..2a35afad2ac7 100644
--- a/nixpkgs/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixpkgs/nixos/modules/i18n/input-method/ibus.nix
@@ -10,10 +10,7 @@ let
     check = x: (lib.types.package.check x) && (attrByPath ["meta" "isIbusEngine"] false x);
   };
 
-  impanel =
-    if cfg.panel != null
-    then "--panel=${cfg.panel}"
-    else "";
+  impanel = optionalString (cfg.panel != null) "--panel=${cfg.panel}";
 
   ibusAutostart = pkgs.writeTextFile {
     name = "autostart-ibus-daemon";
diff --git a/nixpkgs/nixos/modules/i18n/input-method/kime.nix b/nixpkgs/nixos/modules/i18n/input-method/kime.nix
index 29224a6bf75f..e82996926b28 100644
--- a/nixpkgs/nixos/modules/i18n/input-method/kime.nix
+++ b/nixpkgs/nixos/modules/i18n/input-method/kime.nix
@@ -1,40 +1,37 @@
 { config, pkgs, lib, generators, ... }:
-with lib;
-let
-  cfg = config.i18n.inputMethod.kime;
-  yamlFormat = pkgs.formats.yaml { };
-in
-{
-  options = {
-    i18n.inputMethod.kime = {
-      config = mkOption {
-        type = yamlFormat.type;
-        default = { };
-        example = literalExpression ''
-          {
-            daemon = {
-              modules = ["Xim" "Indicator"];
-            };
+let imcfg = config.i18n.inputMethod;
+in {
+  imports = [
+    (lib.mkRemovedOptionModule [ "i18n" "inputMethod" "kime" "config" ] "Use i18n.inputMethod.kime.* instead")
+  ];
 
-            indicator = {
-              icon_color = "White";
-            };
-
-            engine = {
-              hangul = {
-                layout = "dubeolsik";
-              };
-            };
-          }
-          '';
-        description = lib.mdDoc ''
-          kime configuration. Refer to <https://github.com/Riey/kime/blob/v${pkgs.kime.version}/docs/CONFIGURATION.md> for details on supported values.
-        '';
-      };
+  options.i18n.inputMethod.kime = {
+    daemonModules = lib.mkOption {
+      type = lib.types.listOf (lib.types.enum [ "Xim" "Wayland" "Indicator" ]);
+      default = [ "Xim" "Wayland" "Indicator" ];
+      example = [ "Xim" "Indicator" ];
+      description = lib.mdDoc ''
+        List of enabled daemon modules
+      '';
+    };
+    iconColor = lib.mkOption {
+      type = lib.types.enum [ "Black" "White" ];
+      default = "Black";
+      example = "White";
+      description = lib.mdDoc ''
+        Color of the indicator icon
+      '';
+    };
+    extraConfig = lib.mkOption {
+      type = lib.types.lines;
+      default = "";
+      description = lib.mdDoc ''
+        extra kime configuration. Refer to <https://github.com/Riey/kime/blob/v${pkgs.kime.version}/docs/CONFIGURATION.md> for details on supported values.
+      '';
     };
   };
 
-  config = mkIf (config.i18n.inputMethod.enabled == "kime") {
+  config = lib.mkIf (imcfg.enabled == "kime") {
     i18n.inputMethod.package = pkgs.kime;
 
     environment.variables = {
@@ -43,7 +40,12 @@ in
       XMODIFIERS    = "@im=kime";
     };
 
-    environment.etc."xdg/kime/config.yaml".text = replaceStrings [ "\\\\" ] [ "\\" ] (builtins.toJSON cfg.config);
+    environment.etc."xdg/kime/config.yaml".text = ''
+      daemon:
+        modules: [${lib.concatStringsSep "," imcfg.kime.daemonModules}]
+      indicator:
+        icon_color: ${imcfg.kime.iconColor}
+    '' + imcfg.kime.extraConfig;
   };
 
   # uses attributes of the linked package
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix b/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix
index 2f91cd39881d..8426ba8fac00 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/channel.nix
@@ -6,6 +6,12 @@
 with lib;
 
 let
+  # This is copied into the installer image, so it's important that it is filtered
+  # to avoid including a large .git directory.
+  # We also want the source name to be normalised to "source" to avoid depending on the
+  # location of nixpkgs.
+  # In the future we might want to expose the ISO image from the flake and use
+  # `self.outPath` directly instead.
   nixpkgs = lib.cleanSource pkgs.path;
 
   # We need a copy of the Nix expressions for Nixpkgs and NixOS on the
@@ -31,6 +37,14 @@ let
 in
 
 {
+  # Pin the nixpkgs flake in the installer to our cleaned up nixpkgs source.
+  # FIXME: this might be surprising and is really only needed for offline installations,
+  # see discussion in https://github.com/NixOS/nixpkgs/pull/204178#issuecomment-1336289021
+  nix.registry.nixpkgs.to = {
+    type = "path";
+    path = "${channelSources}/nixos";
+  };
+
   # Provide the NixOS/Nixpkgs sources in /etc/nixos.  This is required
   # for nixos-install.
   boot.postBootCommands = mkAfter
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
index 8c7bac6f6cc1..4a00c52916f6 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
@@ -26,7 +26,7 @@ with lib;
 
   # Provide networkmanager for easy wireless configuration.
   networking.networkmanager.enable = true;
-  networking.wireless.enable = mkForce false;
+  networking.wireless.enable = mkImageMediaOverride false;
 
   # KDE complains if power management is disabled (to be precise, if
   # there is no power management backend such as upower).
@@ -38,9 +38,9 @@ with lib;
   # VM guest additions to improve host-guest interaction
   services.spice-vdagentd.enable = true;
   services.qemuGuest.enable = true;
-  virtualisation.vmware.guest.enable = true;
+  virtualisation.vmware.guest.enable = pkgs.stdenv.hostPlatform.isx86;
   virtualisation.hypervGuest.enable = true;
-  services.xe-guest-utilities.enable = true;
+  services.xe-guest-utilities.enable = pkgs.stdenv.hostPlatform.isx86;
   # The VirtualBox guest additions rely on an out-of-tree kernel module
   # which lags behind kernel releases, potentially causing broken builds.
   virtualisation.virtualbox.guest.enable = false;
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix
index d015e10c11d8..12feb2d96ecc 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix
@@ -31,7 +31,7 @@
   };
 
   # Theme calamares with GNOME theme
-  qt5 = {
+  qt = {
     enable = true;
     platformTheme = "gnome";
   };
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix
index 8a6d30d1801a..3f3571d25382 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix
@@ -14,7 +14,10 @@ in
     calamares-nixos
     calamares-nixos-autostart
     calamares-nixos-extensions
-    # Needed for calamares QML module packagechooserq
-    libsForQt5.full
+    # Get list of locales
+    glibcLocales
   ];
+
+  # Support choosing from any locale
+  i18n.supportedLocales = [ "all" ];
 }
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix
new file mode 100644
index 000000000000..9d09cdbe0206
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix
@@ -0,0 +1,15 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ./installation-cd-minimal-new-kernel.nix ];
+
+  # Makes `availableOn` fail for zfs, see <nixos/modules/profiles/base.nix>.
+  # This is a workaround since we cannot remove the `"zfs"` string from `supportedFilesystems`.
+  # The proper fix would be to make `supportedFilesystems` an attrset with true/false which we
+  # could then `lib.mkForce false`
+  nixpkgs.overlays = [(final: super: {
+    zfs = super.zfs.overrideAttrs(_: {
+      meta.platforms = [];
+    });
+  })];
+}
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
index 97506045e0e1..29afdd471091 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
@@ -1,14 +1,24 @@
 # This module defines a small NixOS installation CD.  It does not
 # contain any graphical stuff.
 
-{ ... }:
+{ lib, ... }:
 
 {
-  imports =
-    [ ./installation-cd-base.nix
-    ];
+  imports = [
+    ../../profiles/minimal.nix
+    ./installation-cd-base.nix
+  ];
 
-  isoImage.edition = "minimal";
+  # Causes a lot of uncached builds for a negligible decrease in size.
+  environment.noXlibs = lib.mkOverride 500 false;
 
-  fonts.fontconfig.enable = false;
+  documentation.man.enable = lib.mkOverride 500 true;
+
+  # Although we don't really need HTML documentation in the minimal installer,
+  # not including it may cause annoying cache misses in the case of the NixOS manual.
+  documentation.doc.enable = lib.mkOverride 500 true;
+
+  fonts.fontconfig.enable = lib.mkForce false;
+
+  isoImage.edition = lib.mkForce "minimal";
 }
diff --git a/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix b/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
index 6d0a11b7491f..f9cbafc28657 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -22,8 +22,8 @@ let
       (option: ''
         menuentry '${defaults.name} ${
         # Name appended to menuentry defaults to params if no specific name given.
-        option.name or (if option ? params then "(${option.params})" else "")
-        }' ${if option ? class then " --class ${option.class}" else ""} {
+        option.name or (optionalString (option ? params) "(${option.params})")
+        }' ${optionalString (option ? class) " --class ${option.class}"} {
           linux ${defaults.image} \''${isoboot} ${defaults.params} ${
             option.params or ""
           }
@@ -35,24 +35,29 @@ let
   ;
 
   /**
-   * Given a `config`, builds the default options.
+   * Builds the default options.
    */
-  buildMenuGrub2 = config:
-    buildMenuAdditionalParamsGrub2 config ""
-  ;
+  buildMenuGrub2 = buildMenuAdditionalParamsGrub2 "";
+
+  targetArch =
+    if config.boot.loader.grub.forcei686 then
+      "ia32"
+    else
+      pkgs.stdenv.hostPlatform.efiArch;
 
   /**
-   * Given a `config` and params to add to `params`, build a set of default options.
+   * Given params to add to `params`, build a set of default options.
    * Use this one when creating a variant (e.g. hidpi)
    */
-  buildMenuAdditionalParamsGrub2 = config: additional:
+  buildMenuAdditionalParamsGrub2 = additional:
   let
     finalCfg = {
-      name = "NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}";
+      name = "${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}";
       params = "init=${config.system.build.toplevel}/init ${additional} ${toString config.boot.kernelParams}";
       image = "/boot/${config.system.boot.loader.kernelFile}";
       initrd = "/boot/initrd";
     };
+
   in
     menuBuilderGrub2
     finalCfg
@@ -65,18 +70,24 @@ let
   ;
 
   # Timeout in syslinux is in units of 1/10 of a second.
-  # 0 is used to disable timeouts.
+  # null means max timeout (35996, just under 1h in 1/10 seconds)
+  # 0 means disable timeout
   syslinuxTimeout = if config.boot.loader.timeout == null then
-      0
+      35996
     else
-      max (config.boot.loader.timeout * 10) 1;
+      config.boot.loader.timeout * 10;
 
-
-  max = x: y: if x > y then x else y;
+  # Timeout in grub is in seconds.
+  # null means max timeout (infinity)
+  # 0 means disable timeout
+  grubEfiTimeout = if config.boot.loader.timeout == null then
+      -1
+    else
+      config.boot.loader.timeout;
 
   # The configuration file for syslinux.
 
-  # Notes on syslinux configuration and UNetbootin compatiblity:
+  # Notes on syslinux configuration and UNetbootin compatibility:
   #   * Do not use '/syslinux/syslinux.cfg' as the path for this
   #     configuration. UNetbootin will not parse the file and use it as-is.
   #     This results in a broken configuration if the partition label does
@@ -98,35 +109,35 @@ let
     DEFAULT boot
 
     LABEL boot
-    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with 'nomodeset'
     LABEL boot-nomodeset
-    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (nomodeset)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} nomodeset
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with 'copytoram'
     LABEL boot-copytoram
-    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (copytoram)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} copytoram
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with verbose logging to the console
     LABEL boot-debug
-    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (debug)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} loglevel=7
     INITRD /boot/${config.system.boot.loader.initrdFile}
 
     # A variant to boot with a serial console enabled
     LABEL boot-serial
-    MENU LABEL NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (serial console=ttyS0,115200n8)
+    MENU LABEL ${config.isoImage.prependToMenuLabel}${config.system.nixos.distroName} ${config.system.nixos.label}${config.isoImage.appendToMenuLabel} (serial console=ttyS0,115200n8)
     LINUX /boot/${config.system.boot.loader.kernelFile}
     APPEND init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0,115200n8
     INITRD /boot/${config.system.boot.loader.initrdFile}
@@ -281,7 +292,7 @@ let
     if serial; then set with_serial=yes ;fi
     export with_serial
     clear
-    set timeout=10
+    set timeout=${toString grubEfiTimeout}
 
     # This message will only be viewable when "gfxterm" is not used.
     echo ""
@@ -314,16 +325,16 @@ let
     # Menu entries
     #
 
-    ${buildMenuGrub2 config}
+    ${buildMenuGrub2}
     submenu "HiDPI, Quirks and Accessibility" --class hidpi --class submenu {
       ${grubMenuCfg}
       submenu "Suggests resolution @720p" --class hidpi-720p {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "video=1280x720@60"}
+        ${buildMenuAdditionalParamsGrub2 "video=1280x720@60"}
       }
       submenu "Suggests resolution @1080p" --class hidpi-1080p {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "video=1920x1080@60"}
+        ${buildMenuAdditionalParamsGrub2 "video=1920x1080@60"}
       }
 
       # If we boot into a graphical environment where X is autoran
@@ -331,7 +342,7 @@ let
       # to disable this.
       submenu "Disable display-manager" --class quirk-disable-displaymanager {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "systemd.mask=display-manager.service"}
+        ${buildMenuAdditionalParamsGrub2 "systemd.mask=display-manager.service"}
       }
 
       # Some laptop and convertibles have the panel installed in an
@@ -340,29 +351,29 @@ let
       submenu "" {return}
       submenu "Rotate framebuffer Clockwise" --class rotate-90cw {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:1"}
+        ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:1"}
       }
       submenu "Rotate framebuffer Upside-Down" --class rotate-180 {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:2"}
+        ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:2"}
       }
       submenu "Rotate framebuffer Counter-Clockwise" --class rotate-90ccw {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "fbcon=rotate:3"}
+        ${buildMenuAdditionalParamsGrub2 "fbcon=rotate:3"}
       }
 
       # As a proof of concept, mainly. (Not sure it has accessibility merits.)
       submenu "" {return}
       submenu "Use black on white" --class accessibility-blakconwhite {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "vt.default_red=0xFF,0xBC,0x4F,0xB4,0x56,0xBC,0x4F,0x00,0xA1,0xCF,0x84,0xCA,0x8D,0xB4,0x84,0x68 vt.default_grn=0xFF,0x55,0xBA,0xBA,0x4D,0x4D,0xB3,0x00,0xA0,0x8F,0xB3,0xCA,0x88,0x93,0xA4,0x68 vt.default_blu=0xFF,0x58,0x5F,0x58,0xC5,0xBD,0xC5,0x00,0xA8,0xBB,0xAB,0x97,0xBD,0xC7,0xC5,0x68"}
+        ${buildMenuAdditionalParamsGrub2 "vt.default_red=0xFF,0xBC,0x4F,0xB4,0x56,0xBC,0x4F,0x00,0xA1,0xCF,0x84,0xCA,0x8D,0xB4,0x84,0x68 vt.default_grn=0xFF,0x55,0xBA,0xBA,0x4D,0x4D,0xB3,0x00,0xA0,0x8F,0xB3,0xCA,0x88,0x93,0xA4,0x68 vt.default_blu=0xFF,0x58,0x5F,0x58,0xC5,0xBD,0xC5,0x00,0xA8,0xBB,0xAB,0x97,0xBD,0xC7,0xC5,0x68"}
       }
 
       # Serial access is a must!
       submenu "" {return}
       submenu "Serial console=ttyS0,115200n8" --class serial {
         ${grubMenuCfg}
-        ${buildMenuAdditionalParamsGrub2 config "console=ttyS0,115200n8"}
+        ${buildMenuAdditionalParamsGrub2 "console=ttyS0,115200n8"}
       }
     }
 
@@ -416,7 +427,7 @@ let
       echo "Usage size: $usage_size"
       echo "Image size: $image_size"
       truncate --size=$image_size "$out"
-      faketime "2000-01-01 00:00:00" mkfs.vfat -i 12345678 -n EFIBOOT "$out"
+      mkfs.vfat --invariant -i 12345678 -n EFIBOOT "$out"
 
       # Force a fixed order in mcopy for better determinism, and avoid file globbing
       for d in $(find EFI -type d | sort); do
@@ -431,22 +442,6 @@ let
       fsck.vfat -vn "$out"
     ''; # */
 
-  # Name used by UEFI for architectures.
-  targetArch =
-    if pkgs.stdenv.isi686 || config.boot.loader.grub.forcei686 then
-      "ia32"
-    else if pkgs.stdenv.isx86_64 then
-      "x64"
-    else if pkgs.stdenv.isAarch32 then
-      "arm"
-    else if pkgs.stdenv.isAarch64 then
-      "aa64"
-    else
-      throw "Unsupported architecture";
-
-  # Syslinux (and isolinux) only supports x86-based architectures.
-  canx86BiosBoot = pkgs.stdenv.hostPlatform.isx86;
-
 in
 
 {
@@ -454,13 +449,15 @@ in
 
     isoImage.isoName = mkOption {
       default = "${config.isoImage.isoBaseName}.iso";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Name of the generated ISO image file.
       '';
     };
 
     isoImage.isoBaseName = mkOption {
-      default = "nixos";
+      default = config.system.nixos.distroId;
+      type = lib.types.str;
       description = lib.mdDoc ''
         Prefix of the name of the generated ISO image file.
       '';
@@ -468,6 +465,7 @@ in
 
     isoImage.compressImage = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether the ISO image should be compressed using
         {command}`zstd`.
@@ -475,12 +473,13 @@ in
     };
 
     isoImage.squashfsCompression = mkOption {
-      default = with pkgs.stdenv.targetPlatform; "xz -Xdict-size 100% "
+      default = with pkgs.stdenv.hostPlatform; "xz -Xdict-size 100% "
                 + lib.optionalString isx86 "-Xbcj x86"
                 # Untested but should also reduce size for these platforms
                 + lib.optionalString isAarch "-Xbcj arm"
                 + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
                 + lib.optionalString (isSparc) "-Xbcj sparc";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Compression settings to use for the squashfs nix store.
       '';
@@ -489,6 +488,7 @@ in
 
     isoImage.edition = mkOption {
       default = "";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Specifies which edition string to use in the volume ID of the generated
         ISO image.
@@ -498,6 +498,7 @@ in
     isoImage.volumeID = mkOption {
       # nixos-$EDITION-$RELEASE-$ARCH
       default = "nixos${optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}";
+      type = lib.types.str;
       description = lib.mdDoc ''
         Specifies the label or volume ID of the generated ISO image.
         Note that the label is used by stage 1 of the boot process to
@@ -528,6 +529,7 @@ in
 
     isoImage.includeSystemBuildDependencies = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Set this option to include all the needed sources etc in the
         image. It significantly increases image size. Use that when
@@ -537,15 +539,35 @@ in
       '';
     };
 
+    isoImage.makeBiosBootable = mkOption {
+      # Before this option was introduced, images were BIOS-bootable if the
+      # hostPlatform was x86-based. This option is enabled by default for
+      # backwards compatibility.
+      #
+      # Also note that syslinux package currently cannot be cross-compiled from
+      # non-x86 platforms, so the default is false on non-x86 build platforms.
+      default = pkgs.stdenv.buildPlatform.isx86 && pkgs.stdenv.hostPlatform.isx86;
+      defaultText = lib.literalMD ''
+        `true` if both build and host platforms are x86-based architectures,
+        e.g. i686 and x86_64.
+      '';
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Whether the ISO image should be a BIOS-bootable disk.
+      '';
+    };
+
     isoImage.makeEfiBootable = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
-        Whether the ISO image should be an efi-bootable volume.
+        Whether the ISO image should be an EFI-bootable volume.
       '';
     };
 
     isoImage.makeUsbBootable = mkOption {
       default = false;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether the ISO image should be bootable from CD as well as USB.
       '';
@@ -581,7 +603,7 @@ in
 
     isoImage.syslinuxTheme = mkOption {
       default = ''
-        MENU TITLE NixOS
+        MENU TITLE ${config.system.nixos.distroName}
         MENU RESOLUTION 800 600
         MENU CLEAR
         MENU ROWS 6
@@ -610,8 +632,22 @@ in
       '';
     };
 
+    isoImage.prependToMenuLabel = mkOption {
+      default = "";
+      type = types.str;
+      example = "Install ";
+      description = lib.mdDoc ''
+        The string to prepend before the menu label for the NixOS system.
+        This will be directly prepended (without whitespace) to the NixOS version
+        string, like for example if it is set to `XXX`:
+
+        `XXXNixOS 99.99-pre666`
+      '';
+    };
+
     isoImage.appendToMenuLabel = mkOption {
       default = " Installer";
+      type = types.str;
       example = " Live System";
       description = lib.mdDoc ''
         The string to append after the menu label for the NixOS system.
@@ -676,6 +712,11 @@ in
   config = {
     assertions = [
       {
+        # Syslinux (and isolinux) only supports x86-based architectures.
+        assertion = config.isoImage.makeBiosBootable -> pkgs.stdenv.hostPlatform.isx86;
+        message = "BIOS boot is only supported on x86-based architectures.";
+      }
+      {
         assertion = !(stringLength config.isoImage.volumeID > 32);
         # https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor
         # Volume Identifier can only be 32 bytes
@@ -688,14 +729,12 @@ in
       }
     ];
 
-    boot.loader.grub.version = 2;
-
     # Don't build the GRUB menu builder script, since we don't need it
     # here and it causes a cyclic dependency.
     boot.loader.grub.enable = false;
 
     environment.systemPackages =  [ grubPkgs.grub2 grubPkgs.grub2_efi ]
-      ++ optional canx86BiosBoot pkgs.syslinux
+      ++ optional (config.isoImage.makeBiosBootable) pkgs.syslinux
     ;
 
     # In stage 1 of the boot, mount the CD as the root FS by label so
@@ -746,7 +785,7 @@ in
         { source = pkgs.writeText "version" config.system.nixos.label;
           target = "/version.txt";
         }
-      ] ++ optionals canx86BiosBoot [
+      ] ++ optionals (config.isoImage.makeBiosBootable) [
         { source = config.isoImage.splashImage;
           target = "/isolinux/background.png";
         }
@@ -773,7 +812,7 @@ in
         { source = config.isoImage.efiSplashImage;
           target = "/EFI/boot/efi-background.png";
         }
-      ] ++ optionals (config.boot.loader.grub.memtest86.enable && canx86BiosBoot) [
+      ] ++ optionals (config.boot.loader.grub.memtest86.enable && config.isoImage.makeBiosBootable) [
         { source = "${pkgs.memtest86plus}/memtest.bin";
           target = "/boot/memtest.bin";
         }
@@ -788,10 +827,10 @@ in
     # Create the ISO image.
     system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({
       inherit (config.isoImage) isoName compressImage volumeID contents;
-      bootable = canx86BiosBoot;
+      bootable = config.isoImage.makeBiosBootable;
       bootImage = "/isolinux/isolinux.bin";
-      syslinux = if canx86BiosBoot then pkgs.syslinux else null;
-    } // optionalAttrs (config.isoImage.makeUsbBootable && canx86BiosBoot) {
+      syslinux = if config.isoImage.makeBiosBootable then pkgs.syslinux else null;
+    } // optionalAttrs (config.isoImage.makeUsbBootable && config.isoImage.makeBiosBootable) {
       usbBootable = true;
       isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin";
     } // optionalAttrs config.isoImage.makeEfiBootable {
diff --git a/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix b/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix
index 1563501a7e01..5ca255acf35f 100644
--- a/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix
+++ b/nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix
@@ -1,10 +1,15 @@
 # This module defines a small netboot environment.
 
-{ ... }:
+{ lib, ... }:
 
 {
-  imports =
-    [ ./netboot-base.nix
-      ../../profiles/minimal.nix
-    ];
+  imports = [
+    ./netboot-base.nix
+    ../../profiles/minimal.nix
+  ];
+
+  documentation.man.enable = lib.mkOverride 500 true;
+  hardware.enableRedistributableFirmware = lib.mkOverride 70 false;
+  system.extraDependencies = lib.mkOverride 70 [];
+  networking.wireless.enable = lib.mkOverride 500 false;
 }
diff --git a/nixpkgs/nixos/modules/installer/netboot/netboot.nix b/nixpkgs/nixos/modules/installer/netboot/netboot.nix
index 03bb529cd851..a55c0ab2d655 100644
--- a/nixpkgs/nixos/modules/installer/netboot/netboot.nix
+++ b/nixpkgs/nixos/modules/installer/netboot/netboot.nix
@@ -8,6 +8,20 @@ with lib;
 {
   options = {
 
+    netboot.squashfsCompression = mkOption {
+      default = with pkgs.stdenv.hostPlatform; "xz -Xdict-size 100% "
+                + lib.optionalString isx86 "-Xbcj x86"
+                # Untested but should also reduce size for these platforms
+                + lib.optionalString isAarch "-Xbcj arm"
+                + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
+                + lib.optionalString (isSparc) "-Xbcj sparc";
+      description = lib.mdDoc ''
+        Compression settings to use for the squashfs nix store.
+      '';
+      example = "zstd -Xcompression-level 6";
+      type = types.str;
+    };
+
     netboot.storeContents = mkOption {
       example = literalExpression "[ pkgs.stdenv ]";
       description = lib.mdDoc ''
@@ -77,6 +91,7 @@ with lib;
     # Create the squashfs image that contains the Nix store.
     system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
       storeContents = config.netboot.storeContents;
+      comp = config.netboot.squashfsCompression;
     };
 
 
diff --git a/nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix b/nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix
new file mode 100644
index 000000000000..0e5055960294
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix
@@ -0,0 +1,15 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ./sd-image-aarch64-new-kernel-installer.nix ];
+
+  # Makes `availableOn` fail for zfs, see <nixos/modules/profiles/base.nix>.
+  # This is a workaround since we cannot remove the `"zfs"` string from `supportedFilesystems`.
+  # The proper fix would be to make `supportedFilesystems` an attrset with true/false which we
+  # could then `lib.mkForce false`
+  nixpkgs.overlays = [(final: super: {
+    zfs = super.zfs.overrideAttrs(_: {
+      meta.platforms = [];
+    });
+  })];
+}
diff --git a/nixpkgs/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix b/nixpkgs/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix
new file mode 100644
index 000000000000..143c678e43fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/installer/sd-card/sd-image-powerpc64le.nix
@@ -0,0 +1,49 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/sd-card/sd-image-powerpc64le.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ../../profiles/installation-device.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader = {
+    # powerpc64le-linux typically uses petitboot
+    grub.enable = false;
+    generic-extlinux-compatible = {
+      # petitboot is not does not support all of the extlinux extensions to
+      # syslinux, but its parser is very forgiving; it essentially ignores
+      # whatever it doesn't understand.  See below for a filename adjustment.
+      enable = true;
+    };
+  };
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+  boot.kernelParams = [ "console=hvc0" ];
+
+  sdImage = {
+    populateFirmwareCommands = "";
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${config.boot.loader.generic-extlinux-compatible.populateCmd} \
+        -c ${config.system.build.toplevel} \
+        -d ./files/boot
+    ''
+    # https://github.com/open-power/petitboot/blob/master/discover/syslinux-parser.c
+    # petitboot will look in these paths (plus all-caps versions of them):
+    #  /boot/syslinux/syslinux.cfg
+    #  /syslinux/syslinux.cfg
+    #  /syslinux.cfg
+    + ''
+      mv ./files/boot/extlinux ./files/boot/syslinux
+      mv ./files/boot/syslinux/extlinux.conf ./files/boot/syslinux/syslinux.cfg
+    ''
+    # petitboot does not support relative paths for LINUX or INITRD; it prepends
+    # a `/` when parsing these fields
+    + ''
+      sed -i 's_^\(\W\W*\(INITRD\|initrd\|LINUX\|linux\)\W\)\.\./_\1/boot/_' ./files/boot/syslinux/syslinux.cfg
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/installer/sd-card/sd-image.nix b/nixpkgs/nixos/modules/installer/sd-card/sd-image.nix
index c0335ea48759..ad9b803b1d1e 100644
--- a/nixpkgs/nixos/modules/installer/sd-card/sd-image.nix
+++ b/nixpkgs/nixos/modules/installer/sd-card/sd-image.nix
@@ -35,14 +35,14 @@ in
   options.sdImage = {
     imageName = mkOption {
       default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img";
-      description = ''
+      description = lib.mdDoc ''
         Name of the generated image file.
       '';
     };
 
     imageBaseName = mkOption {
       default = "nixos-sd-image";
-      description = ''
+      description = lib.mdDoc ''
         Prefix of the name of the generated image file.
       '';
     };
@@ -50,7 +50,7 @@ in
     storePaths = mkOption {
       type = with types; listOf package;
       example = literalExpression "[ pkgs.stdenv ]";
-      description = ''
+      description = lib.mdDoc ''
         Derivations to be included in the Nix store in the generated SD image.
       '';
     };
@@ -58,7 +58,7 @@ in
     firmwarePartitionOffset = mkOption {
       type = types.int;
       default = 8;
-      description = ''
+      description = lib.mdDoc ''
         Gap in front of the /boot/firmware partition, in mebibytes (1024×1024
         bytes).
         Can be increased to make more space for boards requiring to dd u-boot
@@ -74,7 +74,7 @@ in
     firmwarePartitionID = mkOption {
       type = types.str;
       default = "0x2178694e";
-      description = ''
+      description = lib.mdDoc ''
         Volume ID for the /boot/firmware partition on the SD card. This value
         must be a 32-bit hexadecimal number.
       '';
@@ -83,7 +83,7 @@ in
     firmwarePartitionName = mkOption {
       type = types.str;
       default = "FIRMWARE";
-      description = ''
+      description = lib.mdDoc ''
         Name of the filesystem which holds the boot firmware.
       '';
     };
@@ -92,7 +92,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7";
-      description = ''
+      description = lib.mdDoc ''
         UUID for the filesystem on the main NixOS partition on the SD card.
       '';
     };
@@ -101,14 +101,14 @@ in
       type = types.int;
       # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB
       default = 30;
-      description = ''
+      description = lib.mdDoc ''
         Size of the /boot/firmware partition, in megabytes.
       '';
     };
 
     populateFirmwareCommands = mkOption {
       example = literalExpression "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to populate the ./firmware directory.
         All files in that directory are copied to the
         /boot/firmware partition on the SD image.
@@ -117,7 +117,7 @@ in
 
     populateRootCommands = mkOption {
       example = literalExpression "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''";
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to populate the ./files directory.
         All files in that directory are copied to the
         root (/) partition on the SD image. Use this to
@@ -128,7 +128,7 @@ in
     postBuildCommands = mkOption {
       example = literalExpression "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''";
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to run after the image is built.
         Can be used for boards requiring to dd u-boot SPL before actual partitions.
       '';
@@ -137,16 +137,16 @@ in
     compressImage = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether the SD image should be compressed using
-        <command>zstd</command>.
+        {command}`zstd`.
       '';
     };
 
     expandOnBoot = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to configure the sd image to expand it's partition on boot.
       '';
     };
@@ -224,14 +224,25 @@ in
         # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
         eval $(partx $img -o START,SECTORS --nr 1 --pairs)
         truncate -s $((SECTORS * 512)) firmware_part.img
-        faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img
+
+        mkfs.vfat --invariant -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img
 
         # Populate the files intended for /boot/firmware
         mkdir firmware
         ${config.sdImage.populateFirmwareCommands}
 
+        find firmware -exec touch --date=2000-01-01 {} +
         # Copy the populated /boot/firmware into the SD image
-        (cd firmware; mcopy -psvm -i ../firmware_part.img ./* ::)
+        cd firmware
+        # Force a fixed order in mcopy for better determinism, and avoid file globbing
+        for d in $(find . -type d -mindepth 1 | sort); do
+          faketime "2000-01-01 00:00:00" mmd -i ../firmware_part.img "::/$d"
+        done
+        for f in $(find . -type f | sort); do
+          mcopy -pvm -i ../firmware_part.img "$f" "::/$f"
+        done
+        cd ..
+
         # Verify the FAT partition before copying it.
         fsck.vfat -vn firmware_part.img
         dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
diff --git a/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix
index 0035ceca6fc9..582334a5aeaf 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/3af6g226v4hsv6x7xzh23d6wqyq0nzjp-nix-2.10.3";
-  i686-linux = "/nix/store/43xxh2jip6rpdhylc5z9a5fxx54dw206-nix-2.10.3";
-  aarch64-linux = "/nix/store/6qw3r57nra08ars8j8zyj3fl8lz4cvnd-nix-2.10.3";
-  x86_64-darwin = "/nix/store/3b7qrm0qjw57fmznrsvm0ai568i89hc2-nix-2.10.3";
-  aarch64-darwin = "/nix/store/gp7k17iy1n7hgf97qwnxw28c6v9nhb1i-nix-2.10.3";
+  x86_64-linux = "/nix/store/ny9r65799s7xhp605bc2753sjvzkxrrs-nix-2.15.1";
+  i686-linux = "/nix/store/ck55dz5klc7szi8rx9ghhm8gi2b5q5bw-nix-2.15.1";
+  aarch64-linux = "/nix/store/cl0a02vr28913dgw98hrm45a4baqr3z1-nix-2.15.1";
+  x86_64-darwin = "/nix/store/wq228jdbz16pp2lnxf32n8dv27pw53p8-nix-2.15.1";
+  aarch64-darwin = "/nix/store/x11cpsjg4q236msfz5scc325pfp9xy64-nix-2.15.1";
 }
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
index b4a94f62ad93..21a257378a63 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
@@ -15,7 +15,7 @@ let
     inherit system pkgs;
   };
 
-  interactiveDriver = (testing.makeTest { inherit nodes; testScript = "start_all(); join_all();"; }).driverInteractive;
+  interactiveDriver = (testing.makeTest { inherit nodes; name = "network"; testScript = "start_all(); join_all();"; }).test.driverInteractive;
 in
 
 
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh b/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
index 89beeee7cf9e..9141cc285702 100644..100755
--- a/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
@@ -97,11 +97,12 @@ chroot_add_resolv_conf "$mountPoint" || echo "$0: failed to set up resolv.conf"
         exec 2>/dev/null
     fi
 
-    # Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings.
+    # Run the activation script. Set $LOCALE_ARCHIVE to suppress some Perl locale warnings.
     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
+    # Create /tmp. This is needed for nix-build and the NixOS activation script to work.
+    # Hide the unhelpful "failed to replace specifiers" errors caused by missing /etc/machine-id.
+    chroot "$mountPoint" "$system/sw/bin/systemd-tmpfiles" --create --remove -E 2> /dev/null || true
 )
 
 unset TMPDIR
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl b/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
index 212b2b3cd23a..c2a5ecbe9e2e 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -85,12 +85,7 @@ sub debug {
 
 
 # nixpkgs.system
-my ($status, @systemLines) = runCommand("@nixInstantiate@ --impure --eval --expr builtins.currentSystem");
-if ($status != 0 || join("", @systemLines) =~ /error/) {
-    die "Failed to retrieve current system type from nix.\n";
-}
-chomp(my $system = @systemLines[0]);
-push @attrs, "nixpkgs.hostPlatform = lib.mkDefault $system;";
+push @attrs, "nixpkgs.hostPlatform = lib.mkDefault \"@system@\";";
 
 
 my $cpuinfo = read_file "/proc/cpuinfo";
@@ -127,9 +122,6 @@ 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.
@@ -203,7 +195,7 @@ sub pciCheck {
     }
 
     # In case this is a virtio scsi device, we need to explicitly make this available.
-    if ($vendor eq "0x1af4" && $device eq "0x1004") {
+    if ($vendor eq "0x1af4" && ($device eq "0x1004" || $device eq "0x1048") ) {
         push @initrdAvailableKernelModules, "virtio_scsi";
     }
 
@@ -324,11 +316,15 @@ if ($virt eq "systemd-nspawn") {
 }
 
 
-# Provide firmware for devices that are not detected by this script,
-# unless we're in a VM/container.
-push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")"
-    if $virt eq "none";
+# Check if we're on bare metal, not in a VM/container.
+if ($virt eq "none") {
+    # Provide firmware for devices that are not detected by this script.
+    push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")";
 
+    # Update the microcode.
+    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";
+}
 
 # For a device name like /dev/sda1, find a more stable path like
 # /dev/disk/by-uuid/X or /dev/disk/by-label/Y.
@@ -339,7 +335,7 @@ sub findStableDevPath {
 
     my $st = stat($dev) or return $dev;
 
-    foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
+    foreach my $dev2 (glob("/dev/stratis/*/*"), glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
         my $st2 = stat($dev2) or next;
         return $dev2 if $st->rdev == $st2->rdev;
     }
@@ -471,14 +467,25 @@ EOF
         }
     }
 
+    # is this a stratis fs?
+    my $stableDevPath = findStableDevPath $device;
+    my $stratisPool;
+    if ($stableDevPath =~ qr#/dev/stratis/(.*)/.*#) {
+        my $poolName = $1;
+        my ($header, @lines) = split "\n", qx/stratis pool list/;
+        my $uuidIndex = index $header, 'UUID';
+        my ($line) = grep /^$poolName /, @lines;
+        $stratisPool = substr $line, $uuidIndex - 32, 36;
+    }
+
     # Don't emit tmpfs entry for /tmp, because it most likely comes from the
-    # boot.tmpOnTmpfs option in configuration.nix (managed declaratively).
+    # boot.tmp.useTmpfs option in configuration.nix (managed declaratively).
     next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
 
     # Emit the filesystem.
     $fileSystems .= <<EOF;
   fileSystems.\"$mountPoint\" =
-    { device = \"${\(findStableDevPath $device)}\";
+    { device = \"$stableDevPath\";
       fsType = \"$fsType\";
 EOF
 
@@ -488,6 +495,12 @@ EOF
 EOF
     }
 
+    if ($stratisPool) {
+        $fileSystems .= <<EOF;
+      stratis.poolUuid = "$stratisPool";
+EOF
+    }
+
     $fileSystems .= <<EOF;
     };
 
@@ -517,21 +530,6 @@ EOF
     }
 }
 
-# For lack of a better way to determine it, guess whether we should use a
-# bigger font for the console from the display mode on the first
-# framebuffer. A way based on the physical size/actual DPI reported by
-# the monitor would be nice, but I don't know how to do this without X :)
-my $fb_modes_file = "/sys/class/graphics/fb0/modes";
-if (-f $fb_modes_file && -r $fb_modes_file) {
-    my $modes = read_file($fb_modes_file);
-    $modes =~ m/([0-9]+)x([0-9]+)/;
-    my $console_width = $1, my $console_height = $2;
-    if ($console_width > 1920) {
-        push @attrs, "# high-resolution display";
-        push @attrs, 'hardware.video.hidpi.enable = lib.mkDefault true;';
-    }
-}
-
 
 # Generate the hardware configuration file.
 
@@ -670,7 +668,6 @@ EOF
             $bootLoaderConfig = <<EOF;
   # Use the GRUB 2 boot loader.
   boot.loader.grub.enable = true;
-  boot.loader.grub.version = 2;
   # boot.loader.grub.efiSupport = true;
   # boot.loader.grub.efiInstallAsRemovable = true;
   # boot.loader.efi.efiSysMountPoint = "/boot/efi";
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-install.sh b/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
index e7cf52f5e32b..20fec525e70b 100644..100755
--- a/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
@@ -195,7 +195,20 @@ if [[ -z $noBootLoader ]]; then
     echo "installing the boot loader..."
     # Grub needs an mtab.
     ln -sfn /proc/mounts "$mountPoint"/etc/mtab
-    NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root "$mountPoint" -- /run/current-system/bin/switch-to-configuration boot
+    export mountPoint
+    NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root "$mountPoint" -c "$(cat <<'EOF'
+      # Create a bind mount for each of the mount points inside the target file
+      # system. This preserves the validity of their absolute paths after changing
+      # the root with `nixos-enter`.
+      # Without this the bootloader installation may fail due to options that
+      # contain paths referenced during evaluation, like initrd.secrets.
+      # when not root, re-execute the script in an unshared namespace
+      mount --rbind --mkdir / "$mountPoint"
+      mount --make-rslave "$mountPoint"
+      /run/current-system/bin/switch-to-configuration boot
+      umount -R "$mountPoint" && rmdir "$mountPoint"
+EOF
+)"
 fi
 
 # Ask the user to set a root password, but only if the passwd command
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-version.sh b/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
index 59a9c572b418..39e34a3718cb 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
@@ -8,11 +8,18 @@ case "$1" in
     ;;
   --hash|--revision)
     if ! [[ @revision@ =~ ^[0-9a-f]+$ ]]; then
-      echo "$0: Nixpkgs commit hash is unknown"
+      echo "$0: Nixpkgs commit hash is unknown" >&2
       exit 1
     fi
     echo "@revision@"
     ;;
+  --configuration-revision)
+    if [[ "@configurationRevision@" =~ "@" ]]; then
+      echo "$0: configuration revision is unknown" >&2
+      exit 1
+    fi
+    echo "@configurationRevision@"
+    ;;
   --json)
     cat <<EOF
 @json@
diff --git a/nixpkgs/nixos/modules/installer/tools/tools.nix b/nixpkgs/nixos/modules/installer/tools/tools.nix
index e46a2df8fa6a..5133ad18f4bb 100644
--- a/nixpkgs/nixos/modules/installer/tools/tools.nix
+++ b/nixpkgs/nixos/modules/installer/tools/tools.nix
@@ -25,6 +25,7 @@ let
     path = makeBinPath [
       pkgs.jq
       nixos-enter
+      pkgs.util-linuxMinimal
     ];
   };
 
@@ -34,7 +35,7 @@ let
     name = "nixos-generate-config";
     src = ./nixos-generate-config.pl;
     perl = "${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl";
-    nixInstantiate = "${pkgs.nix}/bin/nix-instantiate";
+    system = pkgs.stdenv.hostPlatform.system;
     detectvirt = "${config.systemd.package}/bin/systemd-detect-virt";
     btrfs = "${pkgs.btrfs-progs}/bin/btrfs";
     inherit (config.system.nixos-generate-config) configuration desktopConfiguration;
@@ -65,6 +66,9 @@ let
     name = "nixos-enter";
     src = ./nixos-enter.sh;
     inherit (pkgs) runtimeShell;
+    path = makeBinPath [
+      pkgs.util-linuxMinimal
+    ];
   };
 
 in
@@ -123,7 +127,7 @@ in
     system.nixos-generate-config.configuration = mkDefault ''
       # Edit this configuration file to define what should be installed on
       # your system.  Help is available in the configuration.nix(5) man page
-      # and in the NixOS manual (accessible by running ‘nixos-help’).
+      # and in the NixOS manual (accessible by running `nixos-help`).
 
       { config, pkgs, ... }:
 
@@ -159,10 +163,7 @@ in
       $desktopConfiguration
         # Configure keymap in X11
         # services.xserver.layout = "us";
-        # services.xserver.xkbOptions = {
-        #   "eurosign:e";
-        #   "caps:escape" # map caps to escape.
-        # };
+        # services.xserver.xkbOptions = "eurosign:e,caps:escape";
 
         # Enable CUPS to print documents.
         # services.printing.enable = true;
@@ -180,7 +181,7 @@ in
         #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
         #   packages = with pkgs; [
         #     firefox
-        #     thunderbird
+        #     tree
         #   ];
         # };
 
@@ -217,7 +218,7 @@ in
 
         # 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
+        # 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).
@@ -235,6 +236,8 @@ in
         nixos-enter
       ] ++ lib.optional (nixos-option != null) nixos-option;
 
+    documentation.man.man-db.skipPackages = [ nixos-version ];
+
     system.build = {
       inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-enter;
     };
diff --git a/nixpkgs/nixos/modules/misc/assertions.nix b/nixpkgs/nixos/modules/misc/assertions.nix
index 550b3ac97f6a..364bb02be82d 100644
--- a/nixpkgs/nixos/modules/misc/assertions.nix
+++ b/nixpkgs/nixos/modules/misc/assertions.nix
@@ -11,7 +11,7 @@ with lib;
       internal = true;
       default = [];
       example = [ { assertion = false; message = "you can't enable this for that reason"; } ];
-      description = ''
+      description = lib.mdDoc ''
         This option allows modules to express conditions that must
         hold for the evaluation of the system configuration to
         succeed, along with associated error messages for the user.
@@ -23,7 +23,7 @@ with lib;
       default = [];
       type = types.listOf types.str;
       example = [ "The `foo' service is deprecated and will go away soon!" ];
-      description = ''
+      description = lib.mdDoc ''
         This option allows modules to show warnings to users during
         the evaluation of the system configuration.
       '';
diff --git a/nixpkgs/nixos/modules/misc/documentation.nix b/nixpkgs/nixos/modules/misc/documentation.nix
index fb2ed5b023be..4abd55c1bb97 100644
--- a/nixpkgs/nixos/modules/misc/documentation.nix
+++ b/nixpkgs/nixos/modules/misc/documentation.nix
@@ -38,6 +38,7 @@ let
           modules = [ {
             _module.check = false;
           } ] ++ docModules.eager;
+          class = "nixos";
           specialArgs = specialArgs // {
             pkgs = scrubDerivations "pkgs" pkgs;
             # allow access to arbitrary options for eager modules, eg for getting
@@ -48,14 +49,20 @@ let
         };
         scrubDerivations = namePrefix: pkgSet: mapAttrs
           (name: value:
-            let wholeName = "${namePrefix}.${name}"; in
-            if isAttrs value then
+            let
+              wholeName = "${namePrefix}.${name}";
+              guard = lib.warn "Attempt to evaluate package ${wholeName} in option documentation; this is not supported and will eventually be an error. Use `mkPackageOption{,MD}` or `literalExpression` instead.";
+            in if isAttrs value then
               scrubDerivations wholeName value
-              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
+              // optionalAttrs (isDerivation value) {
+                outPath = guard "\${${wholeName}}";
+                drvPath = guard drvPath;
+              }
             else value
           )
           pkgSet;
       in scrubbedEval.options;
+
     baseOptionsJSON =
       let
         filter =
@@ -67,9 +74,9 @@ let
             );
       in
         pkgs.runCommand "lazy-options.json" {
-          libPath = filter "${toString pkgs.path}/lib";
-          pkgsLibPath = filter "${toString pkgs.path}/pkgs/pkgs-lib";
-          nixosPath = filter "${toString pkgs.path}/nixos";
+          libPath = filter (pkgs.path + "/lib");
+          pkgsLibPath = filter (pkgs.path + "/pkgs/pkgs-lib");
+          nixosPath = filter (pkgs.path + "/nixos");
           modules = map (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy;
         } ''
           export NIX_STORE_DIR=$TMPDIR/store
@@ -99,7 +106,8 @@ let
               exit 1
             } >&2
         '';
-    inherit (cfg.nixos.options) warningsAreErrors;
+
+    inherit (cfg.nixos.options) warningsAreErrors allowDocBook;
   };
 
 
@@ -124,7 +132,8 @@ let
     desktopItem = pkgs.makeDesktopItem {
       name = "nixos-manual";
       desktopName = "NixOS Manual";
-      genericName = "View NixOS documentation in a web browser";
+      genericName = "System Manual";
+      comment = "View NixOS documentation in a web browser";
       icon = "nix-snowflake";
       exec = "nixos-help";
       categories = ["System"];
@@ -235,22 +244,21 @@ in
       nixos.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install NixOS's own documentation.
-          <itemizedlist>
-          <listitem><para>This includes man pages like
-                    <citerefentry><refentrytitle>configuration.nix</refentrytitle><manvolnum>5</manvolnum></citerefentry> if <option>documentation.man.enable</option> is
-                    set.</para></listitem>
-          <listitem><para>This includes the HTML manual and the <command>nixos-help</command> command if
-                    <option>documentation.doc.enable</option> is set.</para></listitem>
-          </itemizedlist>
+
+          - This includes man pages like
+            {manpage}`configuration.nix(5)` if {option}`documentation.man.enable` is
+            set.
+          - This includes the HTML manual and the {command}`nixos-help` command if
+            {option}`documentation.doc.enable` is set.
         '';
       };
 
       nixos.extraModules = mkOption {
         type = types.listOf types.raw;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Modules for which to show options even when not imported.
         '';
       };
@@ -265,6 +273,23 @@ in
         '';
       };
 
+      nixos.options.allowDocBook = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Whether to allow DocBook option docs. When set to `false` all option using
+          DocBook documentation will cause a manual build error; additionally a new
+          renderer may be used.
+
+          ::: {.note}
+          The `false` setting for this option is not yet fully supported. While it
+          should work fine and produce the same output as the previous toolchain
+          using DocBook it may not work in all circumstances. Whether markdown option
+          documentation is allowed is independent of this option.
+          :::
+        '';
+      };
+
       nixos.options.warningsAreErrors = mkOption {
         type = types.bool;
         default = true;
@@ -343,6 +368,14 @@ in
     (mkIf cfg.nixos.enable {
       system.build.manual = manual;
 
+      system.activationScripts.check-manual-docbook = ''
+        if [[ $(cat ${manual.optionsUsedDocbook}) = 1 ]]; then
+          echo -e "\e[31;1mwarning\e[0m: This configuration contains option documentation in docbook." \
+                  "Support for docbook is deprecated and will be removed after NixOS 23.05." \
+                  "See nix-store --read-log ${builtins.unsafeDiscardStringContext manual.optionsJSON.drvPath}"
+        fi
+      '';
+
       environment.systemPackages = []
         ++ optional cfg.man.enable manual.manpages
         ++ optionals cfg.doc.enable [ manual.manualHTML nixos-help ];
diff --git a/nixpkgs/nixos/modules/misc/documentation/test.nix b/nixpkgs/nixos/modules/misc/documentation/test.nix
index 1eaa63b1fb6c..dd1588abdb43 100644
--- a/nixpkgs/nixos/modules/misc/documentation/test.nix
+++ b/nixpkgs/nixos/modules/misc/documentation/test.nix
@@ -30,7 +30,7 @@ let
     specialArgs.someArg.myModule = { lib, ... }: {
       options.foobar = lib.mkOption {
         type = lib.types.str;
-        description = "The foobar option was added via specialArgs";
+        description = lib.mdDoc "The foobar option was added via specialArgs";
         default = "qux";
       };
     };
diff --git a/nixpkgs/nixos/modules/misc/ids.nix b/nixpkgs/nixos/modules/misc/ids.nix
index 38ab338aa56c..5b278b5e8062 100644
--- a/nixpkgs/nixos/modules/misc/ids.nix
+++ b/nixpkgs/nixos/modules/misc/ids.nix
@@ -19,7 +19,7 @@ in
 
     ids.uids = lib.mkOption {
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         The user IDs used in NixOS.
       '';
       type = types.attrsOf types.int;
@@ -27,7 +27,7 @@ in
 
     ids.gids = lib.mkOption {
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         The group IDs used in NixOS.
       '';
       type = types.attrsOf types.int;
@@ -233,7 +233,7 @@ in
       # nix-serve = 199; # unused, removed 2020-12-12
       #tvheadend = 200; # dynamically allocated as of 2021-09-18
       uwsgi = 201;
-      gitit = 202;
+      # gitit = 202; # unused, module was removed 2023-04-03
       riemanntools = 203;
       subsonic = 204;
       # riak = 205; # unused, remove 2022-07-22
@@ -338,7 +338,7 @@ in
       lidarr = 306;
       slurm = 307;
       kapacitor = 308;
-      solr = 309;
+      # solr = 309; removed 2023-03-16
       alerta = 310;
       minetest = 311;
       rss2email = 312;
@@ -355,6 +355,7 @@ in
       pipewire = 323;
       rstudio-server = 324;
       localtimed = 325;
+      automatic-timezoned = 326;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -647,7 +648,7 @@ in
       lidarr = 306;
       slurm = 307;
       kapacitor = 308;
-      solr = 309;
+      # solr = 309; removed 2023-03-16
       alerta = 310;
       minetest = 311;
       rss2email = 312;
@@ -664,6 +665,7 @@ in
       pipewire = 323;
       rstudio-server = 324;
       localtimed = 325;
+      automatic-timezoned = 326;
 
       # 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/label.nix b/nixpkgs/nixos/modules/misc/label.nix
index b97cbaa26304..44ee812249ce 100644
--- a/nixpkgs/nixos/modules/misc/label.nix
+++ b/nixpkgs/nixos/modules/misc/label.nix
@@ -12,7 +12,7 @@ in
 
     nixos.label = mkOption {
       type = types.strMatching "[a-zA-Z0-9:_\\.-]*";
-      description = ''
+      description = lib.mdDoc ''
         NixOS version name to be used in the names of generated
         outputs and boot labels.
 
@@ -20,25 +20,26 @@ in
         this is the option for you.
 
         It can only contain letters, numbers and the following symbols:
-        <literal>:</literal>, <literal>_</literal>, <literal>.</literal> and <literal>-</literal>.
+        `:`, `_`, `.` and `-`.
 
-        The default is <option>system.nixos.tags</option> separated by
-        "-" + "-" + <envar>NIXOS_LABEL_VERSION</envar> environment
+        The default is {option}`system.nixos.tags` separated by
+        "-" + "-" + {env}`NIXOS_LABEL_VERSION` environment
         variable (defaults to the value of
-        <option>system.nixos.version</option>).
+        {option}`system.nixos.version`).
 
-        Can be overriden by setting <envar>NIXOS_LABEL</envar>.
+        Can be overridden by setting {env}`NIXOS_LABEL`.
 
         Useful for not loosing track of configurations built from different
         nixos branches/revisions, e.g.:
 
-        <screen>
+        ```
         #!/bin/sh
         today=`date +%Y%m%d`
         branch=`(cd nixpkgs ; git branch 2>/dev/null | sed -n '/^\* / { s|^\* ||; p; }')`
         revision=`(cd nixpkgs ; git rev-parse HEAD)`
         export NIXOS_LABEL_VERSION="$today.$branch-''${revision:0:7}"
-        nixos-rebuild switch</screen>
+        nixos-rebuild switch
+        ```
       '';
     };
 
@@ -46,19 +47,19 @@ in
       type = types.listOf types.str;
       default = [];
       example = [ "with-xen" ];
-      description = ''
+      description = lib.mdDoc ''
         Strings to prefix to the default
-        <option>system.nixos.label</option>.
+        {option}`system.nixos.label`.
 
         Useful for not loosing track of configurations built with
         different options, e.g.:
 
-        <screen>
+        ```
         {
           system.nixos.tags = [ "with-xen" ];
           virtualisation.xen.enable = true;
         }
-        </screen>
+        ```
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/misc/locate.nix b/nixpkgs/nixos/modules/misc/locate.nix
index b83e280b2846..acf441cda628 100644
--- a/nixpkgs/nixos/modules/misc/locate.nix
+++ b/nixpkgs/nixos/modules/misc/locate.nix
@@ -183,8 +183,8 @@ in
     pruneNames = mkOption {
       type = listOf str;
       default = lib.optionals (!isFindutils) [ ".bzr" ".cache" ".git" ".hg" ".svn" ];
-      defaultText = literalDocBook ''
-        <literal>[ ".bzr" ".cache" ".git" ".hg" ".svn" ]</literal>, if
+      defaultText = literalMD ''
+        `[ ".bzr" ".cache" ".git" ".hg" ".svn" ]`, if
         supported by the locate implementation (i.e. mlocate or plocate).
       '';
       description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/misc/man-db.nix b/nixpkgs/nixos/modules/misc/man-db.nix
index 27c688c11cde..75f822c3448f 100644
--- a/nixpkgs/nixos/modules/misc/man-db.nix
+++ b/nixpkgs/nixos/modules/misc/man-db.nix
@@ -7,17 +7,27 @@ in
 {
   options = {
     documentation.man.man-db = {
-      enable = lib.mkEnableOption "man-db as the default man page viewer" // {
+      enable = lib.mkEnableOption (lib.mdDoc "man-db as the default man page viewer") // {
         default = config.documentation.man.enable;
         defaultText = lib.literalExpression "config.documentation.man.enable";
         example = false;
       };
 
+      skipPackages = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [];
+        internal = true;
+        description = lib.mdDoc ''
+          Packages to *not* include in the man-db.
+          This can be useful to avoid unnecessary rebuilds due to packages that change frequently, like nixos-version.
+        '';
+      };
+
       manualPages = lib.mkOption {
         type = lib.types.path;
         default = pkgs.buildEnv {
           name = "man-paths";
-          paths = config.environment.systemPackages;
+          paths = lib.subtractLists cfg.skipPackages config.environment.systemPackages;
           pathsToLink = [ "/share/man" ];
           extraOutputsToInstall = [ "man" ]
             ++ lib.optionals config.documentation.dev.enable [ "devman" ];
@@ -52,9 +62,11 @@ in
     environment.systemPackages = [ cfg.package ];
     environment.etc."man_db.conf".text =
       let
-        manualCache = pkgs.runCommandLocal "man-cache" { } ''
+        manualCache = pkgs.runCommand "man-cache" {
+          nativeBuildInputs = [ cfg.package ];
+        } ''
           echo "MANDB_MAP ${cfg.manualPages}/share/man $out" > man.conf
-          ${cfg.package}/bin/mandb -C man.conf -psc >/dev/null 2>&1
+          mandb -C man.conf -psc >/dev/null 2>&1
         '';
       in
       ''
diff --git a/nixpkgs/nixos/modules/misc/mandoc.nix b/nixpkgs/nixos/modules/misc/mandoc.nix
index d67c42bff6a0..9bcef5b1a09b 100644
--- a/nixpkgs/nixos/modules/misc/mandoc.nix
+++ b/nixpkgs/nixos/modules/misc/mandoc.nix
@@ -10,7 +10,7 @@ in {
 
   options = {
     documentation.man.mandoc = {
-      enable = lib.mkEnableOption "mandoc as the default man page viewer";
+      enable = lib.mkEnableOption (lib.mdDoc "mandoc as the default man page viewer");
 
       manPath = lib.mkOption {
         type = with lib.types; listOf str;
diff --git a/nixpkgs/nixos/modules/misc/meta.nix b/nixpkgs/nixos/modules/misc/meta.nix
index 8e689a63f6bf..95f2765aff1e 100644
--- a/nixpkgs/nixos/modules/misc/meta.nix
+++ b/nixpkgs/nixos/modules/misc/meta.nix
@@ -38,7 +38,7 @@ in
         internal = true;
         default = [];
         example = literalExpression ''[ lib.maintainers.all ]'';
-        description = ''
+        description = lib.mdDoc ''
           List of maintainers of each module.  This option should be defined at
           most once per module.
         '';
@@ -47,8 +47,8 @@ in
       doc = mkOption {
         type = docFile;
         internal = true;
-        example = "./meta.chapter.xml";
-        description = ''
+        example = "./meta.chapter.md";
+        description = lib.mdDoc ''
           Documentation prologue for the set of options of each module.  This
           option should be defined at most once per module.
         '';
@@ -60,7 +60,7 @@ in
         };
         internal = true;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to include this module in the split options doc build.
           Disable if the module references `config`, `pkgs` or other module
           arguments that cannot be evaluated as constants.
diff --git a/nixpkgs/nixos/modules/misc/nixops-autoluks.nix b/nixpkgs/nixos/modules/misc/nixops-autoluks.nix
index 20c143286afa..221b34f3cc36 100644
--- a/nixpkgs/nixos/modules/misc/nixops-autoluks.nix
+++ b/nixpkgs/nixos/modules/misc/nixops-autoluks.nix
@@ -5,7 +5,7 @@ let
 
   inherit (config.nixops) enableDeprecatedAutoLuks;
 in {
-  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption "Enable the deprecated NixOps AutoLuks module";
+  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption (lib.mdDoc "Enable the deprecated NixOps AutoLuks module");
 
   config = {
     assertions = [
diff --git a/nixpkgs/nixos/modules/misc/nixpkgs.nix b/nixpkgs/nixos/modules/misc/nixpkgs.nix
index 721a041a5830..55ec08acf445 100644
--- a/nixpkgs/nixos/modules/misc/nixpkgs.nix
+++ b/nixpkgs/nixos/modules/misc/nixpkgs.nix
@@ -23,12 +23,12 @@ let
     optionalAttrs (lhs ? packageOverrides) {
       packageOverrides = pkgs:
         optCall lhs.packageOverrides pkgs //
-        optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs;
+        optCall (attrByPath [ "packageOverrides" ] { } rhs) pkgs;
     } //
     optionalAttrs (lhs ? perlPackageOverrides) {
       perlPackageOverrides = pkgs:
         optCall lhs.perlPackageOverrides pkgs //
-        optCall (attrByPath ["perlPackageOverrides"] ({}) rhs) pkgs;
+        optCall (attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs;
     };
 
   configType = mkOptionType {
@@ -49,12 +49,17 @@ let
     merge = lib.mergeOneOption;
   };
 
-  pkgsType = mkOptionType {
-    name = "nixpkgs";
+  pkgsType = types.pkgs // {
+    # This type is only used by itself, so let's elaborate the description a bit
+    # for the purpose of documentation.
     description = "An evaluation of Nixpkgs; the top level attribute set of packages";
-    check = builtins.isAttrs;
   };
 
+  # Whether `pkgs` was constructed by this module - not if nixpkgs.pkgs or
+  # _module.args.pkgs is set. However, determining whether _module.args.pkgs
+  # is defined elsewhere does not seem feasible.
+  constructedByMe = !opt.pkgs.isDefined;
+
   hasBuildPlatform = opt.buildPlatform.highestPrio < (mkOptionDefault {}).priority;
   hasHostPlatform = opt.hostPlatform.isDefined;
   hasPlatform = hasHostPlatform || hasBuildPlatform;
@@ -62,11 +67,6 @@ let
   # Context for messages
   hostPlatformLine = optionalString hasHostPlatform "${showOptionWithDefLocs opt.hostPlatform}";
   buildPlatformLine = optionalString hasBuildPlatform "${showOptionWithDefLocs opt.buildPlatform}";
-  platformLines = optionalString hasPlatform ''
-    Your system configuration configures nixpkgs with platform parameters:
-    ${hostPlatformLine
-    }${buildPlatformLine
-    }'';
 
   legacyOptionsDefined =
     optional (opt.localSystem.highestPrio < (mkDefault {}).priority) opt.system
@@ -117,13 +117,13 @@ in
       '';
       type = pkgsType;
       example = literalExpression "import <nixpkgs> {}";
-      description = ''
+      description = lib.mdDoc ''
         If set, the pkgs argument to all NixOS modules is the value of
-        this option, extended with <literal>nixpkgs.overlays</literal>, if
-        that is also set. Either <literal>nixpkgs.crossSystem</literal> or
-        <literal>nixpkgs.localSystem</literal> will be used in an assertion
+        this option, extended with `nixpkgs.overlays`, if
+        that is also set. Either `nixpkgs.crossSystem` or
+        `nixpkgs.localSystem` will be used in an assertion
         to check that the NixOS and Nixpkgs architectures match. Any
-        other options in <literal>nixpkgs.*</literal>, notably <literal>config</literal>,
+        other options in `nixpkgs.*`, notably `config`,
         will be ignored.
 
         If unset, the pkgs argument to all NixOS modules is determined
@@ -132,18 +132,18 @@ in
         The default value imports the Nixpkgs source files
         relative to the location of this NixOS module, because
         NixOS and Nixpkgs are distributed together for consistency,
-        so the <literal>nixos</literal> in the default value is in fact a
-        relative path. The <literal>config</literal>, <literal>overlays</literal>,
-        <literal>localSystem</literal>, and <literal>crossSystem</literal> come
+        so the `nixos` in the default value is in fact a
+        relative path. The `config`, `overlays`,
+        `localSystem`, and `crossSystem` come
         from this option's siblings.
 
         This option can be used by applications like NixOps to increase
         the performance of evaluation, or to create packages that depend
         on a container that should be built with the exact same evaluation
         of Nixpkgs, for example. Applications like this should set
-        their default value using <literal>lib.mkDefault</literal>, so
+        their default value using `lib.mkDefault`, so
         user-provided configuration can override it without using
-        <literal>lib</literal>.
+        `lib`.
 
         Note that using a distinct version of Nixpkgs with NixOS may
         be an unexpected source of problems. Use this option with care.
@@ -302,7 +302,7 @@ in
           ''
         else
           throw ''
-            Neither ${opt.hostPlatform} nor or the legacy option ${opt.system} has been set.
+            Neither ${opt.hostPlatform} nor the legacy option ${opt.system} has been set.
             You can set ${opt.hostPlatform} in hardware-configuration.nix by re-running
             a recent version of nixos-generate-config.
             The option ${opt.system} is still fully supported for NixOS 22.05 interoperability,
@@ -311,33 +311,33 @@ in
       defaultText = lib.literalMD ''
         Traditionally `builtins.currentSystem`, but unset when invoking NixOS through `lib.nixosSystem`.
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option does not need to be specified for NixOS configurations
-        with a recently generated <literal>hardware-configuration.nix</literal>.
+        with a recently generated `hardware-configuration.nix`.
 
         Specifies the Nix platform type on which NixOS should be built.
-        It is better to specify <literal>nixpkgs.localSystem</literal> instead.
-        <programlisting>
+        It is better to specify `nixpkgs.localSystem` instead.
+        ```
         {
           nixpkgs.system = ..;
         }
-        </programlisting>
+        ```
         is the same as
-        <programlisting>
+        ```
         {
           nixpkgs.localSystem.system = ..;
         }
-        </programlisting>
-        See <literal>nixpkgs.localSystem</literal> for more information.
+        ```
+        See `nixpkgs.localSystem` for more information.
 
-        Ignored when <literal>nixpkgs.pkgs</literal>, <literal>nixpkgs.localSystem</literal> or <literal>nixpkgs.hostPlatform</literal> is set.
+        Ignored when `nixpkgs.pkgs`, `nixpkgs.localSystem` or `nixpkgs.hostPlatform` is set.
       '';
     };
   };
 
   config = {
     _module.args = {
-      pkgs = finalPkgs;
+      pkgs = finalPkgs.__splicedPackages;
     };
 
     assertions = [
@@ -353,12 +353,12 @@ in
             else "nixpkgs.localSystem";
           pkgsSystem = finalPkgs.stdenv.targetPlatform.system;
         in {
-          assertion = !hasPlatform -> nixosExpectedSystem == pkgsSystem;
+          assertion = constructedByMe -> !hasPlatform -> nixosExpectedSystem == pkgsSystem;
           message = "The NixOS nixpkgs.pkgs option was set to a Nixpkgs invocation that compiles to target system ${pkgsSystem} but NixOS was configured for system ${nixosExpectedSystem} via NixOS option ${nixosOption}. The NixOS system settings must match the Nixpkgs target system.";
         }
       )
       {
-        assertion = hasPlatform -> legacyOptionsDefined == [];
+        assertion = constructedByMe -> hasPlatform -> legacyOptionsDefined == [];
         message = ''
           Your system configures nixpkgs with the platform parameter${optionalString hasBuildPlatform "s"}:
           ${hostPlatformLine
diff --git a/nixpkgs/nixos/modules/misc/nixpkgs/read-only.nix b/nixpkgs/nixos/modules/misc/nixpkgs/read-only.nix
new file mode 100644
index 000000000000..2a783216a9d5
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/nixpkgs/read-only.nix
@@ -0,0 +1,74 @@
+# A replacement for the traditional nixpkgs module, such that none of the modules
+# can add their own configuration. This ensures that the Nixpkgs configuration is
+# exactly as the user intends.
+# This may also be used as a performance optimization when evaluating multiple
+# configurations at once, with a shared `pkgs`.
+
+# This is a separate module, because merging this logic into the nixpkgs module
+# is too burdensome, considering that it is already burdened with legacy.
+# Moving this logic into a module does not lose any composition benefits, because
+# its purpose is not something that composes anyway.
+
+{ lib, config, ... }:
+
+let
+  cfg = config.nixpkgs;
+  inherit (lib) mkOption types;
+
+in
+{
+  disabledModules = [
+    ../nixpkgs.nix
+  ];
+  options = {
+    nixpkgs = {
+      pkgs = mkOption {
+        type = lib.types.pkgs;
+        description = lib.mdDoc ''The pkgs module argument.'';
+      };
+      config = mkOption {
+        internal = true;
+        type = types.unique { message = "nixpkgs.config is set to read-only"; } types.anything;
+        description = lib.mdDoc ''
+          The Nixpkgs `config` that `pkgs` was initialized with.
+        '';
+      };
+      overlays = mkOption {
+        internal = true;
+        type = types.unique { message = "nixpkgs.overlays is set to read-only"; } types.anything;
+        description = lib.mdDoc ''
+          The Nixpkgs overlays that `pkgs` was initialized with.
+        '';
+      };
+      hostPlatform = mkOption {
+        internal = true;
+        readOnly = true;
+        description = lib.mdDoc ''
+          The platform of the machine that is running the NixOS configuration.
+        '';
+      };
+      buildPlatform = mkOption {
+        internal = true;
+        readOnly = true;
+        description = lib.mdDoc ''
+          The platform of the machine that built the NixOS configuration.
+        '';
+      };
+      # NOTE: do not add the legacy options such as localSystem here. Let's keep
+      #       this module simple and let module authors upgrade their code instead.
+    };
+  };
+  config = {
+    _module.args.pkgs =
+      # find mistaken definitions
+      builtins.seq cfg.config
+      builtins.seq cfg.overlays
+      builtins.seq cfg.hostPlatform
+      builtins.seq cfg.buildPlatform
+      cfg.pkgs;
+    nixpkgs.config = cfg.pkgs.config;
+    nixpkgs.overlays = cfg.pkgs.overlays;
+    nixpkgs.hostPlatform = cfg.pkgs.stdenv.hostPlatform;
+    nixpkgs.buildPlatform = cfg.pkgs.stdenv.buildPlatform;
+  };
+}
diff --git a/nixpkgs/nixos/modules/misc/nixpkgs/test.nix b/nixpkgs/nixos/modules/misc/nixpkgs/test.nix
index 9e8851707f8f..0536cfc9624a 100644
--- a/nixpkgs/nixos/modules/misc/nixpkgs/test.nix
+++ b/nixpkgs/nixos/modules/misc/nixpkgs/test.nix
@@ -1,3 +1,5 @@
+# [nixpkgs]$ nix-build -A nixosTests.nixpkgs --show-trace
+
 { evalMinimalConfig, pkgs, lib, stdenv }:
 let
   eval = mod: evalMinimalConfig {
@@ -27,6 +29,47 @@ let
     let
       uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
     in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions);
+
+  readOnlyUndefined = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+  };
+
+  readOnlyBad = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = { };
+  };
+
+  readOnly = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+  };
+
+  readOnlyBadConfig = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.config.allowUnfree = true; # do in pkgs instead!
+  };
+
+  readOnlyBadOverlays = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.overlays = [ (_: _: {}) ]; # do in pkgs instead!
+  };
+
+  readOnlyBadHostPlatform = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.hostPlatform = "foo-linux"; # do in pkgs instead!
+  };
+
+  readOnlyBadBuildPlatform = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.buildPlatform = "foo-linux"; # do in pkgs instead!
+  };
+
+  throws = x: ! (builtins.tryEval x).success;
+
 in
 lib.recurseIntoAttrs {
   invokeNixpkgsSimple =
@@ -59,5 +102,27 @@ lib.recurseIntoAttrs {
           For a future proof system configuration, we recommend to remove
           the legacy definitions.
         ''];
+    assert getErrors {
+        nixpkgs.localSystem = pkgs.stdenv.hostPlatform;
+        nixpkgs.hostPlatform = pkgs.stdenv.hostPlatform;
+        nixpkgs.pkgs = pkgs;
+      } == [];
+
+
+    # Tests for the read-only.nix module
+    assert readOnly._module.args.pkgs.stdenv.hostPlatform.system == pkgs.stdenv.hostPlatform.system;
+    assert throws readOnlyBad._module.args.pkgs.stdenv;
+    assert throws readOnlyUndefined._module.args.pkgs.stdenv;
+    assert throws readOnlyBadConfig._module.args.pkgs.stdenv;
+    assert throws readOnlyBadOverlays._module.args.pkgs.stdenv;
+    assert throws readOnlyBadHostPlatform._module.args.pkgs.stdenv;
+    assert throws readOnlyBadBuildPlatform._module.args.pkgs.stdenv;
+    # read-only.nix does not provide legacy options, for the sake of simplicity
+    # If you're bothered by this, upgrade your configs to use the new *Platform
+    # options.
+    assert !readOnly.options.nixpkgs?system;
+    assert !readOnly.options.nixpkgs?localSystem;
+    assert !readOnly.options.nixpkgs?crossSystem;
+
     pkgs.emptyFile;
 }
diff --git a/nixpkgs/nixos/modules/misc/passthru.nix b/nixpkgs/nixos/modules/misc/passthru.nix
index 4e99631fdd85..beb9d7829037 100644
--- a/nixpkgs/nixos/modules/misc/passthru.nix
+++ b/nixpkgs/nixos/modules/misc/passthru.nix
@@ -7,7 +7,7 @@
   options = {
     passthru = lib.mkOption {
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         This attribute set will be exported as a system attribute.
         You can put whatever you want here.
       '';
diff --git a/nixpkgs/nixos/modules/misc/version.nix b/nixpkgs/nixos/modules/misc/version.nix
index b3cdaf5568d4..780a6b2a83a6 100644
--- a/nixpkgs/nixos/modules/misc/version.nix
+++ b/nixpkgs/nixos/modules/misc/version.nix
@@ -9,25 +9,27 @@ let
     literalExpression mkRenamedOptionModule mkDefault mkOption trivial types;
 
   needsEscaping = s: null != builtins.match "[a-zA-Z0-9]+" s;
-  escapeIfNeccessary = s: if needsEscaping s then s else ''"${lib.escape [ "\$" "\"" "\\" "\`" ] s}"'';
+  escapeIfNecessary = s: if needsEscaping s then s else ''"${lib.escape [ "\$" "\"" "\\" "\`" ] s}"'';
   attrsToText = attrs:
     concatStringsSep "\n" (
-      mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs
+      mapAttrsToList (n: v: ''${n}=${escapeIfNecessary (toString v)}'') attrs
     ) + "\n";
 
   osReleaseContents = {
-    NAME = "NixOS";
-    ID = "nixos";
+    NAME = "${cfg.distroName}";
+    ID = "${cfg.distroId}";
     VERSION = "${cfg.release} (${cfg.codeName})";
     VERSION_CODENAME = toLower cfg.codeName;
     VERSION_ID = cfg.release;
     BUILD_ID = cfg.version;
-    PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
+    PRETTY_NAME = "${cfg.distroName} ${cfg.release} (${cfg.codeName})";
     LOGO = "nix-snowflake";
-    HOME_URL = "https://nixos.org/";
-    DOCUMENTATION_URL = "https://nixos.org/learn.html";
-    SUPPORT_URL = "https://nixos.org/community.html";
-    BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
+    HOME_URL = lib.optionalString (cfg.distroId == "nixos") "https://nixos.org/";
+    DOCUMENTATION_URL = lib.optionalString (cfg.distroId == "nixos") "https://nixos.org/learn.html";
+    SUPPORT_URL = lib.optionalString (cfg.distroId == "nixos") "https://nixos.org/community.html";
+    BUG_REPORT_URL = lib.optionalString (cfg.distroId == "nixos") "https://github.com/NixOS/nixpkgs/issues";
+  } // lib.optionalAttrs (cfg.variant_id != null) {
+    VARIANT_ID = cfg.variant_id;
   };
 
   initrdReleaseContents = osReleaseContents // {
@@ -87,8 +89,35 @@ in
       description = lib.mdDoc "The NixOS release code name (e.g. `Emu`).";
     };
 
+    nixos.distroId = mkOption {
+      internal = true;
+      type = types.str;
+      default = "nixos";
+      description = lib.mdDoc "The id of the operating system";
+    };
+
+    nixos.distroName = mkOption {
+      internal = true;
+      type = types.str;
+      default = "NixOS";
+      description = lib.mdDoc "The name of the operating system";
+    };
+
+    nixos.variant_id = mkOption {
+      type = types.nullOr (types.strMatching "^[a-z0-9._-]+$");
+      default = null;
+      description = lib.mdDoc "A lower-case string identifying a specific variant or edition of the operating system";
+      example = "installer";
+    };
+
     stateVersion = mkOption {
       type = types.str;
+      # TODO Remove this and drop the default of the option so people are forced to set it.
+      # Doing this also means fixing the comment in nixos/modules/testing/test-instrumentation.nix
+      apply = v:
+        lib.warnIf (options.system.stateVersion.highestPrio == (lib.mkOptionDefault { }).priority)
+          "system.stateVersion is not set, defaulting to ${v}. Read why this matters on https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion."
+          v;
       default = cfg.release;
       defaultText = literalExpression "config.${opt.release}";
       description = lib.mdDoc ''
@@ -101,7 +130,7 @@ in
         to be compatible. The effect is that NixOS will use
         defaults corresponding to the specified release (such as using
         an older version of PostgreSQL).
-        It‘s perfectly fine and recommended to leave this value at the
+        It’s perfectly fine and recommended to leave this value at the
         release version of the first install of this system.
         Changing this option will not upgrade your system. In fact it
         is meant to stay constant exactly when you upgrade your system.
@@ -140,23 +169,15 @@ in
     environment.etc = {
       "lsb-release".text = attrsToText {
         LSB_VERSION = "${cfg.release} (${cfg.codeName})";
-        DISTRIB_ID = "nixos";
+        DISTRIB_ID = "${cfg.distroId}";
         DISTRIB_RELEASE = cfg.release;
         DISTRIB_CODENAME = toLower cfg.codeName;
-        DISTRIB_DESCRIPTION = "NixOS ${cfg.release} (${cfg.codeName})";
+        DISTRIB_DESCRIPTION = "${cfg.distroName} ${cfg.release} (${cfg.codeName})";
       };
 
       "os-release".text = attrsToText osReleaseContents;
     };
 
-    # We have to use `warnings` because when warning in the default of the option
-    # the warning would also be shown when building the manual since the manual
-    # has to evaluate the default.
-    #
-    # TODO Remove this and drop the default of the option so people are forced to set it.
-    # Doing this also means fixing the comment in nixos/modules/testing/test-instrumentation.nix
-    warnings = lib.optional (options.system.stateVersion.highestPrio == (lib.mkOptionDefault { }).priority)
-      "system.stateVersion is not set, defaulting to ${config.system.stateVersion}. Read why this matters on https://nixos.org/manual/nixos/stable/options.html#opt-system.stateVersion.";
   };
 
   # uses version info nixpkgs, which requires a full nixpkgs path
diff --git a/nixpkgs/nixos/modules/misc/wordlist.nix b/nixpkgs/nixos/modules/misc/wordlist.nix
index 988b522d7431..f01fcb6f5a91 100644
--- a/nixpkgs/nixos/modules/misc/wordlist.nix
+++ b/nixpkgs/nixos/modules/misc/wordlist.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     environment.wordlist = {
-      enable = mkEnableOption "environment variables for lists of words";
+      enable = mkEnableOption (lib.mdDoc "environment variables for lists of words");
 
       lists = mkOption {
         type = types.attrsOf (types.nonEmptyListOf types.path);
@@ -23,7 +23,7 @@ in
           }
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           A set with the key names being the environment variable you'd like to
           set and the values being a list of paths to text documents containing
           lists of words. The various files will be merged, sorted, duplicates
diff --git a/nixpkgs/nixos/modules/module-list.nix b/nixpkgs/nixos/modules/module-list.nix
index c02a248a24c3..acc30776e0ff 100644
--- a/nixpkgs/nixos/modules/module-list.nix
+++ b/nixpkgs/nixos/modules/module-list.nix
@@ -1,21 +1,13 @@
 [
+  ./config/appstream.nix
+  ./config/console.nix
   ./config/debug-info.nix
   ./config/fonts/fontconfig.nix
   ./config/fonts/fontdir.nix
   ./config/fonts/fonts.nix
   ./config/fonts/ghostscript.nix
-  ./config/xdg/autostart.nix
-  ./config/xdg/icons.nix
-  ./config/xdg/menus.nix
-  ./config/xdg/mime.nix
-  ./config/xdg/portal.nix
-  ./config/xdg/portals/wlr.nix
-  ./config/xdg/portals/lxqt.nix
-  ./config/appstream.nix
-  ./config/console.nix
-  ./config/xdg/sounds.nix
-  ./config/gtk/gtk-icon-cache.nix
   ./config/gnu.nix
+  ./config/gtk/gtk-icon-cache.nix
   ./config/i18n.nix
   ./config/iproute2.nix
   ./config/krb5/default.nix
@@ -28,9 +20,10 @@
   ./config/nsswitch.nix
   ./config/power-management.nix
   ./config/pulseaudio.nix
-  ./config/qt5.nix
+  ./config/qt.nix
   ./config/resolvconf.nix
   ./config/shells-environment.nix
+  ./config/stevenblack.nix
   ./config/swap.nix
   ./config/sysctl.nix
   ./config/system-environment.nix
@@ -39,25 +32,34 @@
   ./config/unix-odbc-drivers.nix
   ./config/users-groups.nix
   ./config/vte.nix
+  ./config/xdg/autostart.nix
+  ./config/xdg/icons.nix
+  ./config/xdg/menus.nix
+  ./config/xdg/mime.nix
+  ./config/xdg/portal.nix
+  ./config/xdg/portals/lxqt.nix
+  ./config/xdg/portals/wlr.nix
+  ./config/xdg/sounds.nix
   ./config/zram.nix
   ./hardware/acpilight.nix
   ./hardware/all-firmware.nix
   ./hardware/bladeRF.nix
   ./hardware/brillo.nix
   ./hardware/ckb-next.nix
+  ./hardware/corectrl.nix
   ./hardware/cpu/amd-microcode.nix
+  ./hardware/cpu/amd-sev.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/digitalbitbox.nix
+  ./hardware/flipperzero.nix
   ./hardware/flirc.nix
+  ./hardware/gkraken.nix
   ./hardware/gpgsmartcards.nix
-  ./hardware/i2c.nix
   ./hardware/hackrf.nix
-  ./hardware/sensor/hddtemp.nix
-  ./hardware/sensor/iio.nix
+  ./hardware/i2c.nix
+  ./hardware/keyboard/qmk.nix
   ./hardware/keyboard/teck.nix
   ./hardware/keyboard/uhk.nix
   ./hardware/keyboard/zsa.nix
@@ -70,71 +72,75 @@
   ./hardware/network/intel-2200bg.nix
   ./hardware/new-lg4ff.nix
   ./hardware/nitrokey.nix
+  ./hardware/onlykey/default.nix
   ./hardware/opengl.nix
   ./hardware/openrazer.nix
+  ./hardware/opentabletdriver.nix
   ./hardware/pcmcia.nix
   ./hardware/printers.nix
   ./hardware/raid/hpsa.nix
   ./hardware/rtl-sdr.nix
   ./hardware/saleae-logic.nix
+  ./hardware/sata.nix
+  ./hardware/sensor/hddtemp.nix
+  ./hardware/sensor/iio.nix
   ./hardware/steam-hardware.nix
   ./hardware/system-76.nix
   ./hardware/tuxedo-keyboard.nix
   ./hardware/ubertooth.nix
-  ./hardware/usb-wwan.nix
-  ./hardware/onlykey/default.nix
-  ./hardware/opentabletdriver.nix
-  ./hardware/sata.nix
-  ./hardware/wooting.nix
   ./hardware/uinput.nix
+  ./hardware/usb-storage.nix
+  ./hardware/usb-wwan.nix
   ./hardware/video/amdgpu-pro.nix
-  ./hardware/video/capture/mwprocapture.nix
   ./hardware/video/bumblebee.nix
+  ./hardware/video/capture/mwprocapture.nix
   ./hardware/video/displaylink.nix
-  ./hardware/video/hidpi.nix
   ./hardware/video/nvidia.nix
   ./hardware/video/switcheroo-control.nix
   ./hardware/video/uvcvideo/default.nix
   ./hardware/video/webcam/facetimehd.nix
+  ./hardware/video/webcam/ipu6.nix
+  ./hardware/wooting.nix
   ./hardware/xone.nix
   ./hardware/xpadneo.nix
   ./i18n/input-method/default.nix
-  ./i18n/input-method/fcitx.nix
   ./i18n/input-method/fcitx5.nix
   ./i18n/input-method/hime.nix
   ./i18n/input-method/ibus.nix
+  ./i18n/input-method/kime.nix
   ./i18n/input-method/nabi.nix
   ./i18n/input-method/uim.nix
-  ./i18n/input-method/kime.nix
   ./installer/tools/tools.nix
   ./misc/assertions.nix
   ./misc/crashdump.nix
   ./misc/documentation.nix
   ./misc/extra-arguments.nix
   ./misc/ids.nix
-  ./misc/lib.nix
   ./misc/label.nix
+  ./misc/lib.nix
   ./misc/locate.nix
   ./misc/man-db.nix
   ./misc/mandoc.nix
   ./misc/meta.nix
+  ./misc/nixops-autoluks.nix
   ./misc/nixpkgs.nix
   ./misc/passthru.nix
   ./misc/version.nix
   ./misc/wordlist.nix
-  ./misc/nixops-autoluks.nix
-  ./programs/_1password.nix
   ./programs/_1password-gui.nix
+  ./programs/_1password.nix
   ./programs/adb.nix
   ./programs/appgate-sdp.nix
   ./programs/atop.nix
+  ./programs/ausweisapp.nix
   ./programs/autojump.nix
   ./programs/bandwhich.nix
-  ./programs/bash/bash.nix
+  ./programs/bash-my-aws.nix
   ./programs/bash/bash-completion.nix
+  ./programs/bash/bash.nix
+  ./programs/bash/blesh.nix
   ./programs/bash/ls-colors.nix
   ./programs/bash/undistract-me.nix
-  ./programs/bash-my-aws.nix
   ./programs/bcc.nix
   ./programs/browserpass.nix
   ./programs/calls.nix
@@ -143,9 +149,11 @@
   ./programs/cdemu.nix
   ./programs/cfs-zen-tweaks.nix
   ./programs/chromium.nix
+  ./programs/clash-verge.nix
   ./programs/cnping.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/criu.nix
+  ./programs/darling.nix
   ./programs/dconf.nix
   ./programs/digitalbitbox/default.nix
   ./programs/dmrconfig.nix
@@ -155,83 +163,104 @@
   ./programs/extra-container.nix
   ./programs/feedbackd.nix
   ./programs/file-roller.nix
+  ./programs/firefox.nix
   ./programs/firejail.nix
   ./programs/fish.nix
   ./programs/flashrom.nix
   ./programs/flexoptix-app.nix
   ./programs/freetds.nix
   ./programs/fuse.nix
+  ./programs/fzf.nix
   ./programs/gamemode.nix
+  ./programs/gamescope.nix
   ./programs/geary.nix
   ./programs/git.nix
   ./programs/gnome-disks.nix
-  ./programs/gnome-documents.nix
   ./programs/gnome-terminal.nix
-  ./programs/gpaste.nix
   ./programs/gnupg.nix
+  ./programs/gpaste.nix
   ./programs/gphoto2.nix
   ./programs/haguichi.nix
   ./programs/hamster.nix
   ./programs/htop.nix
+  ./programs/hyprland.nix
+  ./programs/iay.nix
   ./programs/iftop.nix
+  ./programs/i3lock.nix
   ./programs/iotop.nix
   ./programs/java.nix
+  ./programs/k3b.nix
   ./programs/k40-whisperer.nix
+  ./programs/kbdlight.nix
   ./programs/kclock.nix
-  ./programs/k3b.nix
   ./programs/kdeconnect.nix
-  ./programs/kbdlight.nix
   ./programs/less.nix
   ./programs/liboping.nix
   ./programs/light.nix
-  ./programs/mosh.nix
+  ./programs/mdevctl.nix
+  ./programs/mepo.nix
   ./programs/mininet.nix
+  ./programs/minipro.nix
+  ./programs/miriway.nix
+  ./programs/mosh.nix
   ./programs/msmtp.nix
   ./programs/mtr.nix
   ./programs/nano.nix
   ./programs/nbd.nix
-  ./programs/nix-ld.nix
   ./programs/neovim.nix
   ./programs/nethoscope.nix
+  ./programs/nexttrace.nix
+  ./programs/nix-index.nix
+  ./programs/nix-ld.nix
   ./programs/nm-applet.nix
   ./programs/nncp.nix
-  ./programs/npm.nix
   ./programs/noisetorch.nix
+  ./programs/npm.nix
   ./programs/oblogout.nix
   ./programs/openvpn3.nix
   ./programs/pantheon-tweaks.nix
   ./programs/partition-manager.nix
   ./programs/plotinus.nix
   ./programs/proxychains.nix
+  ./programs/qdmr.nix
   ./programs/qt5ct.nix
+  ./programs/regreet.nix
+  ./programs/rog-control-center.nix
+  ./programs/rust-motd.nix
   ./programs/screen.nix
-  ./programs/sedutil.nix
   ./programs/seahorse.nix
-  ./programs/slock.nix
+  ./programs/sedutil.nix
   ./programs/shadow.nix
-  ./programs/spacefm.nix
+  ./programs/sharing.nix
   ./programs/singularity.nix
+  ./programs/skim.nix
+  ./programs/slock.nix
+  ./programs/sniffnet.nix
+  ./programs/spacefm.nix
   ./programs/ssh.nix
-  ./programs/sysdig.nix
-  ./programs/systemtap.nix
   ./programs/starship.nix
   ./programs/steam.nix
   ./programs/streamdeck-ui.nix
-  ./programs/sway.nix
+  ./programs/sysdig.nix
   ./programs/system-config-printer.nix
+  ./programs/systemtap.nix
   ./programs/thefuck.nix
   ./programs/thunar.nix
   ./programs/tmux.nix
   ./programs/traceroute.nix
+  ./programs/trippy.nix
   ./programs/tsm-client.nix
   ./programs/turbovnc.nix
   ./programs/udevil.nix
   ./programs/usbtop.nix
   ./programs/vim.nix
   ./programs/wavemon.nix
-  ./programs/waybar.nix
+  ./programs/wayland/river.nix
+  ./programs/wayland/sway.nix
+  ./programs/wayland/waybar.nix
   ./programs/weylus.nix
   ./programs/wireshark.nix
+  ./programs/xastir.nix
   ./programs/wshowkeys.nix
   ./programs/xfconf.nix
   ./programs/xfs_quota.nix
@@ -241,10 +270,10 @@
   ./programs/yabar.nix
   ./programs/zmap.nix
   ./programs/zsh/oh-my-zsh.nix
-  ./programs/zsh/zsh.nix
   ./programs/zsh/zsh-autoenv.nix
   ./programs/zsh/zsh-autosuggestions.nix
   ./programs/zsh/zsh-syntax-highlighting.nix
+  ./programs/zsh/zsh.nix
   ./rename.nix
   ./security/acme
   ./security/apparmor.nix
@@ -253,22 +282,24 @@
   ./security/ca.nix
   ./security/chromium-suid-sandbox.nix
   ./security/dhparams.nix
+  ./security/doas.nix
   ./security/duosec.nix
   ./security/google_oslogin.nix
+  ./security/ipa.nix
   ./security/lock-kernel-modules.nix
   ./security/misc.nix
   ./security/oath.nix
   ./security/pam.nix
-  ./security/pam_usb.nix
   ./security/pam_mount.nix
+  ./security/pam_usb.nix
+  ./security/please.nix
   ./security/polkit.nix
   ./security/rngd.nix
   ./security/rtkit.nix
-  ./security/wrappers/default.nix
   ./security/sudo.nix
-  ./security/doas.nix
   ./security/systemd-confinement.nix
   ./security/tpm2.nix
+  ./security/wrappers/default.nix
   ./services/admin/meshcentral.nix
   ./services/admin/oxidized.nix
   ./services/admin/pgadmin.nix
@@ -278,22 +309,25 @@
   ./services/amqp/rabbitmq.nix
   ./services/audio/alsa.nix
   ./services/audio/botamusique.nix
+  ./services/audio/gmediarender.nix
+  ./services/audio/gonic.nix
   ./services/audio/hqplayerd.nix
   ./services/audio/icecast.nix
   ./services/audio/jack.nix
   ./services/audio/jmusicbot.nix
   ./services/audio/liquidsoap.nix
+  ./services/audio/mopidy.nix
   ./services/audio/mpd.nix
   ./services/audio/mpdscribble.nix
-  ./services/audio/mopidy.nix
+  ./services/audio/navidrome.nix
   ./services/audio/networkaudiod.nix
   ./services/audio/roon-bridge.nix
-  ./services/audio/navidrome.nix
   ./services/audio/roon-server.nix
   ./services/audio/slimserver.nix
   ./services/audio/snapserver.nix
-  ./services/audio/squeezelite.nix
   ./services/audio/spotifyd.nix
+  ./services/audio/squeezelite.nix
+  ./services/audio/tts.nix
   ./services/audio/ympd.nix
   ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
@@ -305,8 +339,8 @@
   ./services/backup/mysql-backup.nix
   ./services/backup/postgresql-backup.nix
   ./services/backup/postgresql-wal-receiver.nix
-  ./services/backup/restic.nix
   ./services/backup/restic-rest-server.nix
+  ./services/backup/restic.nix
   ./services/backup/rsnapshot.nix
   ./services/backup/sanoid.nix
   ./services/backup/syncoid.nix
@@ -314,13 +348,15 @@
   ./services/backup/tsm.nix
   ./services/backup/zfs-replication.nix
   ./services/backup/znapzend.nix
-  ./services/blockchain/ethereum/geth.nix
   ./services/backup/zrepl.nix
+  ./services/blockchain/ethereum/erigon.nix
+  ./services/blockchain/ethereum/geth.nix
+  ./services/blockchain/ethereum/lighthouse.nix
   ./services/cluster/corosync/default.nix
   ./services/cluster/hadoop/default.nix
   ./services/cluster/k3s/default.nix
-  ./services/cluster/kubernetes/addons/dns.nix
   ./services/cluster/kubernetes/addon-manager.nix
+  ./services/cluster/kubernetes/addons/dns.nix
   ./services/cluster/kubernetes/apiserver.nix
   ./services/cluster/kubernetes/controller-manager.nix
   ./services/cluster/kubernetes/default.nix
@@ -340,28 +376,33 @@
   ./services/continuous-integration/buildbot/master.nix
   ./services/continuous-integration/buildbot/worker.nix
   ./services/continuous-integration/buildkite-agents.nix
-  ./services/continuous-integration/hail.nix
-  ./services/continuous-integration/hercules-ci-agent/default.nix
-  ./services/continuous-integration/hydra/default.nix
+  ./services/continuous-integration/gitea-actions-runner.nix
   ./services/continuous-integration/github-runner.nix
+  ./services/continuous-integration/github-runners.nix
   ./services/continuous-integration/gitlab-runner.nix
   ./services/continuous-integration/gocd-agent/default.nix
   ./services/continuous-integration/gocd-server/default.nix
+  ./services/continuous-integration/hail.nix
+  ./services/continuous-integration/hercules-ci-agent/default.nix
+  ./services/continuous-integration/hydra/default.nix
   ./services/continuous-integration/jenkins/default.nix
   ./services/continuous-integration/jenkins/job-builder.nix
   ./services/continuous-integration/jenkins/slave.nix
+  ./services/continuous-integration/woodpecker/agents.nix
+  ./services/continuous-integration/woodpecker/server.nix
   ./services/databases/aerospike.nix
   ./services/databases/cassandra.nix
   ./services/databases/clickhouse.nix
   ./services/databases/cockroachdb.nix
   ./services/databases/couchdb.nix
-  ./services/databases/dragonflydb.nix
   ./services/databases/dgraph.nix
+  ./services/databases/dragonflydb.nix
   ./services/databases/firebird.nix
   ./services/databases/foundationdb.nix
   ./services/databases/hbase-standalone.nix
   ./services/databases/influxdb.nix
   ./services/databases/influxdb2.nix
+  ./services/databases/lldap.nix
   ./services/databases/memcached.nix
   ./services/databases/monetdb.nix
   ./services/databases/mongodb.nix
@@ -372,26 +413,24 @@
   ./services/databases/pgmanage.nix
   ./services/databases/postgresql.nix
   ./services/databases/redis.nix
+  ./services/databases/surrealdb.nix
   ./services/databases/victoriametrics.nix
   ./services/desktops/accountsservice.nix
   ./services/desktops/bamf.nix
   ./services/desktops/blueman.nix
   ./services/desktops/cpupower-gui.nix
+  ./services/desktops/deepin/dde-api.nix
+  ./services/desktops/deepin/app-services.nix
+  ./services/desktops/deepin/dde-daemon.nix
   ./services/desktops/dleyna-renderer.nix
   ./services/desktops/dleyna-server.nix
   ./services/desktops/espanso.nix
   ./services/desktops/flatpak.nix
   ./services/desktops/geoclue2.nix
-  ./services/desktops/gsignond.nix
-  ./services/desktops/gvfs.nix
-  ./services/desktops/malcontent.nix
-  ./services/desktops/pipewire/pipewire.nix
-  ./services/desktops/pipewire/pipewire-media-session.nix
-  ./services/desktops/pipewire/wireplumber.nix
   ./services/desktops/gnome/at-spi2-core.nix
-  ./services/desktops/gnome/chrome-gnome-shell.nix
   ./services/desktops/gnome/evolution-data-server.nix
   ./services/desktops/gnome/glib-networking.nix
+  ./services/desktops/gnome/gnome-browser-connector.nix
   ./services/desktops/gnome/gnome-initial-setup.nix
   ./services/desktops/gnome/gnome-keyring.nix
   ./services/desktops/gnome/gnome-online-accounts.nix
@@ -401,27 +440,34 @@
   ./services/desktops/gnome/gnome-user-share.nix
   ./services/desktops/gnome/rygel.nix
   ./services/desktops/gnome/sushi.nix
-  ./services/desktops/gnome/tracker.nix
   ./services/desktops/gnome/tracker-miners.nix
+  ./services/desktops/gnome/tracker.nix
+  ./services/desktops/gsignond.nix
+  ./services/desktops/gvfs.nix
+  ./services/desktops/malcontent.nix
   ./services/desktops/neard.nix
+  ./services/desktops/pipewire/pipewire.nix
+  ./services/desktops/pipewire/wireplumber.nix
   ./services/desktops/profile-sync-daemon.nix
   ./services/desktops/system-config-printer.nix
+  ./services/desktops/system76-scheduler.nix
   ./services/desktops/telepathy.nix
   ./services/desktops/tumbler.nix
   ./services/desktops/zeitgeist.nix
-  ./services/development/bloop.nix
   ./services/development/blackfire.nix
+  ./services/development/bloop.nix
   ./services/development/distccd.nix
+  ./services/development/gemstash.nix
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
   ./services/development/jupyterhub/default.nix
-  ./services/development/rstudio-server/default.nix
   ./services/development/lorri.nix
+  ./services/development/rstudio-server/default.nix
   ./services/development/zammad.nix
   ./services/display-managers/greetd.nix
   ./services/editors/emacs.nix
-  ./services/editors/infinoted.nix
   ./services/editors/haste.nix
+  ./services/editors/infinoted.nix
   ./services/finance/odoo.nix
   ./services/games/asf.nix
   ./services/games/crossfire-server.nix
@@ -437,6 +483,7 @@
   ./services/hardware/acpid.nix
   ./services/hardware/actkbd.nix
   ./services/hardware/argonone.nix
+  ./services/hardware/asusd.nix
   ./services/hardware/auto-cpufreq.nix
   ./services/hardware/bluetooth.nix
   ./services/hardware/bolt.nix
@@ -454,6 +501,7 @@
   ./services/hardware/lcd.nix
   ./services/hardware/lirc.nix
   ./services/hardware/nvidia-optimus.nix
+  ./services/hardware/openrgb.nix
   ./services/hardware/pcscd.nix
   ./services/hardware/pommed.nix
   ./services/hardware/power-profiles-daemon.nix
@@ -464,20 +512,24 @@
   ./services/hardware/sane_extra_backends/brscan5.nix
   ./services/hardware/sane_extra_backends/dsseries.nix
   ./services/hardware/spacenavd.nix
+  ./services/hardware/supergfxd.nix
   ./services/hardware/tcsd.nix
-  ./services/hardware/tlp.nix
+  ./services/hardware/thermald.nix
   ./services/hardware/thinkfan.nix
   ./services/hardware/throttled.nix
+  ./services/hardware/tlp.nix
   ./services/hardware/trezord.nix
   ./services/hardware/triggerhappy.nix
   ./services/hardware/udev.nix
   ./services/hardware/udisks2.nix
+  ./services/hardware/undervolt.nix
   ./services/hardware/upower.nix
   ./services/hardware/usbmuxd.nix
   ./services/hardware/usbrelayd.nix
-  ./services/hardware/thermald.nix
-  ./services/hardware/undervolt.nix
   ./services/hardware/vdr.nix
+  ./services/hardware/keyd.nix
+  ./services/home-automation/esphome.nix
+  ./services/home-automation/evcc.nix
   ./services/home-automation/home-assistant.nix
   ./services/home-automation/zigbee2mqtt.nix
   ./services/logging/SystemdJournal2Gelf.nix
@@ -498,34 +550,38 @@
   ./services/logging/syslog-ng.nix
   ./services/logging/syslogd.nix
   ./services/logging/vector.nix
+  ./services/logging/ulogd.nix
   ./services/mail/clamsmtp.nix
   ./services/mail/davmail.nix
   ./services/mail/dkimproxy-out.nix
   ./services/mail/dovecot.nix
   ./services/mail/dspam.nix
   ./services/mail/exim.nix
+  ./services/mail/goeland.nix
+  ./services/mail/listmonk.nix
   ./services/mail/maddy.nix
   ./services/mail/mail.nix
   ./services/mail/mailcatcher.nix
   ./services/mail/mailhog.nix
   ./services/mail/mailman.nix
   ./services/mail/mlmmj.nix
+  ./services/mail/nullmailer.nix
   ./services/mail/offlineimap.nix
   ./services/mail/opendkim.nix
   ./services/mail/opensmtpd.nix
   ./services/mail/pfix-srsd.nix
   ./services/mail/postfix.nix
   ./services/mail/postfixadmin.nix
-  ./services/mail/postsrsd.nix
   ./services/mail/postgrey.nix
+  ./services/mail/postsrsd.nix
   ./services/mail/public-inbox.nix
-  ./services/mail/spamassassin.nix
+  ./services/mail/roundcube.nix
   ./services/mail/rspamd.nix
   ./services/mail/rss2email.nix
-  ./services/mail/roundcube.nix
   ./services/mail/schleuder.nix
+  ./services/mail/spamassassin.nix
   ./services/mail/sympa.nix
-  ./services/mail/nullmailer.nix
+  ./services/mail/zeyple.nix
   ./services/matrix/appservice-discord.nix
   ./services/matrix/appservice-irc.nix
   ./services/matrix/conduit.nix
@@ -533,39 +589,41 @@
   ./services/matrix/mautrix-facebook.nix
   ./services/matrix/mautrix-telegram.nix
   ./services/matrix/mjolnir.nix
+  ./services/matrix/mx-puppet-discord.nix
   ./services/matrix/pantalaimon.nix
   ./services/matrix/synapse.nix
-  ./services/misc/ananicy.nix
   ./services/misc/airsonic.nix
+  ./services/misc/ananicy.nix
   ./services/misc/ankisyncd.nix
   ./services/misc/apache-kafka.nix
+  ./services/misc/atuin.nix
   ./services/misc/autofs.nix
   ./services/misc/autorandr.nix
+  ./services/misc/autosuspend.nix
   ./services/misc/bazarr.nix
   ./services/misc/beanstalkd.nix
   ./services/misc/bees.nix
   ./services/misc/bepasty.nix
-  ./services/misc/canto-daemon.nix
   ./services/misc/calibre-server.nix
+  ./services/misc/canto-daemon.nix
   ./services/misc/cfdyndns.nix
-  ./services/misc/clipmenu.nix
-  ./services/misc/clipcat.nix
-  ./services/misc/cpuminer-cryptonight.nix
   ./services/misc/cgminer.nix
+  ./services/misc/clipcat.nix
+  ./services/misc/clipmenu.nix
   ./services/misc/confd.nix
+  ./services/misc/cpuminer-cryptonight.nix
   ./services/misc/devmon.nix
   ./services/misc/dictd.nix
-  ./services/misc/duckling.nix
-  ./services/misc/dwm-status.nix
-  ./services/misc/dysnomia.nix
   ./services/misc/disnix.nix
   ./services/misc/docker-registry.nix
   ./services/misc/domoticz.nix
+  ./services/misc/duckling.nix
+  ./services/misc/dwm-status.nix
+  ./services/misc/dysnomia.nix
   ./services/misc/errbot.nix
   ./services/misc/etcd.nix
   ./services/misc/etebase-server.nix
   ./services/misc/etesync-dav.nix
-  ./services/misc/ethminer.nix
   ./services/misc/exhibitor.nix
   ./services/misc/felix.nix
   ./services/misc/freeswitch.nix
@@ -573,33 +631,33 @@
   ./services/misc/gammu-smsd.nix
   ./services/misc/geoipupdate.nix
   ./services/misc/gitea.nix
-  #./services/misc/gitit.nix
   ./services/misc/gitlab.nix
   ./services/misc/gitolite.nix
   ./services/misc/gitweb.nix
   ./services/misc/gogs.nix
   ./services/misc/gollum.nix
   ./services/misc/gpsd.nix
+  ./services/misc/greenclip.nix
   ./services/misc/headphones.nix
   ./services/misc/heisenbridge.nix
-  ./services/misc/greenclip.nix
   ./services/misc/ihaskell.nix
   ./services/misc/input-remapper.nix
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
   ./services/misc/jellyfin.nix
+  ./services/misc/jellyseerr.nix
   ./services/misc/klipper.nix
-  ./services/misc/logkeys.nix
+  ./services/misc/languagetool.nix
   ./services/misc/leaps.nix
-  ./services/misc/lidarr.nix
   ./services/misc/libreddit.nix
+  ./services/misc/lidarr.nix
   ./services/misc/lifecycled.nix
+  ./services/misc/logkeys.nix
   ./services/misc/mame.nix
   ./services/misc/mbpfan.nix
   ./services/misc/mediatomb.nix
   ./services/misc/metabase.nix
   ./services/misc/moonraker.nix
-  ./services/misc/mx-puppet-discord.nix
   ./services/misc/n8n.nix
   ./services/misc/nitter.nix
   ./services/misc/nix-daemon.nix
@@ -607,6 +665,7 @@
   ./services/misc/nix-optimise.nix
   ./services/misc/nix-ssh-serve.nix
   ./services/misc/novacomd.nix
+  ./services/misc/ntfy-sh.nix
   ./services/misc/nzbget.nix
   ./services/misc/nzbhydra2.nix
   ./services/misc/octoprint.nix
@@ -617,23 +676,25 @@
   ./services/misc/paperless.nix
   ./services/misc/parsoid.nix
   ./services/misc/persistent-evdev.nix
+  ./services/misc/pinnwand.nix
   ./services/misc/plex.nix
   ./services/misc/plikd.nix
   ./services/misc/podgrab.nix
   ./services/misc/polaris.nix
   ./services/misc/portunus.nix
   ./services/misc/prowlarr.nix
-  ./services/misc/tautulli.nix
-  ./services/misc/pinnwand.nix
+  ./services/misc/pufferpanel.nix
   ./services/misc/pykms.nix
   ./services/misc/radarr.nix
+  ./services/misc/readarr.nix
   ./services/misc/redmine.nix
-  ./services/misc/rippled.nix
   ./services/misc/ripple-data-api.nix
+  ./services/misc/rippled.nix
   ./services/misc/rmfakecloud.nix
-  ./services/misc/serviio.nix
+  ./services/misc/rshim.nix
   ./services/misc/safeeyes.nix
   ./services/misc/sdrplay.nix
+  ./services/misc/serviio.nix
   ./services/misc/sickbeard.nix
   ./services/misc/signald.nix
   ./services/misc/siproxd.nix
@@ -649,7 +710,9 @@
   ./services/misc/svnserve.nix
   ./services/misc/synergy.nix
   ./services/misc/sysprof.nix
+  ./services/misc/tandoor-recipes.nix
   ./services/misc/taskserver
+  ./services/misc/tautulli.nix
   ./services/misc/tiddlywiki.nix
   ./services/misc/tp-auto-kbbl.nix
   ./services/misc/tzupdate.nix
@@ -664,21 +727,23 @@
   ./services/monitoring/arbtt.nix
   ./services/monitoring/bosun.nix
   ./services/monitoring/cadvisor.nix
+  ./services/monitoring/cockpit.nix
   ./services/monitoring/collectd.nix
   ./services/monitoring/das_watchdog.nix
   ./services/monitoring/datadog-agent.nix
-  ./services/monitoring/dd-agent/dd-agent.nix
   ./services/monitoring/do-agent.nix
   ./services/monitoring/fusion-inventory.nix
-  ./services/monitoring/grafana.nix
   ./services/monitoring/grafana-agent.nix
   ./services/monitoring/grafana-image-renderer.nix
   ./services/monitoring/grafana-reporter.nix
+  ./services/monitoring/grafana.nix
   ./services/monitoring/graphite.nix
   ./services/monitoring/hdaps.nix
   ./services/monitoring/heapster.nix
   ./services/monitoring/incron.nix
   ./services/monitoring/kapacitor.nix
+  ./services/monitoring/karma.nix
+  ./services/monitoring/kthxbye.nix
   ./services/monitoring/loki.nix
   ./services/monitoring/longview.nix
   ./services/monitoring/mackerel-agent.nix
@@ -689,76 +754,89 @@
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
   ./services/monitoring/parsedmarc.nix
-  ./services/monitoring/prometheus/default.nix
+  ./services/monitoring/prometheus/alertmanager-irc-relay.nix
   ./services/monitoring/prometheus/alertmanager.nix
+  ./services/monitoring/prometheus/default.nix
   ./services/monitoring/prometheus/exporters.nix
   ./services/monitoring/prometheus/pushgateway.nix
+  ./services/monitoring/prometheus/sachet.nix
   ./services/monitoring/prometheus/xmpp-alerts.nix
-  ./services/monitoring/riemann.nix
   ./services/monitoring/riemann-dash.nix
   ./services/monitoring/riemann-tools.nix
+  ./services/monitoring/riemann.nix
   ./services/monitoring/scollector.nix
   ./services/monitoring/smartd.nix
+  ./services/monitoring/statsd.nix
   ./services/monitoring/sysstat.nix
   ./services/monitoring/teamviewer.nix
   ./services/monitoring/telegraf.nix
   ./services/monitoring/thanos.nix
+  ./services/monitoring/tremor-rs.nix
   ./services/monitoring/tuptime.nix
-  ./services/monitoring/unifi-poller.nix
+  ./services/monitoring/unpoller.nix
   ./services/monitoring/ups.nix
+  ./services/monitoring/uptime-kuma.nix
   ./services/monitoring/uptime.nix
+  ./services/monitoring/vmagent.nix
+  ./services/monitoring/vmalert.nix
   ./services/monitoring/vnstat.nix
   ./services/monitoring/zabbix-agent.nix
   ./services/monitoring/zabbix-proxy.nix
   ./services/monitoring/zabbix-server.nix
   ./services/network-filesystems/cachefilesd.nix
+  ./services/network-filesystems/ceph.nix
   ./services/network-filesystems/davfs2.nix
+  ./services/network-filesystems/diod.nix
   ./services/network-filesystems/drbd.nix
   ./services/network-filesystems/glusterfs.nix
   ./services/network-filesystems/kbfs.nix
-  ./services/network-filesystems/ipfs.nix
+  ./services/network-filesystems/kubo.nix
   ./services/network-filesystems/litestream/default.nix
+  ./services/network-filesystems/moosefs.nix
   ./services/network-filesystems/netatalk.nix
   ./services/network-filesystems/nfsd.nix
-  ./services/network-filesystems/moosefs.nix
   ./services/network-filesystems/openafs/client.nix
   ./services/network-filesystems/openafs/server.nix
-  ./services/network-filesystems/orangefs/server.nix
   ./services/network-filesystems/orangefs/client.nix
+  ./services/network-filesystems/orangefs/server.nix
   ./services/network-filesystems/rsyncd.nix
-  ./services/network-filesystems/samba.nix
   ./services/network-filesystems/samba-wsdd.nix
+  ./services/network-filesystems/samba.nix
   ./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/webdav.nix
   ./services/network-filesystems/xtreemfs.nix
-  ./services/network-filesystems/ceph.nix
+  ./services/network-filesystems/yandex-disk.nix
   ./services/networking/3proxy.nix
+  ./services/networking/acme-dns.nix
   ./services/networking/adguardhome.nix
+  ./services/networking/alice-lg.nix
   ./services/networking/amuled.nix
   ./services/networking/antennas.nix
   ./services/networking/aria2.nix
   ./services/networking/asterisk.nix
   ./services/networking/atftpd.nix
+  ./services/networking/autossh.nix
   ./services/networking/avahi-daemon.nix
   ./services/networking/babeld.nix
-  ./services/networking/bee.nix
   ./services/networking/bee-clef.nix
+  ./services/networking/bee.nix
   ./services/networking/biboumi.nix
   ./services/networking/bind.nix
-  ./services/networking/bitcoind.nix
-  ./services/networking/autossh.nix
-  ./services/networking/bird.nix
   ./services/networking/bird-lg.nix
+  ./services/networking/bird.nix
+  ./services/networking/birdwatcher.nix
+  ./services/networking/bitcoind.nix
   ./services/networking/bitlbee.nix
   ./services/networking/blockbook-frontend.nix
   ./services/networking/blocky.nix
+  ./services/networking/cgit.nix
   ./services/networking/charybdis.nix
+  ./services/networking/chisel-server.nix
   ./services/networking/cjdns.nix
   ./services/networking/cloudflare-dyndns.nix
+  ./services/networking/cloudflared.nix
   ./services/networking/cntlm.nix
   ./services/networking/connman.nix
   ./services/networking/consul.nix
@@ -777,8 +855,6 @@
   ./services/networking/dnsdist.nix
   ./services/networking/dnsmasq.nix
   ./services/networking/doh-proxy-rust.nix
-  ./services/networking/ncdns.nix
-  ./services/networking/nomad.nix
   ./services/networking/ejabberd.nix
   ./services/networking/envoy.nix
   ./services/networking/epmd.nix
@@ -791,6 +867,8 @@
   ./services/networking/firefox-syncserver.nix
   ./services/networking/fireqos.nix
   ./services/networking/firewall.nix
+  ./services/networking/firewall-iptables.nix
+  ./services/networking/firewall-nftables.nix
   ./services/networking/flannel.nix
   ./services/networking/freenet.nix
   ./services/networking/freeradius.nix
@@ -801,21 +879,24 @@
   ./services/networking/git-daemon.nix
   ./services/networking/globalprotect-vpn.nix
   ./services/networking/gnunet.nix
+  ./services/networking/go-autoconfig.nix
   ./services/networking/go-neb.nix
   ./services/networking/go-shadowsocks2.nix
   ./services/networking/gobgpd.nix
   ./services/networking/gvpe.nix
   ./services/networking/hans.nix
+  ./services/networking/harmonia.nix
   ./services/networking/haproxy.nix
   ./services/networking/headscale.nix
   ./services/networking/hostapd.nix
   ./services/networking/htpdate.nix
   ./services/networking/https-dns-proxy.nix
   ./services/networking/hylafax/default.nix
-  ./services/networking/i2pd.nix
   ./services/networking/i2p.nix
-  ./services/networking/icecream/scheduler.nix
+  ./services/networking/i2pd.nix
   ./services/networking/icecream/daemon.nix
+  ./services/networking/icecream/scheduler.nix
+  ./services/networking/imaginary.nix
   ./services/networking/inspircd.nix
   ./services/networking/iodine.nix
   ./services/networking/iperf3.nix
@@ -823,6 +904,7 @@
   ./services/networking/iscsi/initiator.nix
   ./services/networking/iscsi/root-initiator.nix
   ./services/networking/iscsi/target.nix
+  ./services/networking/ivpn.nix
   ./services/networking/iwd.nix
   ./services/networking/jibri/default.nix
   ./services/networking/jicofo.nix
@@ -833,6 +915,7 @@
   ./services/networking/knot.nix
   ./services/networking/kresd.nix
   ./services/networking/lambdabot.nix
+  ./services/networking/legit.nix
   ./services/networking/libreswan.nix
   ./services/networking/lldpd.nix
   ./services/networking/logmein-hamachi.nix
@@ -840,14 +923,15 @@
   ./services/networking/lxd-image-server.nix
   ./services/networking/magic-wormhole-mailbox-server.nix
   ./services/networking/matterbridge.nix
-  ./services/networking/mjpg-streamer.nix
   ./services/networking/minidlna.nix
   ./services/networking/miniupnpd.nix
-  ./services/networking/mosquitto.nix
+  ./services/networking/miredo.nix
+  ./services/networking/mjpg-streamer.nix
+  ./services/networking/mmsd.nix
   ./services/networking/monero.nix
   ./services/networking/morty.nix
+  ./services/networking/mosquitto.nix
   ./services/networking/mozillavpn.nix
-  ./services/networking/miredo.nix
   ./services/networking/mstpd.nix
   ./services/networking/mtprotoproxy.nix
   ./services/networking/mtr-exporter.nix
@@ -858,19 +942,25 @@
   ./services/networking/namecoind.nix
   ./services/networking/nar-serve.nix
   ./services/networking/nat.nix
+  ./services/networking/nat-iptables.nix
+  ./services/networking/nat-nftables.nix
   ./services/networking/nats.nix
   ./services/networking/nbd.nix
+  ./services/networking/ncdns.nix
   ./services/networking/ndppd.nix
   ./services/networking/nebula.nix
+  ./services/networking/netbird.nix
+  ./services/networking/networkd-dispatcher.nix
   ./services/networking/networkmanager.nix
   ./services/networking/nextdns.nix
   ./services/networking/nftables.nix
-  ./services/networking/ngircd.nix
   ./services/networking/nghttpx/default.nix
+  ./services/networking/ngircd.nix
   ./services/networking/nix-serve.nix
   ./services/networking/nix-store-gcs-proxy.nix
   ./services/networking/nixops-dns.nix
   ./services/networking/nntp-proxy.nix
+  ./services/networking/nomad.nix
   ./services/networking/nsd.nix
   ./services/networking/ntopng.nix
   ./services/networking/ntp/chrony.nix
@@ -886,20 +976,22 @@
   ./services/networking/openvpn.nix
   ./services/networking/ostinato.nix
   ./services/networking/owamp.nix
+  ./services/networking/pdns-recursor.nix
   ./services/networking/pdnsd.nix
+  ./services/networking/peroxide.nix
+  ./services/networking/picosnitch.nix
   ./services/networking/pixiecore.nix
   ./services/networking/pleroma.nix
   ./services/networking/polipo.nix
   ./services/networking/powerdns.nix
-  ./services/networking/pdns-recursor.nix
   ./services/networking/pppd.nix
   ./services/networking/pptpd.nix
   ./services/networking/prayer.nix
   ./services/networking/privoxy.nix
   ./services/networking/prosody.nix
   ./services/networking/quassel.nix
-  ./services/networking/quorum.nix
   ./services/networking/quicktun.nix
+  ./services/networking/quorum.nix
   ./services/networking/r53-ddns.nix
   ./services/networking/radicale.nix
   ./services/networking/radvd.nix
@@ -913,68 +1005,76 @@
   ./services/networking/sabnzbd.nix
   ./services/networking/seafile.nix
   ./services/networking/searx.nix
-  ./services/networking/skydns.nix
   ./services/networking/shadowsocks.nix
   ./services/networking/shairport-sync.nix
   ./services/networking/shellhub-agent.nix
   ./services/networking/shorewall.nix
   ./services/networking/shorewall6.nix
   ./services/networking/shout.nix
-  ./services/networking/sniproxy.nix
-  ./services/networking/snowflake-proxy.nix
+  ./services/networking/sitespeed-io.nix
+  ./services/networking/skydns.nix
   ./services/networking/smartdns.nix
   ./services/networking/smokeping.nix
+  ./services/networking/sniproxy.nix
+  ./services/networking/snowflake-proxy.nix
   ./services/networking/softether.nix
-  ./services/networking/solanum.nix
   ./services/networking/soju.nix
+  ./services/networking/solanum.nix
   ./services/networking/spacecookie.nix
   ./services/networking/spiped.nix
   ./services/networking/squid.nix
-  ./services/networking/sslh.nix
   ./services/networking/ssh/lshd.nix
   ./services/networking/ssh/sshd.nix
-  ./services/networking/strongswan.nix
+  ./services/networking/sslh.nix
   ./services/networking/strongswan-swanctl/module.nix
-  ./services/networking/stunnel.nix
+  ./services/networking/strongswan.nix
   ./services/networking/stubby.nix
+  ./services/networking/stunnel.nix
   ./services/networking/supplicant.nix
   ./services/networking/supybot.nix
-  ./services/networking/syncthing.nix
-  ./services/networking/syncthing-relay.nix
   ./services/networking/syncplay.nix
+  ./services/networking/syncthing-relay.nix
+  ./services/networking/syncthing.nix
   ./services/networking/tailscale.nix
+  ./services/networking/tayga.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
   ./services/networking/tedicross.nix
-  ./services/networking/tetrd.nix
   ./services/networking/teleport.nix
+  ./services/networking/tetrd.nix
+  ./services/networking/tftpd.nix
   ./services/networking/thelounge.nix
   ./services/networking/tinc.nix
   ./services/networking/tinydns.nix
-  ./services/networking/tftpd.nix
-  ./services/networking/trickster.nix
+  ./services/networking/tmate-ssh-server.nix
   ./services/networking/tox-bootstrapd.nix
   ./services/networking/tox-node.nix
   ./services/networking/toxvpn.nix
+  ./services/networking/trickster.nix
   ./services/networking/tvheadend.nix
+  ./services/networking/twingate.nix
   ./services/networking/ucarp.nix
   ./services/networking/unbound.nix
   ./services/networking/unifi.nix
-  ./services/video/unifi-video.nix
-  ./services/video/rtsp-simple-server.nix
   ./services/networking/uptermd.nix
   ./services/networking/v2ray.nix
+  ./services/networking/v2raya.nix
+  ./services/networking/vdirsyncer.nix
   ./services/networking/vsftpd.nix
   ./services/networking/wasabibackend.nix
   ./services/networking/websockify.nix
   ./services/networking/wg-netmanager.nix
+  ./services/networking/webhook.nix
   ./services/networking/wg-quick.nix
+  ./services/networking/wgautomesh.nix
   ./services/networking/wireguard.nix
   ./services/networking/wpa_supplicant.nix
+  ./services/networking/wstunnel.nix
+  ./services/networking/x2goserver.nix
   ./services/networking/xandikos.nix
   ./services/networking/xinetd.nix
   ./services/networking/xl2tpd.nix
-  ./services/networking/x2goserver.nix
+  ./services/networking/xray.nix
   ./services/networking/xrdp.nix
   ./services/networking/yggdrasil.nix
   ./services/networking/zerobin.nix
@@ -982,36 +1082,42 @@
   ./services/networking/zerotierone.nix
   ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
+  ./services/printing/ipp-usb.nix
+  ./services/printing/cups-pdf.nix
   ./services/scheduling/atd.nix
   ./services/scheduling/cron.nix
   ./services/scheduling/fcron.nix
-  ./services/search/elasticsearch.nix
   ./services/search/elasticsearch-curator.nix
+  ./services/search/elasticsearch.nix
   ./services/search/hound.nix
   ./services/search/kibana.nix
   ./services/search/meilisearch.nix
-  ./services/search/solr.nix
+  ./services/search/opensearch.nix
+  ./services/search/qdrant.nix
   ./services/security/aesmd.nix
+  ./services/security/authelia.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
   ./services/security/clamav.nix
+  ./services/security/endlessh-go.nix
+  ./services/security/endlessh.nix
   ./services/security/fail2ban.nix
   ./services/security/fprintd.nix
   ./services/security/haka.nix
   ./services/security/haveged.nix
   ./services/security/hockeypuck.nix
-  ./services/security/hologram-server.nix
   ./services/security/hologram-agent.nix
-  ./services/security/kanidm.nix
+  ./services/security/hologram-server.nix
   ./services/security/infnoise.nix
+  ./services/security/kanidm.nix
   ./services/security/munge.nix
   ./services/security/nginx-sso.nix
   ./services/security/oauth2_proxy.nix
   ./services/security/oauth2_proxy_nginx.nix
   ./services/security/opensnitch.nix
   ./services/security/pass-secret-service.nix
-  ./services/security/privacyidea.nix
   ./services/security/physlock.nix
+  ./services/security/privacyidea.nix
   ./services/security/shibboleth-sp.nix
   ./services/security/sks.nix
   ./services/security/sshguard.nix
@@ -1022,14 +1128,17 @@
   ./services/security/torsocks.nix
   ./services/security/usbguard.nix
   ./services/security/vault.nix
+  ./services/security/vault-agent.nix
   ./services/security/vaultwarden/default.nix
   ./services/security/yubikey-agent.nix
+  ./services/system/automatic-timezoned.nix
   ./services/system/cachix-agent/default.nix
+  ./services/system/cachix-watch-store.nix
   ./services/system/cloud-init.nix
   ./services/system/dbus.nix
   ./services/system/earlyoom.nix
-  ./services/system/localtimed.nix
   ./services/system/kerberos/default.nix
+  ./services/system/localtimed.nix
   ./services/system/nscd.nix
   ./services/system/saslauthd.nix
   ./services/system/self-deploy.nix
@@ -1046,77 +1155,100 @@
   ./services/ttys/getty.nix
   ./services/ttys/gpm.nix
   ./services/ttys/kmscon.nix
-  ./services/wayland/cage.nix
   ./services/video/epgstation/default.nix
+  ./services/video/go2rtc/default.nix
+  ./services/video/frigate.nix
   ./services/video/mirakurun.nix
   ./services/video/replay-sorcery.nix
+  ./services/video/mediamtx.nix
+  ./services/video/unifi-video.nix
+  ./services/video/v4l2-relayd.nix
+  ./services/wayland/cage.nix
+  ./services/web-apps/akkoma.nix
+  ./services/web-apps/alps.nix
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
   ./services/web-apps/bookstack.nix
   ./services/web-apps/calibre-web.nix
+  ./services/web-apps/coder.nix
+  ./services/web-apps/changedetection-io.nix
+  ./services/web-apps/chatgpt-retrieval-plugin.nix
+  ./services/web-apps/cloudlog.nix
   ./services/web-apps/code-server.nix
-  ./services/web-apps/baget.nix
   ./services/web-apps/convos.nix
   ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/dokuwiki.nix
+  ./services/web-apps/dolibarr.nix
   ./services/web-apps/engelsystem.nix
   ./services/web-apps/ethercalc.nix
   ./services/web-apps/fluidd.nix
+  ./services/web-apps/freshrss.nix
   ./services/web-apps/galene.nix
   ./services/web-apps/gerrit.nix
   ./services/web-apps/gotify-server.nix
   ./services/web-apps/grocy.nix
+  ./services/web-apps/pixelfed.nix
   ./services/web-apps/healthchecks.nix
   ./services/web-apps/hedgedoc.nix
   ./services/web-apps/hledger-web.nix
   ./services/web-apps/icingaweb2/icingaweb2.nix
   ./services/web-apps/icingaweb2/module-monitoring.nix
-  ./services/web-apps/ihatemoney
+  ./services/web-apps/invidious.nix
+  ./services/web-apps/invoiceplane.nix
   ./services/web-apps/isso.nix
   ./services/web-apps/jirafeau.nix
   ./services/web-apps/jitsi-meet.nix
+  ./services/web-apps/kasmweb/default.nix
+  ./services/web-apps/kavita.nix
   ./services/web-apps/keycloak.nix
   ./services/web-apps/komga.nix
   ./services/web-apps/lemmy.nix
-  ./services/web-apps/invidious.nix
-  ./services/web-apps/invoiceplane.nix
   ./services/web-apps/limesurvey.nix
+  ./services/web-apps/mainsail.nix
   ./services/web-apps/mastodon.nix
+  ./services/web-apps/matomo.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
+  ./services/web-apps/monica.nix
   ./services/web-apps/moodle.nix
   ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
+  ./services/web-apps/nextcloud-notify_push.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
-  ./services/web-apps/phylactery.nix
   ./services/web-apps/onlyoffice.nix
-  ./services/web-apps/pict-rs.nix
+  ./services/web-apps/openvscode-server.nix
+  ./services/web-apps/openwebrx.nix
+  ./services/web-apps/outline.nix
+  ./services/web-apps/peering-manager.nix
   ./services/web-apps/peertube.nix
+  ./services/web-apps/pgpkeyserver-lite.nix
+  ./services/web-apps/phylactery.nix
+  ./services/web-apps/photoprism.nix
+  ./services/web-apps/pict-rs.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/prosody-filer.nix
-  ./services/web-apps/matomo.nix
-  ./services/web-apps/openwebrx.nix
   ./services/web-apps/restya-board.nix
-  ./services/web-apps/sogo.nix
+  ./services/web-apps/sftpgo.nix
   ./services/web-apps/rss-bridge.nix
-  ./services/web-apps/tt-rss.nix
-  ./services/web-apps/trilium.nix
   ./services/web-apps/selfoss.nix
   ./services/web-apps/shiori.nix
   ./services/web-apps/snipe-it.nix
+  ./services/web-apps/sogo.nix
+  ./services/web-apps/trilium.nix
+  ./services/web-apps/tt-rss.nix
   ./services/web-apps/vikunja.nix
-  ./services/web-apps/wiki-js.nix
   ./services/web-apps/whitebophir.nix
+  ./services/web-apps/wiki-js.nix
   ./services/web-apps/wordpress.nix
+  ./services/web-apps/writefreely.nix
   ./services/web-apps/youtrack.nix
   ./services/web-apps/zabbix.nix
   ./services/web-servers/agate.nix
@@ -1124,13 +1256,16 @@
   ./services/web-servers/caddy/default.nix
   ./services/web-servers/darkhttpd.nix
   ./services/web-servers/fcgiwrap.nix
+  ./services/web-servers/garage.nix
   ./services/web-servers/hitch/default.nix
   ./services/web-servers/hydron.nix
   ./services/web-servers/jboss/default.nix
+  ./services/web-servers/keter
   ./services/web-servers/lighttpd/cgit.nix
   ./services/web-servers/lighttpd/collectd.nix
   ./services/web-servers/lighttpd/default.nix
   ./services/web-servers/lighttpd/gitweb.nix
+  ./services/web-servers/merecat.nix
   ./services/web-servers/mighttpd2.nix
   ./services/web-servers/minio.nix
   ./services/web-servers/molly-brown.nix
@@ -1138,21 +1273,17 @@
   ./services/web-servers/nginx/gitweb.nix
   ./services/web-servers/phpfpm/default.nix
   ./services/web-servers/pomerium.nix
-  ./services/web-servers/unit/default.nix
+  ./services/web-servers/stargazer.nix
   ./services/web-servers/tomcat.nix
-  ./services/web-servers/keter
   ./services/web-servers/traefik.nix
   ./services/web-servers/trafficserver/default.nix
   ./services/web-servers/ttyd.nix
+  ./services/web-servers/unit/default.nix
   ./services/web-servers/uwsgi.nix
   ./services/web-servers/varnish/default.nix
   ./services/web-servers/zope2.nix
-  ./services/x11/extra-layouts.nix
   ./services/x11/clight.nix
   ./services/x11/colord.nix
-  ./services/x11/picom.nix
-  ./services/x11/unclutter.nix
-  ./services/x11/unclutter-xfixes.nix
   ./services/x11/desktop-managers/default.nix
   ./services/x11/display-managers/default.nix
   ./services/x11/display-managers/gdm.nix
@@ -1162,25 +1293,31 @@
   ./services/x11/display-managers/startx.nix
   ./services/x11/display-managers/sx.nix
   ./services/x11/display-managers/xpra.nix
+  ./services/x11/extra-layouts.nix
   ./services/x11/fractalart.nix
+  ./services/x11/gdk-pixbuf.nix
+  ./services/x11/hardware/cmt.nix
+  ./services/x11/hardware/digimend.nix
   ./services/x11/hardware/libinput.nix
   ./services/x11/hardware/synaptics.nix
   ./services/x11/hardware/wacom.nix
-  ./services/x11/hardware/digimend.nix
-  ./services/x11/hardware/cmt.nix
-  ./services/x11/gdk-pixbuf.nix
   ./services/x11/imwheel.nix
+  ./services/x11/picom.nix
   ./services/x11/redshift.nix
   ./services/x11/touchegg.nix
+  ./services/x11/unclutter-xfixes.nix
+  ./services/x11/unclutter.nix
   ./services/x11/urserver.nix
   ./services/x11/urxvtd.nix
   ./services/x11/window-managers/awesome.nix
-  ./services/x11/window-managers/default.nix
+  ./services/x11/window-managers/bspwm.nix
   ./services/x11/window-managers/clfswm.nix
+  ./services/x11/window-managers/default.nix
   ./services/x11/window-managers/fluxbox.nix
   ./services/x11/window-managers/icewm.nix
-  ./services/x11/window-managers/bspwm.nix
+  ./services/x11/window-managers/katriawm.nix
   ./services/x11/window-managers/metacity.nix
+  ./services/x11/window-managers/nimdow.nix
   ./services/x11/window-managers/none.nix
   ./services/x11/window-managers/twm.nix
   ./services/x11/window-managers/windowlab.nix
@@ -1191,13 +1328,15 @@
   ./services/x11/xfs.nix
   ./services/x11/xserver.nix
   ./system/activation/activation-script.nix
+  ./system/activation/specialisation.nix
+  ./system/activation/bootspec.nix
   ./system/activation/top-level.nix
   ./system/boot/binfmt.nix
   ./system/boot/emergency-mode.nix
   ./system/boot/grow-partition.nix
   ./system/boot/initrd-network.nix
-  ./system/boot/initrd-ssh.nix
   ./system/boot/initrd-openvpn.nix
+  ./system/boot/initrd-ssh.nix
   ./system/boot/kernel.nix
   ./system/boot/kexec.nix
   ./system/boot/loader/efi.nix
@@ -1206,11 +1345,13 @@
   ./system/boot/loader/grub/grub.nix
   ./system/boot/loader/grub/ipxe.nix
   ./system/boot/loader/grub/memtest.nix
+  ./system/boot/loader/external/external.nix
   ./system/boot/loader/init-script/init-script.nix
   ./system/boot/loader/loader.nix
   ./system/boot/loader/raspberrypi/raspberrypi.nix
   ./system/boot/loader/systemd-boot/systemd-boot.nix
   ./system/boot/luksroot.nix
+  ./system/boot/stratisroot.nix
   ./system/boot/modprobe.nix
   ./system/boot/networkd.nix
   ./system/boot/plymouth.nix
@@ -1225,11 +1366,16 @@
   ./system/boot/systemd/journald.nix
   ./system/boot/systemd/logind.nix
   ./system/boot/systemd/nspawn.nix
+  ./system/boot/systemd/oomd.nix
+  ./system/boot/systemd/repart.nix
   ./system/boot/systemd/shutdown.nix
   ./system/boot/systemd/tmpfiles.nix
   ./system/boot/systemd/user.nix
+  ./system/boot/systemd/userdbd.nix
+  ./system/boot/systemd/homed.nix
   ./system/boot/timesyncd.nix
   ./system/boot/tmp.nix
+  ./system/boot/uvesafb.nix
   ./system/etc/etc-activation.nix
   ./tasks/auto-upgrade.nix
   ./tasks/bcache.nix
@@ -1241,6 +1387,8 @@
   ./tasks/filesystems/btrfs.nix
   ./tasks/filesystems/cifs.nix
   ./tasks/filesystems/ecryptfs.nix
+  ./tasks/filesystems/envfs.nix
+  ./tasks/filesystems/erofs.nix
   ./tasks/filesystems/exfat.nix
   ./tasks/filesystems/ext.nix
   ./tasks/filesystems/f2fs.nix
@@ -1254,46 +1402,49 @@
   ./tasks/filesystems/xfs.nix
   ./tasks/filesystems/zfs.nix
   ./tasks/lvm.nix
-  ./tasks/network-interfaces.nix
-  ./tasks/network-interfaces-systemd.nix
   ./tasks/network-interfaces-scripted.nix
+  ./tasks/network-interfaces-systemd.nix
+  ./tasks/network-interfaces.nix
+  ./tasks/powertop.nix
   ./tasks/scsi-link-power-management.nix
   ./tasks/snapraid.nix
+  ./tasks/stratis.nix
   ./tasks/swraid.nix
   ./tasks/trackpoint.nix
-  ./tasks/powertop.nix
   ./testing/service-runner.nix
+  ./virtualisation/amazon-options.nix
   ./virtualisation/anbox.nix
   ./virtualisation/appvm.nix
   ./virtualisation/build-vm.nix
   ./virtualisation/container-config.nix
   ./virtualisation/containerd.nix
   ./virtualisation/containers.nix
-  ./virtualisation/nixos-containers.nix
-  ./virtualisation/oci-containers.nix
   ./virtualisation/cri-o.nix
-  ./virtualisation/docker.nix
   ./virtualisation/docker-rootless.nix
+  ./virtualisation/docker.nix
   ./virtualisation/ecs-agent.nix
+  ./virtualisation/hyperv-guest.nix
+  ./virtualisation/kvmgt.nix
   ./virtualisation/libvirtd.nix
   ./virtualisation/lxc.nix
   ./virtualisation/lxcfs.nix
   ./virtualisation/lxd.nix
-  ./virtualisation/amazon-options.nix
-  ./virtualisation/hyperv-guest.nix
-  ./virtualisation/kvmgt.nix
+  ./virtualisation/multipass.nix
+  ./virtualisation/nixos-containers.nix
+  ./virtualisation/oci-containers.nix
   ./virtualisation/openstack-options.nix
   ./virtualisation/openvswitch.nix
   ./virtualisation/parallels-guest.nix
   ./virtualisation/podman/default.nix
   ./virtualisation/qemu-guest-agent.nix
+  ./virtualisation/rosetta.nix
   ./virtualisation/spice-usb-redirection.nix
   ./virtualisation/virtualbox-guest.nix
   ./virtualisation/virtualbox-host.nix
   ./virtualisation/vmware-guest.nix
   ./virtualisation/vmware-host.nix
   ./virtualisation/waydroid.nix
-  ./virtualisation/xen-dom0.nix
   ./virtualisation/xe-guest-utilities.nix
+  ./virtualisation/xen-dom0.nix
   { documentation.nixos.extraModules = [ ./virtualisation/qemu-vm.nix ]; }
 ]
diff --git a/nixpkgs/nixos/modules/profiles/all-hardware.nix b/nixpkgs/nixos/modules/profiles/all-hardware.nix
index 5fa64a6c52e9..4857ea4dbeae 100644
--- a/nixpkgs/nixos/modules/profiles/all-hardware.nix
+++ b/nixpkgs/nixos/modules/profiles/all-hardware.nix
@@ -31,7 +31,7 @@ in
       "pata_winbond"
 
       # SCSI support (incomplete).
-      "3w-9xxx" "3w-xxxx" "aic79xx" "aic7xxx" "arcmsr"
+      "3w-9xxx" "3w-xxxx" "aic79xx" "aic7xxx" "arcmsr" "hpsa"
 
       # USB support, especially for booting from USB CD-ROM
       # drives.
diff --git a/nixpkgs/nixos/modules/profiles/base.nix b/nixpkgs/nixos/modules/profiles/base.nix
index 33dd80d7c5ab..9f32f85a61ec 100644
--- a/nixpkgs/nixos/modules/profiles/base.nix
+++ b/nixpkgs/nixos/modules/profiles/base.nix
@@ -1,7 +1,7 @@
 # This module defines the software packages included in the "minimal"
-# installation CD.  It might be useful elsewhere.
+# installation CD. It might be useful elsewhere.
 
-{ lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 {
   # Include some utilities that are useful for installing or repairing
@@ -17,18 +17,23 @@
     pkgs.ddrescue
     pkgs.ccrypt
     pkgs.cryptsetup # needed for dm-crypt volumes
-    pkgs.mkpasswd # for generating password files
 
     # Some text editors.
-    pkgs.vim
+    (pkgs.vim.customize {
+      name = "vim";
+      vimrcConfig.packages.default = {
+        start = [ pkgs.vimPlugins.vim-nix ];
+      };
+      vimrcConfig.customRC = "syntax on";
+    })
 
     # Some networking tools.
     pkgs.fuse
     pkgs.fuse3
     pkgs.sshfs-fuse
-    pkgs.rsync
     pkgs.socat
     pkgs.screen
+    pkgs.tcpdump
 
     # Hardware-related tools.
     pkgs.sdparm
@@ -36,22 +41,17 @@
     pkgs.smartmontools # for diagnosing hard disks
     pkgs.pciutils
     pkgs.usbutils
-
-    # Tools to create / manipulate filesystems.
-    pkgs.ntfsprogs # for resizing NTFS partitions
-    pkgs.dosfstools
-    pkgs.mtools
-    pkgs.xfsprogs.bin
-    pkgs.jfsutils
-    pkgs.f2fs-tools
+    pkgs.nvme-cli
 
     # Some compression/archiver tools.
     pkgs.unzip
     pkgs.zip
   ];
 
-  # Include support for various filesystems.
-  boot.supportedFilesystems = [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "zfs" "ntfs" "cifs" ];
+  # Include support for various filesystems and tools to create / manipulate them.
+  boot.supportedFilesystems =
+    [ "btrfs" "cifs" "f2fs" "jfs" "ntfs" "reiserfs" "vfat" "xfs" ] ++
+    lib.optional (lib.meta.availableOn pkgs.stdenv.hostPlatform config.boot.zfs.package) "zfs";
 
   # Configure host id for ZFS to work
   networking.hostId = lib.mkDefault "8425e349";
diff --git a/nixpkgs/nixos/modules/profiles/docker-container.nix b/nixpkgs/nixos/modules/profiles/docker-container.nix
index 183645de36fb..5365e49711dc 100644
--- a/nixpkgs/nixos/modules/profiles/docker-container.nix
+++ b/nixpkgs/nixos/modules/profiles/docker-container.nix
@@ -1,13 +1,12 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
-let inherit (pkgs) writeScript; in
-
 let
- pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
+  inherit (pkgs) writeScript;
+
+  pkgs2storeContents = map (x: { object = x; symlink = "none"; });
+in
 
-in {
+{
   # Docker image config.
   imports = [
     ../installer/cd-dvd/channel.nix
diff --git a/nixpkgs/nixos/modules/profiles/installation-device.nix b/nixpkgs/nixos/modules/profiles/installation-device.nix
index a8601a9e2c06..32884f4b8754 100644
--- a/nixpkgs/nixos/modules/profiles/installation-device.nix
+++ b/nixpkgs/nixos/modules/profiles/installation-device.nix
@@ -20,12 +20,13 @@ with lib;
     ];
 
   config = {
+    system.nixos.variant_id = lib.mkDefault "installer";
 
     # Enable in installer, even if the minimal profile disables it.
-    documentation.enable = mkForce true;
+    documentation.enable = mkImageMediaOverride true;
 
     # Show the manual.
-    documentation.nixos.enable = mkForce true;
+    documentation.nixos.enable = mkImageMediaOverride true;
 
     # Use less privileged nixos user
     users.users.nixos = {
@@ -41,7 +42,7 @@ with lib;
     # Allow passwordless sudo from nixos user
     security.sudo = {
       enable = mkDefault true;
-      wheelNeedsPassword = mkForce false;
+      wheelNeedsPassword = mkImageMediaOverride false;
     };
 
     # Automatically log in at the virtual consoles.
@@ -51,9 +52,9 @@ with lib;
     services.getty.helpLine = ''
       The "nixos" and "root" accounts have empty passwords.
 
-      An ssh daemon is running. You then must set a password
-      for either "root" or "nixos" with `passwd` or add an ssh key
-      to /home/nixos/.ssh/authorized_keys be able to login.
+      To log in over ssh you must set a password for either "nixos" or "root"
+      with `passwd` (prefix with `sudo` for "root"), or add your public key to
+      /home/nixos/.ssh/authorized_keys or /root/.ssh/authorized_keys.
 
       If you need a wireless connection, type
       `sudo systemctl start wpa_supplicant` and configure a
@@ -64,14 +65,14 @@ with lib;
       start the graphical user interface.
     '';
 
-    # We run sshd by default. Login via root is only possible after adding a
-    # password via "passwd" or by adding a ssh key to /home/nixos/.ssh/authorized_keys.
+    # We run sshd by default. Login is only possible after adding a
+    # password via "passwd" or by adding a ssh key to ~/.ssh/authorized_keys.
     # The latter one is particular useful if keys are manually added to
     # installation device for head-less systems i.e. arm boards by manually
     # mounting the storage in a different system.
     services.openssh = {
       enable = true;
-      permitRootLogin = "yes";
+      settings.PermitRootLogin = "yes";
     };
 
     # Enable wpa_supplicant, but don't start it by default.
diff --git a/nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key b/nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key
new file mode 100644
index 000000000000..b18489795369
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCQVnMW/wZWqrdWrjrRPhfEFFq1KLYguagSflLhFnVQmwAAAJASuMMnErjD
+JwAAAAtzc2gtZWQyNTUxOQAAACCQVnMW/wZWqrdWrjrRPhfEFFq1KLYguagSflLhFnVQmw
+AAAEDIN2VWFyggtoSPXcAFy8dtG1uAig8sCuyE21eMDt2GgJBWcxb/Blaqt1auOtE+F8QU
+WrUotiC5qBJ+UuEWdVCbAAAACnJvb3RAbml4b3MBAgM=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub b/nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
new file mode 100644
index 000000000000..2c45826715fc
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBWcxb/Blaqt1auOtE+F8QUWrUotiC5qBJ+UuEWdVCb root@nixos
diff --git a/nixpkgs/nixos/modules/profiles/macos-builder.nix b/nixpkgs/nixos/modules/profiles/macos-builder.nix
new file mode 100644
index 000000000000..768c673e7f37
--- /dev/null
+++ b/nixpkgs/nixos/modules/profiles/macos-builder.nix
@@ -0,0 +1,239 @@
+{ config, lib, pkgs, ... }:
+
+let
+  keysDirectory = "/var/keys";
+
+  user = "builder";
+
+  keyType = "ed25519";
+
+  cfg = config.virtualisation.darwin-builder;
+
+in
+
+{
+  imports = [
+    ../virtualisation/qemu-vm.nix
+
+    # Avoid a dependency on stateVersion
+    {
+      disabledModules = [
+        ../virtualisation/nixos-containers.nix
+        ../services/x11/desktop-managers/xterm.nix
+      ];
+      config = { };
+      options.boot.isContainer = lib.mkOption { default = false; internal = true; };
+    }
+  ];
+
+  options.virtualisation.darwin-builder = with lib; {
+    diskSize = mkOption {
+      default = 20 * 1024;
+      type = types.int;
+      example = 30720;
+      description = "The maximum disk space allocated to the runner in MB";
+    };
+    memorySize = mkOption {
+      default = 3 * 1024;
+      type = types.int;
+      example = 8192;
+      description = "The runner's memory in MB";
+    };
+    min-free = mkOption {
+      default = 1024 * 1024 * 1024;
+      type = types.int;
+      example = 1073741824;
+      description = ''
+        The threshold (in bytes) of free disk space left at which to
+        start garbage collection on the runner
+      '';
+    };
+    max-free = mkOption {
+      default = 3 * 1024 * 1024 * 1024;
+      type = types.int;
+      example = 3221225472;
+      description = ''
+        The threshold (in bytes) of free disk space left at which to
+        stop garbage collection on the runner
+      '';
+    };
+    workingDirectory = mkOption {
+       default = ".";
+       type = types.str;
+       example = "/var/lib/darwin-builder";
+       description = ''
+         The working directory to use to run the script. When running
+         as part of a flake will need to be set to a non read-only filesystem.
+       '';
+    };
+    hostPort = mkOption {
+      default = 22;
+      type = types.int;
+      example = 31022;
+      description = ''
+        The localhost host port to forward TCP to the guest port.
+      '';
+    };
+  };
+
+  config = {
+    # The builder is not intended to be used interactively
+    documentation.enable = false;
+
+    environment.etc = {
+      "ssh/ssh_host_ed25519_key" = {
+        mode = "0600";
+
+        source = ./keys/ssh_host_ed25519_key;
+      };
+
+      "ssh/ssh_host_ed25519_key.pub" = {
+        mode = "0644";
+
+        source = ./keys/ssh_host_ed25519_key.pub;
+      };
+    };
+
+    # DNS fails for QEMU user networking (SLiRP) on macOS.  See:
+    #
+    # https://github.com/utmapp/UTM/issues/2353
+    #
+    # This works around that by using a public DNS server other than the DNS
+    # server that QEMU provides (normally 10.0.2.3)
+    networking.nameservers = [ "8.8.8.8" ];
+
+    nix.settings = {
+      auto-optimise-store = true;
+
+      min-free = cfg.min-free;
+
+      max-free = cfg.max-free;
+
+      trusted-users = [ "root" user ];
+    };
+
+    services = {
+      getty.autologinUser = user;
+
+      openssh = {
+        enable = true;
+
+        authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ];
+      };
+    };
+
+    system.build.macos-builder-installer =
+      let
+        privateKey = "/etc/nix/${user}_${keyType}";
+
+        publicKey = "${privateKey}.pub";
+
+        # This installCredentials script is written so that it's as easy as
+        # possible for a user to audit before confirming the `sudo`
+        installCredentials = hostPkgs.writeShellScript "install-credentials" ''
+          KEYS="''${1}"
+          INSTALL=${hostPkgs.coreutils}/bin/install
+          "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey}
+          "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey}
+        '';
+
+        hostPkgs = config.virtualisation.host.pkgs;
+
+  script = hostPkgs.writeShellScriptBin "create-builder" (
+          # When running as non-interactively as part of a DarwinConfiguration the working directory
+          # must be set to a writeable directory.
+        (if cfg.workingDirectory != "." then ''
+          ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}"
+          cd "${cfg.workingDirectory}"
+  '' else "") + ''
+          KEYS="''${KEYS:-./keys}"
+          ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}"
+          PRIVATE_KEY="''${KEYS}/${user}_${keyType}"
+          PUBLIC_KEY="''${PRIVATE_KEY}.pub"
+          if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then
+              ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}"
+              ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost'
+          fi
+          if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then
+            (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}")
+          fi
+          KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${config.system.build.vm}/bin/run-nixos-vm
+        '');
+
+      in
+      script.overrideAttrs (old: {
+        meta = (old.meta or { }) // {
+          platforms = lib.platforms.darwin;
+        };
+      });
+
+    system = {
+      # To prevent gratuitous rebuilds on each change to Nixpkgs
+      nixos.revision = null;
+
+      stateVersion = lib.mkDefault (throw ''
+        The macOS linux builder should not need a stateVersion to be set, but a module
+        has accessed stateVersion nonetheless.
+        Please inspect the trace of the following command to figure out which module
+        has a dependency on stateVersion.
+
+          nix-instantiate --attr darwin.builder --show-trace
+      '');
+    };
+
+    users.users."${user}" = {
+      isNormalUser = true;
+    };
+
+    security.polkit.enable = true;
+
+    security.polkit.extraConfig = ''
+      polkit.addRule(function(action, subject) {
+        if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") {
+          return "yes";
+        } else {
+          return "no";
+        }
+      })
+    '';
+
+    virtualisation = {
+      diskSize = cfg.diskSize;
+
+      memorySize = cfg.memorySize;
+
+      forwardPorts = [
+        { from = "host"; guest.port = 22; host.port = cfg.hostPort; }
+      ];
+
+      # Disable graphics for the builder since users will likely want to run it
+      # non-interactively in the background.
+      graphics = false;
+
+      sharedDirectories.keys = {
+        source = "\"$KEYS\"";
+        target = keysDirectory;
+      };
+
+      # If we don't enable this option then the host will fail to delegate builds
+      # to the guest, because:
+      #
+      # - The host will lock the path to build
+      # - The host will delegate the build to the guest
+      # - The guest will attempt to lock the same path and fail because
+      #   the lockfile on the host is visible on the guest
+      #
+      # Snapshotting the host's /nix/store as an image isolates the guest VM's
+      # /nix/store from the host's /nix/store, preventing this problem.
+      useNixStoreImage = true;
+
+      # Obviously the /nix/store needs to be writable on the guest in order for it
+      # to perform builds.
+      writableStore = true;
+
+      # This ensures that anything built on the guest isn't lost when the guest is
+      # restarted.
+      writableStoreUseTmpfs = false;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/profiles/minimal.nix b/nixpkgs/nixos/modules/profiles/minimal.nix
index 0e65989214a1..bd1b2b452189 100644
--- a/nixpkgs/nixos/modules/profiles/minimal.nix
+++ b/nixpkgs/nixos/modules/profiles/minimal.nix
@@ -10,7 +10,22 @@ with lib;
 
   documentation.enable = mkDefault false;
 
+  documentation.doc.enable = mkDefault false;
+
+  documentation.info.enable = mkDefault false;
+
+  documentation.man.enable = mkDefault false;
+
   documentation.nixos.enable = mkDefault false;
 
   programs.command-not-found.enable = mkDefault false;
+
+  services.logrotate.enable = mkDefault false;
+
+  services.udisks2.enable = mkDefault false;
+
+  xdg.autostart.enable = mkDefault false;
+  xdg.icons.enable = mkDefault false;
+  xdg.mime.enable = mkDefault false;
+  xdg.sounds.enable = mkDefault false;
 }
diff --git a/nixpkgs/nixos/modules/programs/_1password-gui.nix b/nixpkgs/nixos/modules/programs/_1password-gui.nix
index 20bd846d5162..27c0d34a2eed 100644
--- a/nixpkgs/nixos/modules/programs/_1password-gui.nix
+++ b/nixpkgs/nixos/modules/programs/_1password-gui.nix
@@ -16,7 +16,7 @@ in
 
   options = {
     programs._1password-gui = {
-      enable = mkEnableOption "the 1Password GUI application";
+      enable = mkEnableOption (lib.mdDoc "the 1Password GUI application");
 
       polkitPolicyOwners = mkOption {
         type = types.listOf types.str;
@@ -27,7 +27,7 @@ in
         '';
       };
 
-      package = mkPackageOption pkgs "1Password GUI" {
+      package = mkPackageOptionMD pkgs "1Password GUI" {
         default = [ "_1password-gui" ];
       };
     };
diff --git a/nixpkgs/nixos/modules/programs/_1password.nix b/nixpkgs/nixos/modules/programs/_1password.nix
index b87e9b776e85..8537484c7e67 100644
--- a/nixpkgs/nixos/modules/programs/_1password.nix
+++ b/nixpkgs/nixos/modules/programs/_1password.nix
@@ -16,9 +16,9 @@ in
 
   options = {
     programs._1password = {
-      enable = mkEnableOption "the 1Password CLI tool";
+      enable = mkEnableOption (lib.mdDoc "the 1Password CLI tool");
 
-      package = mkPackageOption pkgs "1Password CLI" {
+      package = mkPackageOptionMD pkgs "1Password CLI" {
         default = [ "_1password" ];
       };
     };
diff --git a/nixpkgs/nixos/modules/programs/appgate-sdp.nix b/nixpkgs/nixos/modules/programs/appgate-sdp.nix
index 12cb542f4d04..bdd538dc2f1f 100644
--- a/nixpkgs/nixos/modules/programs/appgate-sdp.nix
+++ b/nixpkgs/nixos/modules/programs/appgate-sdp.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     programs.appgate-sdp = {
-      enable = mkEnableOption "AppGate SDP VPN client";
+      enable = mkEnableOption (lib.mdDoc "AppGate SDP VPN client");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/atop.nix b/nixpkgs/nixos/modules/programs/atop.nix
index a0763d2dcf6b..9d5843bd670e 100644
--- a/nixpkgs/nixos/modules/programs/atop.nix
+++ b/nixpkgs/nixos/modules/programs/atop.nix
@@ -14,7 +14,7 @@ in
 
     programs.atop = rec {
 
-      enable = mkEnableOption "Atop";
+      enable = mkEnableOption (lib.mdDoc "Atop");
 
       package = mkOption {
         type = types.package;
@@ -142,6 +142,7 @@ in
               # convert remainings logs and start eventually
               atop.serviceConfig.ExecStartPre = pkgs.writeShellScript "atop-update-log-format" ''
                 set -e -u
+                shopt -s nullglob
                 for logfile in "$LOGPATH"/atop_*
                 do
                   ${atop}/bin/atopconvert "$logfile" "$logfile".new
@@ -150,6 +151,8 @@ in
                   if ! ${pkgs.diffutils}/bin/cmp -s "$logfile" "$logfile".new
                   then
                     ${pkgs.coreutils}/bin/mv -v -f "$logfile".new "$logfile"
+                  else
+                    ${pkgs.coreutils}/bin/rm -f "$logfile".new
                   fi
                 done
               '';
diff --git a/nixpkgs/nixos/modules/programs/ausweisapp.nix b/nixpkgs/nixos/modules/programs/ausweisapp.nix
new file mode 100644
index 000000000000..ef1f059568c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/ausweisapp.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg  = config.programs.ausweisapp;
+in
+{
+  options.programs.ausweisapp = {
+    enable = mkEnableOption (lib.mdDoc "AusweisApp2");
+
+    openFirewall = mkOption {
+      description = lib.mdDoc ''
+        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp2.
+      '';
+      default = false;
+      type = lib.types.bool;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ AusweisApp2 ];
+    networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ 24727 ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/bash-my-aws.nix b/nixpkgs/nixos/modules/programs/bash-my-aws.nix
index 15e429a75497..10f16cae651b 100644
--- a/nixpkgs/nixos/modules/programs/bash-my-aws.nix
+++ b/nixpkgs/nixos/modules/programs/bash-my-aws.nix
@@ -13,7 +13,7 @@ in
   {
     options = {
       programs.bash-my-aws = {
-        enable = mkEnableOption "bash-my-aws";
+        enable = mkEnableOption (lib.mdDoc "bash-my-aws");
       };
     };
 
diff --git a/nixpkgs/nixos/modules/programs/bash/bash-completion.nix b/nixpkgs/nixos/modules/programs/bash/bash-completion.nix
index b8e5b1bfa336..96fbe0126d66 100644
--- a/nixpkgs/nixos/modules/programs/bash/bash-completion.nix
+++ b/nixpkgs/nixos/modules/programs/bash/bash-completion.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options = {
-    programs.bash.enableCompletion = mkEnableOption "Bash completion for all interactive bash shells" // {
+    programs.bash.enableCompletion = mkEnableOption (lib.mdDoc "Bash completion for all interactive bash shells") // {
       default = true;
     };
   };
diff --git a/nixpkgs/nixos/modules/programs/bash/bash.nix b/nixpkgs/nixos/modules/programs/bash/bash.nix
index 249e99ddc472..286faeadc489 100644
--- a/nixpkgs/nixos/modules/programs/bash/bash.nix
+++ b/nixpkgs/nixos/modules/programs/bash/bash.nix
@@ -12,7 +12,7 @@ let
   cfg = config.programs.bash;
 
   bashAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+    mapAttrsFlatten (k: v: "alias -- ${k}=${escapeShellArg v}")
       (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
diff --git a/nixpkgs/nixos/modules/programs/bash/blesh.nix b/nixpkgs/nixos/modules/programs/bash/blesh.nix
new file mode 100644
index 000000000000..8fa51bef7744
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/bash/blesh.nix
@@ -0,0 +1,16 @@
+{ lib, config, pkgs, ... }:
+with lib;
+let
+  cfg = config.programs.bash.blesh;
+in {
+  options = {
+    programs.bash.blesh.enable = mkEnableOption (mdDoc "blesh");
+  };
+
+  config = mkIf cfg.enable {
+    programs.bash.interactiveShellInit = mkBefore ''
+      source ${pkgs.blesh}/share/blesh/ble.sh
+    '';
+  };
+  meta.maintainers = with maintainers; [ laalsaas ];
+}
diff --git a/nixpkgs/nixos/modules/programs/bash/ls-colors.nix b/nixpkgs/nixos/modules/programs/bash/ls-colors.nix
index 254ee14c477d..6a5253a3cca2 100644
--- a/nixpkgs/nixos/modules/programs/bash/ls-colors.nix
+++ b/nixpkgs/nixos/modules/programs/bash/ls-colors.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options = {
-    programs.bash.enableLsColors = mkEnableOption "extra colors in directory listings" // {
+    programs.bash.enableLsColors = mkEnableOption (lib.mdDoc "extra colors in directory listings") // {
       default = true;
     };
   };
diff --git a/nixpkgs/nixos/modules/programs/bash/undistract-me.nix b/nixpkgs/nixos/modules/programs/bash/undistract-me.nix
index 8d1b1740f644..587b649377df 100644
--- a/nixpkgs/nixos/modules/programs/bash/undistract-me.nix
+++ b/nixpkgs/nixos/modules/programs/bash/undistract-me.nix
@@ -8,9 +8,9 @@ in
 {
   options = {
     programs.bash.undistractMe = {
-      enable = mkEnableOption "notifications when long-running terminal commands complete";
+      enable = mkEnableOption (lib.mdDoc "notifications when long-running terminal commands complete");
 
-      playSound = mkEnableOption "notification sounds when long-running terminal commands complete";
+      playSound = mkEnableOption (lib.mdDoc "notification sounds when long-running terminal commands complete");
 
       timeout = mkOption {
         default = 10;
diff --git a/nixpkgs/nixos/modules/programs/bcc.nix b/nixpkgs/nixos/modules/programs/bcc.nix
index e475c6ceaa6c..ff29d56bedb9 100644
--- a/nixpkgs/nixos/modules/programs/bcc.nix
+++ b/nixpkgs/nixos/modules/programs/bcc.nix
@@ -1,6 +1,6 @@
 { config, pkgs, lib, ... }:
 {
-  options.programs.bcc.enable = lib.mkEnableOption "bcc";
+  options.programs.bcc.enable = lib.mkEnableOption (lib.mdDoc "bcc");
 
   config = lib.mkIf config.programs.bcc.enable {
     environment.systemPackages = [ pkgs.bcc ];
diff --git a/nixpkgs/nixos/modules/programs/browserpass.nix b/nixpkgs/nixos/modules/programs/browserpass.nix
index e1456d3c1848..346d38e5e880 100644
--- a/nixpkgs/nixos/modules/programs/browserpass.nix
+++ b/nixpkgs/nixos/modules/programs/browserpass.nix
@@ -4,7 +4,7 @@ with lib;
 
 {
 
-  options.programs.browserpass.enable = mkEnableOption "Browserpass native messaging host";
+  options.programs.browserpass.enable = mkEnableOption (lib.mdDoc "Browserpass native messaging host");
 
   config = mkIf config.programs.browserpass.enable {
     environment.etc = let
diff --git a/nixpkgs/nixos/modules/programs/calls.nix b/nixpkgs/nixos/modules/programs/calls.nix
index 08a223b408d4..7a18982915a9 100644
--- a/nixpkgs/nixos/modules/programs/calls.nix
+++ b/nixpkgs/nixos/modules/programs/calls.nix
@@ -7,9 +7,9 @@ let
 in {
   options = {
     programs.calls = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         Whether to enable GNOME calls: a phone dialer and call handler.
-      '';
+      '');
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/captive-browser.nix b/nixpkgs/nixos/modules/programs/captive-browser.nix
index 7ebce17bebfd..36ceb1a69610 100644
--- a/nixpkgs/nixos/modules/programs/captive-browser.nix
+++ b/nixpkgs/nixos/modules/programs/captive-browser.nix
@@ -34,7 +34,7 @@ in
 
   options = {
     programs.captive-browser = {
-      enable = mkEnableOption "captive browser";
+      enable = mkEnableOption (lib.mdDoc "captive browser");
 
       package = mkOption {
         type = types.package;
@@ -85,9 +85,9 @@ in
       bindInterface = mkOption {
         default = true;
         type = types.bool;
-        description = ''
-          Binds <package>captive-browser</package> to the network interface declared in
-          <literal>cfg.interface</literal>. This can be used to avoid collisions
+        description = lib.mdDoc ''
+          Binds `captive-browser` to the network interface declared in
+          `cfg.interface`. This can be used to avoid collisions
           with private subnets.
         '';
       };
diff --git a/nixpkgs/nixos/modules/programs/ccache.nix b/nixpkgs/nixos/modules/programs/ccache.nix
index a554109533b2..567c853e8c7d 100644
--- a/nixpkgs/nixos/modules/programs/ccache.nix
+++ b/nixpkgs/nixos/modules/programs/ccache.nix
@@ -6,7 +6,7 @@ let
 in {
   options.programs.ccache = {
     # host configuration
-    enable = mkEnableOption "CCache";
+    enable = mkEnableOption (lib.mdDoc "CCache");
     cacheDir = mkOption {
       type = types.path;
       description = lib.mdDoc "CCache directory";
@@ -17,7 +17,7 @@ in {
       type = types.listOf types.str;
       description = lib.mdDoc "Nix top-level packages to be compiled using CCache";
       default = [];
-      example = [ "wxGTK30" "ffmpeg" "libav_all" ];
+      example = [ "wxGTK32" "ffmpeg" "libav_all" ];
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/cfs-zen-tweaks.nix b/nixpkgs/nixos/modules/programs/cfs-zen-tweaks.nix
index f168662bbefe..97c2570475c4 100644
--- a/nixpkgs/nixos/modules/programs/cfs-zen-tweaks.nix
+++ b/nixpkgs/nixos/modules/programs/cfs-zen-tweaks.nix
@@ -17,7 +17,7 @@ in
   };
 
   options = {
-    programs.cfs-zen-tweaks.enable = mkEnableOption "CFS Zen Tweaks";
+    programs.cfs-zen-tweaks.enable = mkEnableOption (lib.mdDoc "CFS Zen Tweaks");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/programs/chromium.nix b/nixpkgs/nixos/modules/programs/chromium.nix
index 98eb071e6144..4024f337dfcd 100644
--- a/nixpkgs/nixos/modules/programs/chromium.nix
+++ b/nixpkgs/nixos/modules/programs/chromium.nix
@@ -19,7 +19,7 @@ in
 
   options = {
     programs.chromium = {
-      enable = mkEnableOption "<command>chromium</command> policies";
+      enable = mkEnableOption (lib.mdDoc "{command}`chromium` policies");
 
       extensions = mkOption {
         type = types.listOf types.str;
@@ -76,10 +76,10 @@ in
 
       extraOpts = mkOption {
         type = types.attrs;
-        description = ''
+        description = lib.mdDoc ''
           Extra chromium policy options. A list of available policies
           can be found in the Chrome Enterprise documentation:
-          <link xlink:href="https://cloud.google.com/docs/chrome-enterprise/policies/">https://cloud.google.com/docs/chrome-enterprise/policies/</link>
+          <https://cloud.google.com/docs/chrome-enterprise/policies/>
           Make sure the selected policy is supported on Linux and your browser version.
         '';
         default = {};
diff --git a/nixpkgs/nixos/modules/programs/clash-verge.nix b/nixpkgs/nixos/modules/programs/clash-verge.nix
new file mode 100644
index 000000000000..29977be3858f
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/clash-verge.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+{
+  options.programs.clash-verge = {
+    enable = lib.mkEnableOption (lib.mdDoc ''
+      Clash Verge.
+    '');
+
+    autoStart = lib.mkEnableOption (lib.mdDoc ''
+      Clash Verge Auto Launch.
+    '');
+
+    tunMode = lib.mkEnableOption (lib.mdDoc ''
+      Clash Verge Tun Mode.
+    '');
+  };
+
+  config =
+    let
+      cfg = config.programs.clash-verge;
+    in
+    lib.mkIf cfg.enable {
+
+      environment.systemPackages = [
+        pkgs.clash-verge
+        (lib.mkIf cfg.autoStart (pkgs.makeAutostartItem {
+          name = "clash-verge";
+          package = pkgs.clash-verge;
+        }))
+      ];
+
+      security.wrappers.clash-verge = lib.mkIf cfg.tunMode {
+        owner = "root";
+        group = "root";
+        capabilities = "cap_net_bind_service,cap_net_admin=+ep";
+        source = "${lib.getExe pkgs.clash-verge}";
+      };
+    };
+
+  meta.maintainers = with lib.maintainers; [ zendo ];
+}
diff --git a/nixpkgs/nixos/modules/programs/cnping.nix b/nixpkgs/nixos/modules/programs/cnping.nix
index d208d2b07040..d3cf659d4297 100644
--- a/nixpkgs/nixos/modules/programs/cnping.nix
+++ b/nixpkgs/nixos/modules/programs/cnping.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     programs.cnping = {
-      enable = mkEnableOption "Whether to install a setcap wrapper for cnping";
+      enable = mkEnableOption (lib.mdDoc "Whether to install a setcap wrapper for cnping");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/darling.nix b/nixpkgs/nixos/modules/programs/darling.nix
new file mode 100644
index 000000000000..c4e1c73b5c29
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/darling.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.darling;
+in {
+  options = {
+    programs.darling = {
+      enable = lib.mkEnableOption (lib.mdDoc "Darling, a Darwin/macOS compatibility layer for Linux");
+      package = lib.mkPackageOptionMD pkgs "darling" {};
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers.darling = {
+      source = lib.getExe cfg.package;
+      owner = "root";
+      group = "root";
+      setuid = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/dconf.nix b/nixpkgs/nixos/modules/programs/dconf.nix
index b5ef42a3b72b..7261a143528f 100644
--- a/nixpkgs/nixos/modules/programs/dconf.nix
+++ b/nixpkgs/nixos/modules/programs/dconf.nix
@@ -28,7 +28,7 @@ in
 
   options = {
     programs.dconf = {
-      enable = mkEnableOption "dconf";
+      enable = mkEnableOption (lib.mdDoc "dconf");
 
       profiles = mkOption {
         type = types.attrsOf types.path;
diff --git a/nixpkgs/nixos/modules/programs/digitalbitbox/default.md b/nixpkgs/nixos/modules/programs/digitalbitbox/default.md
new file mode 100644
index 000000000000..9bca14e97ffe
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/digitalbitbox/default.md
@@ -0,0 +1,47 @@
+# Digital Bitbox {#module-programs-digitalbitbox}
+
+Digital Bitbox is a hardware wallet and second-factor authenticator.
+
+The `digitalbitbox` programs module may be installed by setting
+`programs.digitalbitbox` to `true` in a manner similar to
+```
+programs.digitalbitbox.enable = true;
+```
+and bundles the `digitalbitbox` package (see [](#sec-digitalbitbox-package)),
+which contains the `dbb-app` and `dbb-cli` binaries, along with the hardware
+module (see [](#sec-digitalbitbox-hardware-module)) which sets up the necessary
+udev rules to access the device.
+
+Enabling the digitalbitbox module is pretty much the easiest way to get a
+Digital Bitbox device working on your system.
+
+For more information, see <https://digitalbitbox.com/start_linux>.
+
+## Package {#sec-digitalbitbox-package}
+
+The binaries, `dbb-app` (a GUI tool) and `dbb-cli` (a CLI tool), are available
+through the `digitalbitbox` package which could be installed as follows:
+```
+environment.systemPackages = [
+  pkgs.digitalbitbox
+];
+```
+
+## Hardware {#sec-digitalbitbox-hardware-module}
+
+The digitalbitbox hardware package enables the udev rules for Digital Bitbox
+devices and may be installed as follows:
+```
+hardware.digitalbitbox.enable = true;
+```
+
+In order to alter the udev rules, one may provide different values for the
+`udevRule51` and `udevRule52` attributes by means of overriding as follows:
+```
+programs.digitalbitbox = {
+  enable = true;
+  package = pkgs.digitalbitbox.override {
+    udevRule51 = "something else";
+  };
+};
+```
diff --git a/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix b/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix
index 101ee8ddbafc..5ee6cdafe63a 100644
--- a/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix
+++ b/nixpkgs/nixos/modules/programs/digitalbitbox/default.nix
@@ -33,7 +33,7 @@ in
   };
 
   meta = {
-    doc = ./doc.xml;
+    doc = ./default.md;
     maintainers = with lib.maintainers; [ vidbina ];
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml b/nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml
deleted file mode 100644
index c63201628dbd..000000000000
--- a/nixpkgs/nixos/modules/programs/digitalbitbox/doc.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-programs-digitalbitbox">
- <title>Digital Bitbox</title>
- <para>
-  Digital Bitbox is a hardware wallet and second-factor authenticator.
- </para>
- <para>
-  The <literal>digitalbitbox</literal> programs module may be installed by
-  setting <literal>programs.digitalbitbox</literal> to <literal>true</literal>
-  in a manner similar to
-<programlisting>
-<xref linkend="opt-programs.digitalbitbox.enable"/> = true;
-</programlisting>
-  and bundles the <literal>digitalbitbox</literal> package (see
-  <xref
-      linkend="sec-digitalbitbox-package" />), which contains the
-  <literal>dbb-app</literal> and <literal>dbb-cli</literal> binaries, along
-  with the hardware module (see
-  <xref
-      linkend="sec-digitalbitbox-hardware-module" />) which sets up the
-  necessary udev rules to access the device.
- </para>
- <para>
-  Enabling the digitalbitbox module is pretty much the easiest way to get a
-  Digital Bitbox device working on your system.
- </para>
- <para>
-  For more information, see
-  <link xlink:href="https://digitalbitbox.com/start_linux" />.
- </para>
- <section xml:id="sec-digitalbitbox-package">
-  <title>Package</title>
-
-  <para>
-   The binaries, <literal>dbb-app</literal> (a GUI tool) and
-   <literal>dbb-cli</literal> (a CLI tool), are available through the
-   <literal>digitalbitbox</literal> package which could be installed as
-   follows:
-<programlisting>
-<xref linkend="opt-environment.systemPackages"/> = [
-  pkgs.digitalbitbox
-];
-</programlisting>
-  </para>
- </section>
- <section xml:id="sec-digitalbitbox-hardware-module">
-  <title>Hardware</title>
-
-  <para>
-   The digitalbitbox hardware package enables the udev rules for Digital Bitbox
-   devices and may be installed as follows:
-<programlisting>
-<xref linkend="opt-hardware.digitalbitbox.enable"/> = true;
-</programlisting>
-  </para>
-
-  <para>
-   In order to alter the udev rules, one may provide different values for the
-   <literal>udevRule51</literal> and <literal>udevRule52</literal> attributes
-   by means of overriding as follows:
-<programlisting>
-programs.digitalbitbox = {
-  <link linkend="opt-programs.digitalbitbox.enable">enable</link> = true;
-  <link linkend="opt-programs.digitalbitbox.package">package</link> = pkgs.digitalbitbox.override {
-    udevRule51 = "something else";
-  };
-};
-</programlisting>
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/programs/droidcam.nix b/nixpkgs/nixos/modules/programs/droidcam.nix
index 9843a1f5be25..c9b4457d1d18 100644
--- a/nixpkgs/nixos/modules/programs/droidcam.nix
+++ b/nixpkgs/nixos/modules/programs/droidcam.nix
@@ -4,7 +4,7 @@ with lib;
 
 {
   options.programs.droidcam = {
-    enable = mkEnableOption "DroidCam client";
+    enable = mkEnableOption (lib.mdDoc "DroidCam client");
   };
 
   config = lib.mkIf config.programs.droidcam.enable {
diff --git a/nixpkgs/nixos/modules/programs/evince.nix b/nixpkgs/nixos/modules/programs/evince.nix
index bbc54241d52d..9ed5ea0feb04 100644
--- a/nixpkgs/nixos/modules/programs/evince.nix
+++ b/nixpkgs/nixos/modules/programs/evince.nix
@@ -22,7 +22,7 @@ in {
     programs.evince = {
 
       enable = mkEnableOption
-        "Evince, the GNOME document viewer";
+        (lib.mdDoc "Evince, the GNOME document viewer");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/programs/extra-container.nix b/nixpkgs/nixos/modules/programs/extra-container.nix
index c10ccd769168..5e717c4d8223 100644
--- a/nixpkgs/nixos/modules/programs/extra-container.nix
+++ b/nixpkgs/nixos/modules/programs/extra-container.nix
@@ -5,10 +5,10 @@ let
   cfg = config.programs.extra-container;
 in {
   options = {
-    programs.extra-container.enable = mkEnableOption ''
+    programs.extra-container.enable = mkEnableOption (lib.mdDoc ''
       extra-container, a tool for running declarative NixOS containers
       without host system rebuilds
-    '';
+    '');
   };
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.extra-container ];
diff --git a/nixpkgs/nixos/modules/programs/feedbackd.nix b/nixpkgs/nixos/modules/programs/feedbackd.nix
index 7e6cf829467d..cee8daa31462 100644
--- a/nixpkgs/nixos/modules/programs/feedbackd.nix
+++ b/nixpkgs/nixos/modules/programs/feedbackd.nix
@@ -7,11 +7,11 @@ let
 in {
   options = {
     programs.feedbackd = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         Whether to enable the feedbackd D-BUS service and udev rules.
 
         Your user needs to be in the `feedbackd` group to trigger effects.
-      '';
+      '');
       package = mkOption {
         description = lib.mdDoc ''
           Which feedbackd package to use.
diff --git a/nixpkgs/nixos/modules/programs/file-roller.nix b/nixpkgs/nixos/modules/programs/file-roller.nix
index ca2651becfea..ca0c4d1b2a2a 100644
--- a/nixpkgs/nixos/modules/programs/file-roller.nix
+++ b/nixpkgs/nixos/modules/programs/file-roller.nix
@@ -21,7 +21,7 @@ in {
 
     programs.file-roller = {
 
-      enable = mkEnableOption "File Roller, an archive manager for GNOME";
+      enable = mkEnableOption (lib.mdDoc "File Roller, an archive manager for GNOME");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/programs/firefox.nix b/nixpkgs/nixos/modules/programs/firefox.nix
new file mode 100644
index 000000000000..ead048134d8d
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/firefox.nix
@@ -0,0 +1,268 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.firefox;
+
+  nmh = cfg.nativeMessagingHosts;
+
+  policyFormat = pkgs.formats.json { };
+
+  organisationInfo = ''
+    When this option is in use, Firefox will inform you that "your browser
+    is managed by your organisation". That message appears because NixOS
+    installs what you have declared here such that it cannot be overridden
+    through the user interface. It does not mean that someone else has been
+    given control of your browser, unless of course they also control your
+    NixOS configuration.
+  '';
+in
+{
+  options.programs.firefox = {
+    enable = mkEnableOption (mdDoc "the Firefox web browser");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.firefox;
+      description = mdDoc "Firefox package to use.";
+      defaultText = literalExpression "pkgs.firefox";
+      relatedPackages = [
+        "firefox"
+        "firefox-beta-bin"
+        "firefox-bin"
+        "firefox-devedition-bin"
+        "firefox-esr"
+      ];
+    };
+
+    policies = mkOption {
+      type = policyFormat.type;
+      default = { };
+      description = mdDoc ''
+        Group policies to install.
+
+        See [Mozilla's documentation](https://github.com/mozilla/policy-templates/blob/master/README.md)
+        for a list of available options.
+
+        This can be used to install extensions declaratively! Check out the
+        documentation of the `ExtensionSettings` policy for details.
+
+        ${organisationInfo}
+      '';
+    };
+
+    preferences = mkOption {
+      type = with types; attrsOf (oneOf [ bool int string ]);
+      default = { };
+      description = mdDoc ''
+        Preferences to set from `about:config`.
+
+        Some of these might be able to be configured more ergonomically
+        using policies.
+
+        ${organisationInfo}
+      '';
+    };
+
+    preferencesStatus = mkOption {
+      type = types.enum [ "default" "locked" "user" "clear" ];
+      default = "locked";
+      description = mdDoc ''
+        The status of `firefox.preferences`.
+
+        `status` can assume the following values:
+        - `"default"`: Preferences appear as default.
+        - `"locked"`: Preferences appear as default and can't be changed.
+        - `"user"`: Preferences appear as changed.
+        - `"clear"`: Value has no effect. Resets to factory defaults on each startup.
+      '';
+    };
+
+    languagePacks = mkOption {
+      # Available languages can be found in https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/
+      type = types.listOf (types.enum ([
+        "ach"
+        "af"
+        "an"
+        "ar"
+        "ast"
+        "az"
+        "be"
+        "bg"
+        "bn"
+        "br"
+        "bs"
+        "ca-valencia"
+        "ca"
+        "cak"
+        "cs"
+        "cy"
+        "da"
+        "de"
+        "dsb"
+        "el"
+        "en-CA"
+        "en-GB"
+        "en-US"
+        "eo"
+        "es-AR"
+        "es-CL"
+        "es-ES"
+        "es-MX"
+        "et"
+        "eu"
+        "fa"
+        "ff"
+        "fi"
+        "fr"
+        "fy-NL"
+        "ga-IE"
+        "gd"
+        "gl"
+        "gn"
+        "gu-IN"
+        "he"
+        "hi-IN"
+        "hr"
+        "hsb"
+        "hu"
+        "hy-AM"
+        "ia"
+        "id"
+        "is"
+        "it"
+        "ja"
+        "ka"
+        "kab"
+        "kk"
+        "km"
+        "kn"
+        "ko"
+        "lij"
+        "lt"
+        "lv"
+        "mk"
+        "mr"
+        "ms"
+        "my"
+        "nb-NO"
+        "ne-NP"
+        "nl"
+        "nn-NO"
+        "oc"
+        "pa-IN"
+        "pl"
+        "pt-BR"
+        "pt-PT"
+        "rm"
+        "ro"
+        "ru"
+        "sco"
+        "si"
+        "sk"
+        "sl"
+        "son"
+        "sq"
+        "sr"
+        "sv-SE"
+        "szl"
+        "ta"
+        "te"
+        "th"
+        "tl"
+        "tr"
+        "trs"
+        "uk"
+        "ur"
+        "uz"
+        "vi"
+        "xh"
+        "zh-CN"
+        "zh-TW"
+      ]));
+      default = [ ];
+      description = mdDoc ''
+        The language packs to install.
+      '';
+    };
+
+    autoConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = mdDoc ''
+        AutoConfig files can be used to set and lock preferences that are not covered
+        by the policies.json for Mac and Linux. This method can be used to automatically
+        change user preferences or prevent the end user from modifiying specific
+        preferences by locking them. More info can be found in https://support.mozilla.org/en-US/kb/customizing-firefox-using-autoconfig.
+      '';
+    };
+
+    nativeMessagingHosts = mapAttrs (_: v: mkEnableOption (mdDoc v)) {
+      browserpass = "Browserpass support";
+      bukubrow = "Bukubrow support";
+      euwebid = "Web eID support";
+      ff2mpv = "ff2mpv support";
+      fxCast = "fx_cast support";
+      gsconnect = "GSConnect support";
+      jabref = "JabRef support";
+      passff = "PassFF support";
+      tridactyl = "Tridactyl support";
+      ugetIntegrator = "Uget Integrator support";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      (cfg.package.override {
+        extraPrefs = cfg.autoConfig;
+        extraNativeMessagingHosts = with pkgs; optionals nmh.ff2mpv [
+          ff2mpv
+        ] ++ optionals nmh.euwebid [
+          web-eid-app
+        ] ++ optionals nmh.gsconnect [
+          gnomeExtensions.gsconnect
+        ] ++ optionals nmh.jabref [
+          jabref
+        ] ++ optionals nmh.passff [
+          passff-host
+        ];
+      })
+    ];
+
+    nixpkgs.config.firefox = {
+      enableBrowserpass = nmh.browserpass;
+      enableBukubrow = nmh.bukubrow;
+      enableEUWebID = nmh.euwebid;
+      enableTridactylNative = nmh.tridactyl;
+      enableUgetIntegrator = nmh.ugetIntegrator;
+      enableFXCastBridge = nmh.fxCast;
+    };
+
+    environment.etc =
+      let
+        policiesJSON = policyFormat.generate "firefox-policies.json" { inherit (cfg) policies; };
+      in
+      mkIf (cfg.policies != { }) {
+        "firefox/policies/policies.json".source = "${policiesJSON}";
+      };
+
+    # Preferences are converted into a policy
+    programs.firefox.policies = {
+      Preferences = (mapAttrs
+        (_: value: { Value = value; Status = cfg.preferencesStatus; })
+        cfg.preferences);
+      ExtensionSettings = listToAttrs (map
+        (lang: nameValuePair
+          "langpack-${lang}@firefox.mozilla.org"
+          {
+            installation_mode = "normal_installed";
+            install_url = "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi";
+          }
+        )
+        cfg.languagePacks);
+    };
+  };
+
+  meta.maintainers = with maintainers; [ danth ];
+}
diff --git a/nixpkgs/nixos/modules/programs/firejail.nix b/nixpkgs/nixos/modules/programs/firejail.nix
index 417eff91d3c7..6f79c13d94b4 100644
--- a/nixpkgs/nixos/modules/programs/firejail.nix
+++ b/nixpkgs/nixos/modules/programs/firejail.nix
@@ -8,18 +8,21 @@ let
   wrappedBins = pkgs.runCommand "firejail-wrapped-binaries"
     { preferLocalBuild = true;
       allowSubstitutes = false;
+      # take precedence over non-firejailed versions
+      meta.priority = -1;
     }
     ''
       mkdir -p $out/bin
+      mkdir -p $out/share/applications
       ${lib.concatStringsSep "\n" (lib.mapAttrsToList (command: value:
       let
         opts = if builtins.isAttrs value
         then value
-        else { executable = value; profile = null; extraArgs = []; };
+        else { executable = value; desktop = null; profile = null; extraArgs = []; };
         args = lib.escapeShellArgs (
           opts.extraArgs
           ++ (optional (opts.profile != null) "--profile=${toString opts.profile}")
-          );
+        );
       in
       ''
         cat <<_EOF >$out/bin/${command}
@@ -27,31 +30,42 @@ let
         exec /run/wrappers/bin/firejail ${args} -- ${toString opts.executable} "\$@"
         _EOF
         chmod 0755 $out/bin/${command}
+
+        ${lib.optionalString (opts.desktop != null) ''
+          substitute ${opts.desktop} $out/share/applications/$(basename ${opts.desktop}) \
+            --replace ${opts.executable} $out/bin/${command}
+        ''}
       '') cfg.wrappedBinaries)}
     '';
 
 in {
   options.programs.firejail = {
-    enable = mkEnableOption "firejail";
+    enable = mkEnableOption (lib.mdDoc "firejail");
 
     wrappedBinaries = mkOption {
       type = types.attrsOf (types.either types.path (types.submodule {
         options = {
           executable = mkOption {
             type = types.path;
-            description = "Executable to run sandboxed";
+            description = lib.mdDoc "Executable to run sandboxed";
             example = literalExpression ''"''${lib.getBin pkgs.firefox}/bin/firefox"'';
           };
+          desktop = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = lib.mkDoc ".desktop file to modify. Only necessary if it uses the absolute path to the executable.";
+            example = literalExpression ''"''${pkgs.firefox}/share/applications/firefox.desktop"'';
+          };
           profile = mkOption {
             type = types.nullOr types.path;
             default = null;
-            description = "Profile to use";
+            description = lib.mdDoc "Profile to use";
             example = literalExpression ''"''${pkgs.firejail}/etc/firejail/firefox.profile"'';
           };
           extraArgs = mkOption {
             type = types.listOf types.str;
             default = [];
-            description = "Extra arguments to pass to firejail";
+            description = lib.mdDoc "Extra arguments to pass to firejail";
             example = [ "--private=~/.firejail_home" ];
           };
         };
@@ -71,12 +85,6 @@ in {
       '';
       description = lib.mdDoc ''
         Wrap the binaries in firejail and place them in the global path.
-
-        You will get file collisions if you put the actual application binary in
-        the global environment (such as by adding the application package to
-        `environment.systemPackages`), 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/fish.nix b/nixpkgs/nixos/modules/programs/fish.nix
index 357105c3e79b..478f07d01310 100644
--- a/nixpkgs/nixos/modules/programs/fish.nix
+++ b/nixpkgs/nixos/modules/programs/fish.nix
@@ -35,7 +35,7 @@ let
     '';
 
   babelfishTranslate = path: name:
-    pkgs.runCommand "${name}.fish" {
+    pkgs.runCommandLocal "${name}.fish" {
       nativeBuildInputs = [ pkgs.babelfish ];
     } "${pkgs.babelfish}/bin/babelfish < ${path} > $out;";
 
@@ -303,7 +303,7 @@ in
     programs.fish.interactiveShellInit = ''
       # add completions generated by NixOS to $fish_complete_path
       begin
-        # joins with null byte to acommodate all characters in paths, then respectively gets all paths before (exclusive) / after (inclusive) the first one including "generated_completions",
+        # joins with null byte to accommodate all characters in paths, then respectively gets all paths before (exclusive) / after (inclusive) the first one including "generated_completions",
         # splits by null byte, and then removes all empty lines produced by using 'string'
         set -l prev (string join0 $fish_complete_path | string match --regex "^.*?(?=\x00[^\x00]*generated_completions.*)" | string split0 | string match -er ".")
         set -l post (string join0 $fish_complete_path | string match --regex "[^\x00]*generated_completions.*" | string split0 | string match -er ".")
diff --git a/nixpkgs/nixos/modules/programs/flashrom.nix b/nixpkgs/nixos/modules/programs/flashrom.nix
index 5f0de5a40234..9f8faff14e47 100644
--- a/nixpkgs/nixos/modules/programs/flashrom.nix
+++ b/nixpkgs/nixos/modules/programs/flashrom.nix
@@ -16,11 +16,11 @@ in
         group.
       '';
     };
+    package = mkPackageOptionMD pkgs "flashrom" { };
   };
 
   config = mkIf cfg.enable {
-    services.udev.packages = [ pkgs.flashrom ];
-    environment.systemPackages = [ pkgs.flashrom ];
-    users.groups.flashrom = { };
+    services.udev.packages = [ cfg.package ];
+    environment.systemPackages = [ cfg.package ];
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/flexoptix-app.nix b/nixpkgs/nixos/modules/programs/flexoptix-app.nix
index e87d1076508b..2524e7ba4d58 100644
--- a/nixpkgs/nixos/modules/programs/flexoptix-app.nix
+++ b/nixpkgs/nixos/modules/programs/flexoptix-app.nix
@@ -7,7 +7,7 @@ let
 in {
   options = {
     programs.flexoptix-app = {
-      enable = mkEnableOption "FLEXOPTIX app + udev rules";
+      enable = mkEnableOption (lib.mdDoc "FLEXOPTIX app + udev rules");
 
       package = mkOption {
         description = lib.mdDoc "FLEXOPTIX app package to use";
diff --git a/nixpkgs/nixos/modules/programs/fzf.nix b/nixpkgs/nixos/modules/programs/fzf.nix
new file mode 100644
index 000000000000..7c4f338e29b3
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/fzf.nix
@@ -0,0 +1,32 @@
+{ pkgs, config, lib, ... }:
+with lib;
+let
+  cfg = config.programs.fzf;
+in
+{
+  options = {
+    programs.fzf = {
+      fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with fzf");
+      keybindings = mkEnableOption (mdDoc "fzf keybindings");
+    };
+  };
+  config = {
+    environment.systemPackages = optional (cfg.keybindings || cfg.fuzzyCompletion) pkgs.fzf;
+
+    programs.bash.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
+      source ${pkgs.fzf}/share/fzf/completion.bash
+    '' + optionalString cfg.keybindings ''
+      source ${pkgs.fzf}/share/fzf/key-bindings.bash
+    '';
+
+    programs.zsh.interactiveShellInit = optionalString (!config.programs.zsh.ohMyZsh.enable)
+      (optionalString cfg.fuzzyCompletion ''
+        source ${pkgs.fzf}/share/fzf/completion.zsh
+      '' + optionalString cfg.keybindings ''
+        source ${pkgs.fzf}/share/fzf/key-bindings.zsh
+      '');
+
+    programs.zsh.ohMyZsh.plugins = lib.mkIf (cfg.keybindings || cfg.fuzzyCompletion) [ "fzf" ];
+  };
+  meta.maintainers = with maintainers; [ laalsaas ];
+}
diff --git a/nixpkgs/nixos/modules/programs/gamemode.nix b/nixpkgs/nixos/modules/programs/gamemode.nix
index 84e20934edb6..c43e2c2296f5 100644
--- a/nixpkgs/nixos/modules/programs/gamemode.nix
+++ b/nixpkgs/nixos/modules/programs/gamemode.nix
@@ -10,9 +10,9 @@ in
 {
   options = {
     programs.gamemode = {
-      enable = mkEnableOption "GameMode to optimise system performance on demand";
+      enable = mkEnableOption (lib.mdDoc "GameMode to optimise system performance on demand");
 
-      enableRenice = mkEnableOption "CAP_SYS_NICE on gamemoded to support lowering process niceness" // {
+      enableRenice = mkEnableOption (lib.mdDoc "CAP_SYS_NICE on gamemoded to support lowering process niceness") // {
         default = true;
       };
 
diff --git a/nixpkgs/nixos/modules/programs/gamescope.nix b/nixpkgs/nixos/modules/programs/gamescope.nix
new file mode 100644
index 000000000000..c4424849a41e
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/gamescope.nix
@@ -0,0 +1,85 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+with lib; let
+  cfg = config.programs.gamescope;
+
+  gamescope =
+    let
+      wrapperArgs =
+        optional (cfg.args != [ ])
+          ''--add-flags "${toString cfg.args}"''
+        ++ builtins.attrValues (mapAttrs (var: val: "--set-default ${var} ${val}") cfg.env);
+    in
+    pkgs.runCommand "gamescope" { nativeBuildInputs = [ pkgs.makeBinaryWrapper ]; } ''
+      mkdir -p $out/bin
+      makeWrapper ${cfg.package}/bin/gamescope $out/bin/gamescope --inherit-argv0 \
+        ${toString wrapperArgs}
+    '';
+in
+{
+  options.programs.gamescope = {
+    enable = mkEnableOption (mdDoc "gamescope");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.gamescope;
+      defaultText = literalExpression "pkgs.gamescope";
+      description = mdDoc ''
+        The GameScope package to use.
+      '';
+    };
+
+    capSysNice = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Add cap_sys_nice capability to the GameScope
+        binary so that it may renice itself.
+      '';
+    };
+
+    args = mkOption {
+      type = types.listOf types.string;
+      default = [ ];
+      example = [ "--rt" "--prefer-vk-device 8086:9bc4" ];
+      description = mdDoc ''
+        Arguments passed to GameScope on startup.
+      '';
+    };
+
+    env = mkOption {
+      type = types.attrsOf types.string;
+      default = { };
+      example = literalExpression ''
+        # for Prime render offload on Nvidia laptops.
+        # Also requires `hardware.nvidia.prime.offload.enable`.
+        {
+          __NV_PRIME_RENDER_OFFLOAD = "1";
+          __VK_LAYER_NV_optimus = "NVIDIA_only";
+          __GLX_VENDOR_LIBRARY_NAME = "nvidia";
+        }
+      '';
+      description = mdDoc ''
+        Default environment variables available to the GameScope process, overridable at runtime.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers = mkIf cfg.capSysNice {
+      gamescope = {
+        owner = "root";
+        group = "root";
+        source = "${gamescope}/bin/gamescope";
+        capabilities = "cap_sys_nice+pie";
+      };
+    };
+
+    environment.systemPackages = mkIf (!cfg.capSysNice) [ gamescope ];
+  };
+
+  meta.maintainers = with maintainers; [ nrdxp ];
+}
diff --git a/nixpkgs/nixos/modules/programs/geary.nix b/nixpkgs/nixos/modules/programs/geary.nix
index 407680c30dc3..d9454a2247fd 100644
--- a/nixpkgs/nixos/modules/programs/geary.nix
+++ b/nixpkgs/nixos/modules/programs/geary.nix
@@ -11,7 +11,7 @@ in {
   };
 
   options = {
-    programs.geary.enable = mkEnableOption "Geary, a Mail client for GNOME 3";
+    programs.geary.enable = mkEnableOption (lib.mdDoc "Geary, a Mail client for GNOME 3");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/programs/git.nix b/nixpkgs/nixos/modules/programs/git.nix
index c4cf3cc561ae..4e271a8c134b 100644
--- a/nixpkgs/nixos/modules/programs/git.nix
+++ b/nixpkgs/nixos/modules/programs/git.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     programs.git = {
-      enable = mkEnableOption "git";
+      enable = mkEnableOption (lib.mdDoc "git");
 
       package = mkOption {
         type = types.package;
@@ -20,20 +20,46 @@ in
       };
 
       config = mkOption {
-        type = with types; attrsOf (attrsOf anything);
-        default = { };
+        type =
+          with types;
+          let
+            gitini = attrsOf (attrsOf anything);
+          in
+          either gitini (listOf gitini) // {
+            merge = loc: defs:
+              let
+                config = foldl'
+                  (acc: { value, ... }@x: acc // (if isList value then {
+                    ordered = acc.ordered ++ value;
+                  } else {
+                    unordered = acc.unordered ++ [ x ];
+                  }))
+                  {
+                    ordered = [ ];
+                    unordered = [ ];
+                  }
+                  defs;
+              in
+              [ (gitini.merge loc config.unordered) ] ++ config.ordered;
+          };
+        default = [ ];
         example = {
           init.defaultBranch = "main";
           url."https://github.com/".insteadOf = [ "gh:" "github:" ];
         };
         description = lib.mdDoc ''
-          Configuration to write to /etc/gitconfig. See the CONFIGURATION FILE
-          section of git-config(1) for more information.
+          Configuration to write to /etc/gitconfig. A list can also be
+          specified to keep the configuration in order. For example, setting
+          `config` to `[ { foo.x = 42; } { bar.y = 42; }]` will put the `foo`
+          section before the `bar` section unlike the default alphabetical
+          order, which can be helpful for sections such as `include` and
+          `includeIf`. See the CONFIGURATION FILE section of git-config(1) for
+          more information.
         '';
       };
 
       lfs = {
-        enable = mkEnableOption "git-lfs";
+        enable = mkEnableOption (lib.mdDoc "git-lfs");
 
         package = mkOption {
           type = types.package;
@@ -48,8 +74,8 @@ in
   config = mkMerge [
     (mkIf cfg.enable {
       environment.systemPackages = [ cfg.package ];
-      environment.etc.gitconfig = mkIf (cfg.config != {}) {
-        text = generators.toGitINI cfg.config;
+      environment.etc.gitconfig = mkIf (cfg.config != [ ]) {
+        text = concatMapStringsSep "\n" generators.toGitINI cfg.config;
       };
     })
     (mkIf (cfg.enable && cfg.lfs.enable) {
diff --git a/nixpkgs/nixos/modules/programs/gnome-documents.nix b/nixpkgs/nixos/modules/programs/gnome-documents.nix
deleted file mode 100644
index 2831ac9aff2e..000000000000
--- a/nixpkgs/nixos/modules/programs/gnome-documents.nix
+++ /dev/null
@@ -1,54 +0,0 @@
-# GNOME Documents.
-
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-{
-
-  meta = {
-    maintainers = teams.gnome.members;
-  };
-
-  # Added 2019-08-09
-  imports = [
-    (mkRenamedOptionModule
-      [ "services" "gnome" "gnome-documents" "enable" ]
-      [ "programs" "gnome-documents" "enable" ])
-  ];
-
-  ###### interface
-
-  options = {
-
-    programs.gnome-documents = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to enable GNOME Documents, a document
-          manager application for GNOME.
-        '';
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.programs.gnome-documents.enable {
-
-    environment.systemPackages = [ pkgs.gnome.gnome-documents ];
-
-    services.dbus.packages = [ pkgs.gnome.gnome-documents ];
-
-    services.gnome.gnome-online-accounts.enable = true;
-
-    services.gnome.gnome-online-miners.enable = true;
-
-  };
-
-}
diff --git a/nixpkgs/nixos/modules/programs/gnome-terminal.nix b/nixpkgs/nixos/modules/programs/gnome-terminal.nix
index 71a6b217880c..a8d82e0b018c 100644
--- a/nixpkgs/nixos/modules/programs/gnome-terminal.nix
+++ b/nixpkgs/nixos/modules/programs/gnome-terminal.nix
@@ -24,7 +24,7 @@ in
   ];
 
   options = {
-    programs.gnome-terminal.enable = mkEnableOption "GNOME Terminal";
+    programs.gnome-terminal.enable = mkEnableOption (lib.mdDoc "GNOME Terminal");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/programs/gnupg.nix b/nixpkgs/nixos/modules/programs/gnupg.nix
index ad27766b72ec..764a67a160c1 100644
--- a/nixpkgs/nixos/modules/programs/gnupg.nix
+++ b/nixpkgs/nixos/modules/programs/gnupg.nix
@@ -10,7 +10,8 @@ let
 
   defaultPinentryFlavor =
     if xserverCfg.desktopManager.lxqt.enable
-    || xserverCfg.desktopManager.plasma5.enable then
+    || xserverCfg.desktopManager.plasma5.enable
+    || xserverCfg.desktopManager.deepin.enable then
       "qt"
     else if xserverCfg.desktopManager.xfce.enable then
       "gtk2"
@@ -71,7 +72,7 @@ in
       type = types.nullOr (types.enum pkgs.pinentry.flavors);
       example = "gnome3";
       default = defaultPinentryFlavor;
-      defaultText = literalDocBook ''matching the configured desktop environment'';
+      defaultText = literalMD ''matching the configured desktop environment'';
       description = lib.mdDoc ''
         Which pinentry interface to use. If not null, the path to the
         pinentry binary will be passed to gpg-agent via commandline and
@@ -129,12 +130,14 @@ in
     environment.interactiveShellInit = ''
       # Bind gpg-agent to this TTY if gpg commands are used.
       export GPG_TTY=$(tty)
+    '';
 
-    '' + (optionalString cfg.agent.enableSSHSupport ''
-      # SSH agent protocol doesn't support changing TTYs, so bind the agent
-      # to every new TTY.
-      ${cfg.package}/bin/gpg-connect-agent --quiet updatestartuptty /bye > /dev/null
-    '');
+    programs.ssh.extraConfig = optionalString cfg.agent.enableSSHSupport ''
+      # The SSH agent protocol doesn't have support for changing TTYs; however we
+      # can simulate this with the `exec` feature of openssh (see ssh_config(5))
+      # that hooks a command to the shell currently running the ssh program.
+      Match host * exec "${pkgs.runtimeShell} -c '${cfg.package}/bin/gpg-connect-agent --quiet updatestartuptty /bye >/dev/null 2>&1'"
+    '';
 
     environment.extraInit = mkIf cfg.agent.enableSSHSupport ''
       if [ -z "$SSH_AUTH_SOCK" ]; then
diff --git a/nixpkgs/nixos/modules/programs/haguichi.nix b/nixpkgs/nixos/modules/programs/haguichi.nix
index 4f48551cf1da..699327c28c61 100644
--- a/nixpkgs/nixos/modules/programs/haguichi.nix
+++ b/nixpkgs/nixos/modules/programs/haguichi.nix
@@ -4,7 +4,7 @@ with lib;
 
 {
   options.programs.haguichi = {
-    enable = mkEnableOption "Haguichi, a Linux GUI frontend to the proprietary LogMeIn Hamachi";
+    enable = mkEnableOption (lib.mdDoc "Haguichi, a Linux GUI frontend to the proprietary LogMeIn Hamachi");
   };
 
   config = mkIf config.programs.haguichi.enable {
diff --git a/nixpkgs/nixos/modules/programs/hamster.nix b/nixpkgs/nixos/modules/programs/hamster.nix
index 0bb56ad7ff36..f50438cc1704 100644
--- a/nixpkgs/nixos/modules/programs/hamster.nix
+++ b/nixpkgs/nixos/modules/programs/hamster.nix
@@ -6,7 +6,7 @@ with lib;
   meta.maintainers = pkgs.hamster.meta.maintainers;
 
   options.programs.hamster.enable =
-    mkEnableOption "hamster, a time tracking program";
+    mkEnableOption (lib.mdDoc "hamster, a time tracking program");
 
   config = lib.mkIf config.programs.hamster.enable {
     environment.systemPackages = [ pkgs.hamster ];
diff --git a/nixpkgs/nixos/modules/programs/htop.nix b/nixpkgs/nixos/modules/programs/htop.nix
index 94f6e3c0efa7..2682ced490ca 100644
--- a/nixpkgs/nixos/modules/programs/htop.nix
+++ b/nixpkgs/nixos/modules/programs/htop.nix
@@ -20,13 +20,13 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.htop;
-      defaultText = "pkgs.htop";
+      defaultText = lib.literalExpression "pkgs.htop";
       description = lib.mdDoc ''
         The htop package that should be used.
       '';
     };
 
-    enable = mkEnableOption "htop process monitor";
+    enable = mkEnableOption (lib.mdDoc "htop process monitor");
 
     settings = mkOption {
       type = with types; attrsOf (oneOf [ str int bool (listOf (oneOf [ str int bool ])) ]);
diff --git a/nixpkgs/nixos/modules/programs/hyprland.nix b/nixpkgs/nixos/modules/programs/hyprland.nix
new file mode 100644
index 000000000000..92b8e992e648
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/hyprland.nix
@@ -0,0 +1,81 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+with lib; let
+  cfg = config.programs.hyprland;
+
+  defaultHyprlandPackage = pkgs.hyprland.override {
+    enableXWayland = cfg.xwayland.enable;
+    hidpiXWayland = cfg.xwayland.hidpi;
+    nvidiaPatches = cfg.nvidiaPatches;
+  };
+in
+{
+  options.programs.hyprland = {
+    enable = mkEnableOption null // {
+      description = mdDoc ''
+        Hyprland, the dynamic tiling Wayland compositor that doesn't sacrifice on its looks.
+
+        You can manually launch Hyprland by executing {command}`Hyprland` on a TTY.
+
+        A configuration file will be generated in {file}`~/.config/hypr/hyprland.conf`.
+        See <https://wiki.hyprland.org> for more information.
+      '';
+    };
+
+    package = mkOption {
+      type = types.path;
+      default = defaultHyprlandPackage;
+      defaultText = literalExpression ''
+        pkgs.hyprland.override {
+          enableXWayland = config.programs.hyprland.xwayland.enable;
+          hidpiXWayland = config.programs.hyprland.xwayland.hidpi;
+          nvidiaPatches = config.programs.hyprland.nvidiaPatches;
+        }
+      '';
+      example = literalExpression "<Hyprland flake>.packages.<system>.default";
+      description = mdDoc ''
+        The Hyprland package to use.
+        Setting this option will make {option}`programs.hyprland.xwayland` and
+        {option}`programs.hyprland.nvidiaPatches` not work.
+      '';
+    };
+
+    xwayland = {
+      enable = mkEnableOption (mdDoc "XWayland") // { default = true; };
+      hidpi = mkEnableOption null // {
+        description = mdDoc ''
+          Enable HiDPI XWayland, based on [XWayland MR 733](https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/733).
+          See <https://wiki.hyprland.org/Nix/Options-Overrides/#xwayland-hidpi> for more info.
+        '';
+      };
+    };
+
+    nvidiaPatches = mkEnableOption (mdDoc "patching wlroots for better Nvidia support");
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    fonts.enableDefaultFonts = mkDefault true;
+    hardware.opengl.enable = mkDefault true;
+
+    programs = {
+      dconf.enable = mkDefault true;
+      xwayland.enable = mkDefault cfg.xwayland.enable;
+    };
+
+    security.polkit.enable = true;
+
+    services.xserver.displayManager.sessionPackages = [ cfg.package ];
+
+    xdg.portal = {
+      enable = mkDefault true;
+      extraPortals = [
+        pkgs.xdg-desktop-portal-hyprland
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/i3lock.nix b/nixpkgs/nixos/modules/programs/i3lock.nix
new file mode 100644
index 000000000000..466ae59c9277
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/i3lock.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs.i3lock;
+
+in {
+
+  ###### interface
+
+  options = {
+    programs.i3lock = {
+      enable = mkEnableOption (mdDoc "i3lock");
+      package = mkOption {
+        type        = types.package;
+        default     = pkgs.i3lock;
+        defaultText = literalExpression "pkgs.i3lock";
+        example     = literalExpression ''
+          pkgs.i3lock-color
+        '';
+        description = mdDoc ''
+          Specify which package to use for the i3lock program,
+          The i3lock package must include a i3lock file or link in its out directory in order for the u2fSupport option to work correctly.
+        '';
+      };
+      u2fSupport = mkOption {
+        type        = types.bool;
+        default     = false;
+        example     = true;
+        description = mdDoc ''
+          Whether to enable U2F support in the i3lock program.
+          U2F enables authentication using a hardware device, such as a security key.
+          When U2F support is enabled, the i3lock program will set the setuid bit on the i3lock binary and enable the pam u2fAuth service,
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    security.wrappers.i3lock = mkIf cfg.u2fSupport {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${cfg.package.out}/bin/i3lock";
+    };
+
+    security.pam.services.i3lock.u2fAuth = cfg.u2fSupport;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/programs/iay.nix b/nixpkgs/nixos/modules/programs/iay.nix
new file mode 100644
index 000000000000..9164f5cb6486
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/iay.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.iay;
+  inherit (lib) mkEnableOption mkIf mkOption mkPackageOptionMD optionalString types;
+in {
+  options.programs.iay = {
+    enable = mkEnableOption (lib.mdDoc "iay");
+    package = mkPackageOptionMD pkgs "iay" {};
+
+    minimalPrompt = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Use minimal one-liner prompt.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    programs.bash.promptInit = ''
+      if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
+        PS1='$(iay ${optionalString cfg.minimalPrompt "-m"})'
+      fi
+    '';
+
+    programs.zsh.promptInit = ''
+      if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
+        autoload -Uz add-zsh-hook
+        _iay_prompt() {
+          PROMPT="$(iay -z ${optionalString cfg.minimalPrompt "-m"})"
+        }
+        add-zsh-hook precmd _iay_prompt
+      fi
+    '';
+  };
+
+  meta.maintainers = pkgs.iay.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/programs/iftop.nix b/nixpkgs/nixos/modules/programs/iftop.nix
index c74714a9a6d6..1db018858b65 100644
--- a/nixpkgs/nixos/modules/programs/iftop.nix
+++ b/nixpkgs/nixos/modules/programs/iftop.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.iftop;
 in {
   options = {
-    programs.iftop.enable = mkEnableOption "iftop + setcap wrapper";
+    programs.iftop.enable = mkEnableOption (lib.mdDoc "iftop + setcap wrapper");
   };
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.iftop ];
diff --git a/nixpkgs/nixos/modules/programs/iotop.nix b/nixpkgs/nixos/modules/programs/iotop.nix
index b7c1c69f9ddd..0eb60b989eb3 100644
--- a/nixpkgs/nixos/modules/programs/iotop.nix
+++ b/nixpkgs/nixos/modules/programs/iotop.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.iotop;
 in {
   options = {
-    programs.iotop.enable = mkEnableOption "iotop + setcap wrapper";
+    programs.iotop.enable = mkEnableOption (lib.mdDoc "iotop + setcap wrapper");
   };
   config = mkIf cfg.enable {
     security.wrappers.iotop = {
diff --git a/nixpkgs/nixos/modules/programs/java.nix b/nixpkgs/nixos/modules/programs/java.nix
index 5994f53f76b7..c5f83858d06a 100644
--- a/nixpkgs/nixos/modules/programs/java.nix
+++ b/nixpkgs/nixos/modules/programs/java.nix
@@ -8,27 +8,25 @@ with lib;
 let
   cfg = config.programs.java;
 in
-
 {
 
   options = {
 
     programs.java = {
 
-      enable = mkEnableOption "java" // {
-        description = ''
+      enable = mkEnableOption (lib.mdDoc "java") // {
+        description = lib.mdDoc ''
           Install and setup the Java development kit.
-          <note>
-          <para>This adds JAVA_HOME to the global environment, by sourcing the
-            jdk's setup-hook on shell init. It is equivalent to starting a shell
-            through 'nix-shell -p jdk', or roughly the following system-wide
-            configuration:
-          </para>
-          <programlisting>
-            environment.variables.JAVA_HOME = ''${pkgs.jdk.home}/lib/openjdk;
-            environment.systemPackages = [ pkgs.jdk ];
-          </programlisting>
-          </note>
+
+          ::: {.note}
+          This adds JAVA_HOME to the global environment, by sourcing the
+          jdk's setup-hook on shell init. It is equivalent to starting a shell
+          through 'nix-shell -p jdk', or roughly the following system-wide
+          configuration:
+
+              environment.variables.JAVA_HOME = ''${pkgs.jdk.home}/lib/openjdk;
+              environment.systemPackages = [ pkgs.jdk ];
+          :::
         '';
       };
 
@@ -41,12 +39,35 @@ in
         type = types.package;
       };
 
+      binfmt = mkEnableOption (lib.mdDoc "binfmt to execute java jar's and classes");
+
     };
 
   };
 
   config = mkIf cfg.enable {
 
+    boot.binfmt.registrations = mkIf cfg.binfmt {
+      java-class = {
+        recognitionType = "extension";
+        magicOrExtension = "class";
+        interpreter = pkgs.writeShellScript "java-class-wrapper" ''
+          test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+          classpath=$(dirname "$1")
+          class=$(basename "''${1%%.class}")
+          $JAVA_HOME/bin/java -classpath "$classpath" "$class" "''${@:2}"
+        '';
+      };
+      java-jar = {
+        recognitionType = "extension";
+        magicOrExtension = "jar";
+        interpreter = pkgs.writeShellScript "java-jar-wrapper" ''
+          test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+          $JAVA_HOME/bin/java -jar "$@"
+        '';
+      };
+    };
+
     environment.systemPackages = [ cfg.package ];
 
     environment.shellInit = ''
diff --git a/nixpkgs/nixos/modules/programs/k3b.nix b/nixpkgs/nixos/modules/programs/k3b.nix
index 68a4d08f349c..5d19e4f1cc4f 100644
--- a/nixpkgs/nixos/modules/programs/k3b.nix
+++ b/nixpkgs/nixos/modules/programs/k3b.nix
@@ -8,15 +8,15 @@ with lib;
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable k3b, the KDE disk burning application.
 
-        Additionally to installing <package>k3b</package> enabling this will
-        add <literal>setuid</literal> wrappers in <literal>/run/wrappers/bin</literal>
-        for both <package>cdrdao</package> and <package>cdrecord</package>. On first
-        run you must manually configure the path of <package>cdrdae</package> and
-        <package>cdrecord</package> to correspond to the appropriate paths under
-        <literal>/run/wrappers/bin</literal> in the "Setup External Programs" menu.
+        Additionally to installing `k3b` enabling this will
+        add `setuid` wrappers in `/run/wrappers/bin`
+        for both `cdrdao` and `cdrecord`. On first
+        run you must manually configure the path of `cdrdae` and
+        `cdrecord` to correspond to the appropriate paths under
+        `/run/wrappers/bin` in the "Setup External Programs" menu.
       '';
     };
   };
@@ -28,7 +28,7 @@ with lib;
       k3b
       dvdplusrwtools
       cdrdao
-      cdrkit
+      cdrtools
     ];
 
     security.wrappers = {
@@ -44,7 +44,7 @@ with lib;
         owner = "root";
         group = "cdrom";
         permissions = "u+wrx,g+x";
-        source = "${pkgs.cdrkit}/bin/cdrecord";
+        source = "${pkgs.cdrtools}/bin/cdrecord";
       };
     };
 
diff --git a/nixpkgs/nixos/modules/programs/k40-whisperer.nix b/nixpkgs/nixos/modules/programs/k40-whisperer.nix
index 305c828f0a8a..27a79caa4b53 100644
--- a/nixpkgs/nixos/modules/programs/k40-whisperer.nix
+++ b/nixpkgs/nixos/modules/programs/k40-whisperer.nix
@@ -10,7 +10,7 @@ let
 in
 {
   options.programs.k40-whisperer = {
-    enable = mkEnableOption "K40-Whisperer";
+    enable = mkEnableOption (lib.mdDoc "K40-Whisperer");
 
     group = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/programs/kbdlight.nix b/nixpkgs/nixos/modules/programs/kbdlight.nix
index 8a2a0057cf2d..6c3c79ddb4aa 100644
--- a/nixpkgs/nixos/modules/programs/kbdlight.nix
+++ b/nixpkgs/nixos/modules/programs/kbdlight.nix
@@ -7,7 +7,7 @@ let
 
 in
 {
-  options.programs.kbdlight.enable = mkEnableOption "kbdlight";
+  options.programs.kbdlight.enable = mkEnableOption (lib.mdDoc "kbdlight");
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.kbdlight ];
diff --git a/nixpkgs/nixos/modules/programs/kclock.nix b/nixpkgs/nixos/modules/programs/kclock.nix
index 42d81d2798ba..63d6fb1e2d7f 100644
--- a/nixpkgs/nixos/modules/programs/kclock.nix
+++ b/nixpkgs/nixos/modules/programs/kclock.nix
@@ -4,7 +4,7 @@ let
   cfg = config.programs.kclock;
   kclockPkg = pkgs.libsForQt5.kclock;
 in {
-  options.programs.kclock = { enable = mkEnableOption "Enable KClock"; };
+  options.programs.kclock = { enable = mkEnableOption (lib.mdDoc "KClock"); };
 
   config = mkIf cfg.enable {
     services.dbus.packages = [ kclockPkg ];
diff --git a/nixpkgs/nixos/modules/programs/kdeconnect.nix b/nixpkgs/nixos/modules/programs/kdeconnect.nix
index 1f326c9e9219..4978c428ce34 100644
--- a/nixpkgs/nixos/modules/programs/kdeconnect.nix
+++ b/nixpkgs/nixos/modules/programs/kdeconnect.nix
@@ -2,15 +2,15 @@
 with lib;
 {
   options.programs.kdeconnect = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       kdeconnect.
 
       Note that it will open the TCP and UDP port from
       1714 to 1764 as they are needed for it to function properly.
-      You can use the <option>package</option> to use
-      <literal>gnomeExtensions.gsconnect</literal> as an alternative
+      You can use the {option}`package` to use
+      `gnomeExtensions.gsconnect` as an alternative
       implementation if you use Gnome.
-    '';
+    '');
     package = mkOption {
       default = pkgs.plasma5Packages.kdeconnect-kde;
       defaultText = literalExpression "pkgs.plasma5Packages.kdeconnect-kde";
diff --git a/nixpkgs/nixos/modules/programs/less.nix b/nixpkgs/nixos/modules/programs/less.nix
index 9f2d5d915815..81c68307aee1 100644
--- a/nixpkgs/nixos/modules/programs/less.nix
+++ b/nixpkgs/nixos/modules/programs/less.nix
@@ -11,7 +11,7 @@ let
     ${concatStringsSep "\n"
       (mapAttrsToList (command: action: "${command} ${action}") cfg.commands)
     }
-    ${if cfg.clearDefaultCommands then "#stop" else ""}
+    ${optionalString cfg.clearDefaultCommands "#stop"}
 
     #line-edit
     ${concatStringsSep "\n"
@@ -35,7 +35,7 @@ in
 
       # note that environment.nix sets PAGER=less, and
       # therefore also enables this module
-      enable = mkEnableOption "less";
+      enable = mkEnableOption (lib.mdDoc "less");
 
       configFile = mkOption {
         type = types.nullOr types.path;
@@ -103,7 +103,8 @@ in
         type = types.nullOr types.str;
         default = null;
         description = lib.mdDoc ''
-          When less closes a file opened in such a way, it will call another program, called the input postprocessor, which may  perform  any  desired  clean-up  action (such  as deleting the replacement file created by LESSOPEN).
+          When less closes a file opened in such a way, it will call another program, called the input postprocessor,
+          which may perform any desired clean-up action (such as deleting the replacement file created by LESSOPEN).
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/programs/liboping.nix b/nixpkgs/nixos/modules/programs/liboping.nix
index 4433f9767d6e..39e75ba90c9d 100644
--- a/nixpkgs/nixos/modules/programs/liboping.nix
+++ b/nixpkgs/nixos/modules/programs/liboping.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.liboping;
 in {
   options.programs.liboping = {
-    enable = mkEnableOption "liboping";
+    enable = mkEnableOption (lib.mdDoc "liboping");
   };
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs; [ liboping ];
diff --git a/nixpkgs/nixos/modules/programs/mdevctl.nix b/nixpkgs/nixos/modules/programs/mdevctl.nix
new file mode 100644
index 000000000000..2b7285233350
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/mdevctl.nix
@@ -0,0 +1,18 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.programs.mdevctl;
+in {
+  options.programs.mdevctl = {
+    enable = mkEnableOption (lib.mdDoc "Mediated Device Management");
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ mdevctl ];
+
+    environment.etc."mdevctl.d/scripts.d/notifiers/.keep".text = "";
+    environment.etc."mdevctl.d/scripts.d/callouts/.keep".text = "";
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/mepo.nix b/nixpkgs/nixos/modules/programs/mepo.nix
new file mode 100644
index 000000000000..4b1706a2a0e5
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/mepo.nix
@@ -0,0 +1,46 @@
+{ pkgs, config, lib, ...}:
+with lib;
+let
+  cfg = config.programs.mepo;
+in
+{
+  options.programs.mepo = {
+    enable = mkEnableOption (mdDoc "Mepo");
+
+    locationBackends = {
+      gpsd = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc ''
+          Whether to enable location detection via gpsd.
+          This may require additional configuration of gpsd, see [here](#opt-services.gpsd.enable)
+        '';
+      };
+
+      geoclue = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc "Whether to enable location detection via geoclue";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [
+      mepo
+    ] ++ lib.optional cfg.locationBackends.geoclue geoclue2-with-demo-agent
+    ++ lib.optional cfg.locationBackends.gpsd gpsd;
+
+    services.geoclue2 = mkIf cfg.locationBackends.geoclue {
+      enable = true;
+      appConfig.where-am-i = {
+        isAllowed = true;
+        isSystem = false;
+      };
+    };
+
+    services.gpsd.enable = cfg.locationBackends.gpsd;
+  };
+
+  meta.maintainers = with maintainers; [ laalsaas ];
+}
diff --git a/nixpkgs/nixos/modules/programs/mininet.nix b/nixpkgs/nixos/modules/programs/mininet.nix
index 2cf6c014c352..02272729d233 100644
--- a/nixpkgs/nixos/modules/programs/mininet.nix
+++ b/nixpkgs/nixos/modules/programs/mininet.nix
@@ -14,7 +14,7 @@ let
   pyEnv = pkgs.python.withPackages(ps: [ ps.mininet-python ]);
 
   mnexecWrapped = pkgs.runCommand "mnexec-wrapper"
-    { buildInputs = [ pkgs.makeWrapper pkgs.pythonPackages.wrapPython ]; }
+    { nativeBuildInputs = [ pkgs.makeWrapper pkgs.pythonPackages.wrapPython ]; }
     ''
       makeWrapper ${pkgs.mininet}/bin/mnexec \
         $out/bin/mnexec \
@@ -28,7 +28,7 @@ let
     '';
 in
 {
-  options.programs.mininet.enable = mkEnableOption "Mininet";
+  options.programs.mininet.enable = mkEnableOption (lib.mdDoc "Mininet");
 
   config = mkIf cfg.enable {
 
diff --git a/nixpkgs/nixos/modules/programs/minipro.nix b/nixpkgs/nixos/modules/programs/minipro.nix
new file mode 100644
index 000000000000..a947f83f2ee0
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/minipro.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.minipro;
+in
+{
+  options = {
+    programs.minipro = {
+      enable = lib.mkEnableOption (lib.mdDoc "minipro") // {
+        description = lib.mdDoc ''
+          Installs minipro and its udev rules.
+          Users of the `plugdev` group can interact with connected MiniPRO chip programmers.
+        '';
+      };
+
+      package = lib.mkPackageOptionMD pkgs "minipro" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    users.groups.plugdev = { };
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ infinidoge ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/miriway.nix b/nixpkgs/nixos/modules/programs/miriway.nix
new file mode 100644
index 000000000000..a67e1a17a7e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/miriway.nix
@@ -0,0 +1,78 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.programs.miriway;
+in {
+  options.programs.miriway = {
+    enable = lib.mkEnableOption (lib.mdDoc ''
+      Miriway, a Mir based Wayland compositor. You can manually launch Miriway by
+      executing "exec miriway" on a TTY, or launch it from a display manager. Copy
+      /etc/xdg/xdg-miriway/miriway-shell.config to ~/.config/miriway-shell.config
+      to modify the system-wide configuration on a per-user basis. See <https://github.com/Miriway/Miriway>,
+      and "miriway --help" for more information'');
+
+    config = lib.mkOption {
+      type = lib.types.lines;
+      default = ''
+        x11-window-title=Miriway (Mir-on-X)
+        idle-timeout=600
+        ctrl-alt=t:miriway-terminal # Default "terminal emulator finder"
+
+        shell-component=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+
+        meta=Left:@dock-left
+        meta=Right:@dock-right
+        meta=Space:@toggle-maximized
+        meta=Home:@workspace-begin
+        meta=End:@workspace-end
+        meta=Page_Up:@workspace-up
+        meta=Page_Down:@workspace-down
+        ctrl-alt=BackSpace:@exit
+      '';
+      example = ''
+        idle-timeout=300
+        ctrl-alt=t:weston-terminal
+        add-wayland-extensions=all
+
+        shell-components=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+
+        shell-component=waybar
+        shell-component=wbg Pictures/wallpaper
+
+        shell-meta=a:synapse
+
+        meta=Left:@dock-left
+        meta=Right:@dock-right
+        meta=Space:@toggle-maximized
+        meta=Home:@workspace-begin
+        meta=End:@workspace-end
+        meta=Page_Up:@workspace-up
+        meta=Page_Down:@workspace-down
+        ctrl-alt=BackSpace:@exit
+      '';
+      description = lib.mdDoc ''
+        Miriway's config. This will be installed system-wide.
+        The default will install the miriway package's barebones example config.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      systemPackages = [ pkgs.miriway ];
+      etc = {
+        "xdg/xdg-miriway/miriway-shell.config".text = cfg.config;
+      };
+    };
+
+    hardware.opengl.enable = lib.mkDefault true;
+    fonts.enableDefaultFonts = lib.mkDefault true;
+    programs.dconf.enable = lib.mkDefault true;
+    programs.xwayland.enable = lib.mkDefault true;
+
+    # To make the Miriway session available if a display manager like SDDM is enabled:
+    services.xserver.displayManager.sessionPackages = [ pkgs.miriway ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ OPNA2608 ];
+}
diff --git a/nixpkgs/nixos/modules/programs/mosh.nix b/nixpkgs/nixos/modules/programs/mosh.nix
index 31aadb6aba61..9e56e1731d7c 100644
--- a/nixpkgs/nixos/modules/programs/mosh.nix
+++ b/nixpkgs/nixos/modules/programs/mosh.nix
@@ -17,7 +17,7 @@ in
       type = lib.types.bool;
     };
     withUtempter = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable libutempter for mosh.
         This is required so that mosh can write to /var/run/utmp (which can be queried with `who` to display currently connected user sessions).
         Note, this will add a guid wrapper for the group utmp!
diff --git a/nixpkgs/nixos/modules/programs/msmtp.nix b/nixpkgs/nixos/modules/programs/msmtp.nix
index fbdab2cac559..a9aed027bdb7 100644
--- a/nixpkgs/nixos/modules/programs/msmtp.nix
+++ b/nixpkgs/nixos/modules/programs/msmtp.nix
@@ -10,7 +10,7 @@ in {
 
   options = {
     programs.msmtp = {
-      enable = mkEnableOption "msmtp - an SMTP client";
+      enable = mkEnableOption (lib.mdDoc "msmtp - an SMTP client");
 
       setSendmail = mkOption {
         type = types.bool;
@@ -45,7 +45,7 @@ in {
             passwordeval = "cat /secrets/password.txt";
           };
         };
-        description = ''
+        description = lib.mdDoc ''
           Named accounts and their respective configurations.
           The special name "default" allows a default account to be defined.
           See msmtp(1) for the available options.
diff --git a/nixpkgs/nixos/modules/programs/nano.nix b/nixpkgs/nixos/modules/programs/nano.nix
index 16bab620d6e2..7705bf0ddc72 100644
--- a/nixpkgs/nixos/modules/programs/nano.nix
+++ b/nixpkgs/nixos/modules/programs/nano.nix
@@ -35,8 +35,17 @@ in
   ###### implementation
 
   config = lib.mkIf (cfg.nanorc != "" || cfg.syntaxHighlight) {
-    environment.etc.nanorc.text = lib.concatStrings [ cfg.nanorc
-      (lib.optionalString cfg.syntaxHighlight ''${LF}include "${pkgs.nano}/share/nano/*.nanorc"'') ];
+    environment.etc.nanorc.text = lib.concatStringsSep LF (
+      ( lib.optionals cfg.syntaxHighlight [
+          "# The line below is added because value of programs.nano.syntaxHighlight is set to true"
+          ''include "${pkgs.nano}/share/nano/*.nanorc"''
+          ""
+      ])
+      ++ ( lib.optionals (cfg.nanorc != "") [
+        "# The lines below have been set from value of programs.nano.nanorc"
+        cfg.nanorc
+      ])
+    );
   };
 
 }
diff --git a/nixpkgs/nixos/modules/programs/nbd.nix b/nixpkgs/nixos/modules/programs/nbd.nix
index fea9bc1ff71a..a44403021e35 100644
--- a/nixpkgs/nixos/modules/programs/nbd.nix
+++ b/nixpkgs/nixos/modules/programs/nbd.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     programs.nbd = {
-      enable = mkEnableOption "Network Block Device (nbd) support";
+      enable = mkEnableOption (lib.mdDoc "Network Block Device (nbd) support");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/neovim.nix b/nixpkgs/nixos/modules/programs/neovim.nix
index 85963159a725..1b53b9b5d919 100644
--- a/nixpkgs/nixos/modules/programs/neovim.nix
+++ b/nixpkgs/nixos/modules/programs/neovim.nix
@@ -4,14 +4,22 @@ with lib;
 
 let
   cfg = config.programs.neovim;
-
-  runtime' = filter (f: f.enable) (attrValues cfg.runtime);
-
-  runtime = pkgs.linkFarm "neovim-runtime" (map (x: { name = x.target; path = x.source; }) runtime');
-
-in {
+in
+{
   options.programs.neovim = {
-    enable = mkEnableOption "Neovim";
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = lib.mdDoc ''
+        Whether to enable Neovim.
+
+        When enabled through this option, Neovim is wrapped to use a
+        configuration managed by this module. The configuration file in the
+        user's home directory at {file}`~/.config/nvim/init.vim` is no longer
+        loaded by default.
+      '';
+    };
 
     defaultEditor = mkOption {
       type = types.bool;
@@ -58,7 +66,7 @@ in {
 
     configure = mkOption {
       type = types.attrs;
-      default = {};
+      default = { };
       example = literalExpression ''
         {
           customRC = '''
@@ -89,11 +97,11 @@ in {
       type = types.package;
       visible = false;
       readOnly = true;
-      description = "Resulting customized neovim package.";
+      description = lib.mdDoc "Resulting customized neovim package.";
     };
 
     runtime = mkOption {
-      default = {};
+      default = { };
       example = literalExpression ''
         { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
       '';
@@ -103,14 +111,15 @@ in {
 
       type = with types; attrsOf (submodule (
         { name, config, ... }:
-        { options = {
+        {
+          options = {
 
             enable = mkOption {
               type = types.bool;
               default = true;
               description = lib.mdDoc ''
-                Whether this /etc file should be generated.  This
-                option allows specific /etc files to be disabled.
+                Whether this runtime directory should be generated.  This
+                option allows specific runtime files to be disabled.
               '';
             };
 
@@ -129,20 +138,16 @@ in {
             };
 
             source = mkOption {
-              type = types.path;
+              default = null;
+              type = types.nullOr types.path;
               description = lib.mdDoc "Path of the source file.";
             };
 
           };
 
-          config = {
-            target = mkDefault name;
-            source = mkIf (config.text != null) (
-              let name' = "neovim-runtime" + baseNameOf name;
-              in mkDefault (pkgs.writeText name' config.text));
-          };
-
-        }));
+          config.target = mkDefault name;
+        }
+      ));
 
     };
   };
@@ -153,14 +158,19 @@ in {
     ];
     environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "nvim");
 
-    programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package {
-      inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby;
-      configure = cfg.configure // {
+    environment.etc = listToAttrs (attrValues (mapAttrs
+      (name: value: {
+        name = "xdg/nvim/${name}";
+        value = removeAttrs
+          (value // {
+            target = "xdg/nvim/${value.target}";
+          })
+          (optionals (isNull value.source) [ "source" ]);
+      })
+      cfg.runtime));
 
-        customRC = (cfg.configure.customRC or "") + ''
-          set runtimepath^=${runtime}/etc
-        '';
-      };
+    programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package {
+      inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby configure;
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/nexttrace.nix b/nixpkgs/nixos/modules/programs/nexttrace.nix
new file mode 100644
index 000000000000..091d4f17f9f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/nexttrace.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.nexttrace;
+
+in
+{
+  options = {
+    programs.nexttrace = {
+      enable = lib.mkEnableOption (lib.mdDoc "Nexttrace to the global environment and configure a setcap wrapper for it");
+      package = lib.mkPackageOptionMD pkgs "nexttrace" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    security.wrappers.nexttrace = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw,cap_net_admin+eip";
+      source = "${cfg.package}/bin/nexttrace";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/nix-index.nix b/nixpkgs/nixos/modules/programs/nix-index.nix
new file mode 100644
index 000000000000..a494b9d8c2c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/nix-index.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.programs.nix-index;
+in {
+  options.programs.nix-index = with lib; {
+    enable = mkEnableOption (lib.mdDoc "nix-index, a file database for nixpkgs");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.nix-index;
+      defaultText = literalExpression "pkgs.nix-index";
+      description = lib.mdDoc "Package providing the `nix-index` tool.";
+    };
+
+    enableBashIntegration = mkEnableOption (lib.mdDoc "Bash integration") // {
+      default = true;
+    };
+
+    enableZshIntegration = mkEnableOption (lib.mdDoc "Zsh integration") // {
+      default = true;
+    };
+
+    enableFishIntegration = mkEnableOption (lib.mdDoc "Fish integration") // {
+      default = true;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = let
+      checkOpt = name: {
+        assertion = cfg.${name} -> !config.programs.command-not-found.enable;
+        message = ''
+          The 'programs.command-not-found.enable' option is mutually exclusive
+          with the 'programs.nix-index.${name}' option.
+        '';
+      };
+    in [ (checkOpt "enableBashIntegration") (checkOpt "enableZshIntegration") ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    programs.bash.interactiveShellInit = lib.mkIf cfg.enableBashIntegration ''
+      source ${cfg.package}/etc/profile.d/command-not-found.sh
+    '';
+
+    programs.zsh.interactiveShellInit = lib.mkIf cfg.enableZshIntegration ''
+      source ${cfg.package}/etc/profile.d/command-not-found.sh
+    '';
+
+    # See https://github.com/bennofs/nix-index/issues/126
+    programs.fish.interactiveShellInit = let
+      wrapper = pkgs.writeScript "command-not-found" ''
+        #!${pkgs.bash}/bin/bash
+        source ${cfg.package}/etc/profile.d/command-not-found.sh
+        command_not_found_handle "$@"
+      '';
+    in lib.mkIf cfg.enableFishIntegration ''
+      function __fish_command_not_found_handler --on-event fish_command_not_found
+          ${wrapper} $argv
+      end
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/nix-ld.nix b/nixpkgs/nixos/modules/programs/nix-ld.nix
index 89779cfef390..f0c265f0e5a3 100644
--- a/nixpkgs/nixos/modules/programs/nix-ld.nix
+++ b/nixpkgs/nixos/modules/programs/nix-ld.nix
@@ -1,10 +1,67 @@
 { pkgs, lib, config, ... }:
+let
+  cfg = config.programs.nix-ld;
+
+  # TODO make glibc here configurable?
+  nix-ld-so = pkgs.runCommand "ld.so" {} ''
+    ln -s "$(cat '${pkgs.stdenv.cc}/nix-support/dynamic-linker')" $out
+  '';
+
+  nix-ld-libraries = pkgs.buildEnv {
+    name = "lb-library-path";
+    pathsToLink = [ "/lib" ];
+    paths = map lib.getLib cfg.libraries;
+    extraPrefix = "/share/nix-ld";
+    ignoreCollisions = true;
+  };
+
+  # We currently take all libraries from systemd and nix as the default.
+  # Is there a better list?
+  baseLibraries = with pkgs; [
+    zlib
+    zstd
+    stdenv.cc.cc
+    curl
+    openssl
+    attr
+    libssh
+    bzip2
+    libxml2
+    acl
+    libsodium
+    util-linux
+    xz
+    systemd
+  ];
+in
 {
   meta.maintainers = [ lib.maintainers.mic92 ];
-  options = {
-    programs.nix-ld.enable = lib.mkEnableOption ''nix-ld, Documentation: <link xlink:href="https://github.com/Mic92/nix-ld"/>'';
+  options.programs.nix-ld = {
+    enable = lib.mkEnableOption (lib.mdDoc ''nix-ld, Documentation: <https://github.com/Mic92/nix-ld>'');
+    package = lib.mkOption {
+      type = lib.types.package;
+      description = lib.mdDoc "Which package to use for the nix-ld.";
+      default = pkgs.nix-ld;
+      defaultText = lib.literalExpression "pkgs.nix-ld";
+    };
+    libraries = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
+      description = lib.mdDoc "Libraries that automatically become available to all programs. The default set includes common libraries.";
+      default = baseLibraries;
+      defaultText = lib.literalExpression "baseLibraries derived from systemd and nix dependencies.";
+    };
   };
+
   config = lib.mkIf config.programs.nix-ld.enable {
-    systemd.tmpfiles.packages = [ pkgs.nix-ld ];
+    systemd.tmpfiles.packages = [ cfg.package ];
+
+    environment.systemPackages = [ nix-ld-libraries ];
+
+    environment.pathsToLink = [ "/share/nix-ld" ];
+
+    environment.variables = {
+      NIX_LD = toString nix-ld-so;
+      NIX_LD_LIBRARY_PATH = "/run/current-system/sw/share/nix-ld/lib";
+    };
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/nm-applet.nix b/nixpkgs/nixos/modules/programs/nm-applet.nix
index ef24030c9db9..4b09b1884d7e 100644
--- a/nixpkgs/nixos/modules/programs/nm-applet.nix
+++ b/nixpkgs/nixos/modules/programs/nm-applet.nix
@@ -6,7 +6,7 @@
   };
 
   options.programs.nm-applet = {
-    enable = lib.mkEnableOption "nm-applet";
+    enable = lib.mkEnableOption (lib.mdDoc "nm-applet");
 
     indicator = lib.mkOption {
       type = lib.types.bool;
diff --git a/nixpkgs/nixos/modules/programs/nncp.nix b/nixpkgs/nixos/modules/programs/nncp.nix
index a58748d2fe62..98fea84ab740 100644
--- a/nixpkgs/nixos/modules/programs/nncp.nix
+++ b/nixpkgs/nixos/modules/programs/nncp.nix
@@ -11,7 +11,7 @@ in {
   options.programs.nncp = {
 
     enable =
-      mkEnableOption "NNCP (Node to Node copy) utilities and configuration";
+      mkEnableOption (lib.mdDoc "NNCP (Node to Node copy) utilities and configuration");
 
     group = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/programs/noisetorch.nix b/nixpkgs/nixos/modules/programs/noisetorch.nix
index d5e004da73eb..c022b01d79af 100644
--- a/nixpkgs/nixos/modules/programs/noisetorch.nix
+++ b/nixpkgs/nixos/modules/programs/noisetorch.nix
@@ -6,7 +6,7 @@ let cfg = config.programs.noisetorch;
 in
 {
   options.programs.noisetorch = {
-    enable = mkEnableOption "noisetorch + setcap wrapper";
+    enable = mkEnableOption (lib.mdDoc "noisetorch + setcap wrapper");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/programs/npm.nix b/nixpkgs/nixos/modules/programs/npm.nix
index 709438048347..48dc48e668f3 100644
--- a/nixpkgs/nixos/modules/programs/npm.nix
+++ b/nixpkgs/nixos/modules/programs/npm.nix
@@ -11,7 +11,7 @@ in
 
   options = {
     programs.npm = {
-      enable = mkEnableOption "<command>npm</command> global config";
+      enable = mkEnableOption (lib.mdDoc "{command}`npm` global config");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/programs/openvpn3.nix b/nixpkgs/nixos/modules/programs/openvpn3.nix
index f3101d3cebdc..df7e9ef22c10 100644
--- a/nixpkgs/nixos/modules/programs/openvpn3.nix
+++ b/nixpkgs/nixos/modules/programs/openvpn3.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.programs.openvpn3 = {
-    enable = mkEnableOption "the openvpn3 client";
+    enable = mkEnableOption (lib.mdDoc "the openvpn3 client");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/programs/pantheon-tweaks.nix b/nixpkgs/nixos/modules/programs/pantheon-tweaks.nix
index 0b8a19ea22c0..82f93619db15 100644
--- a/nixpkgs/nixos/modules/programs/pantheon-tweaks.nix
+++ b/nixpkgs/nixos/modules/programs/pantheon-tweaks.nix
@@ -9,7 +9,7 @@ with lib;
 
   ###### interface
   options = {
-    programs.pantheon-tweaks.enable = mkEnableOption "Pantheon Tweaks, an unofficial system settings panel for Pantheon";
+    programs.pantheon-tweaks.enable = mkEnableOption (lib.mdDoc "Pantheon Tweaks, an unofficial system settings panel for Pantheon");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/programs/partition-manager.nix b/nixpkgs/nixos/modules/programs/partition-manager.nix
index 1be2f0a69a11..c18598b7c25d 100644
--- a/nixpkgs/nixos/modules/programs/partition-manager.nix
+++ b/nixpkgs/nixos/modules/programs/partition-manager.nix
@@ -7,7 +7,7 @@ with lib;
 
   ###### interface
   options = {
-    programs.partition-manager.enable = mkEnableOption "KDE Partition Manager";
+    programs.partition-manager.enable = mkEnableOption (lib.mdDoc "KDE Partition Manager");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/programs/plotinus.md b/nixpkgs/nixos/modules/programs/plotinus.md
new file mode 100644
index 000000000000..fac3bbad1e08
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/plotinus.md
@@ -0,0 +1,17 @@
+# Plotinus {#module-program-plotinus}
+
+*Source:* {file}`modules/programs/plotinus.nix`
+
+*Upstream documentation:* <https://github.com/p-e-w/plotinus>
+
+Plotinus is a searchable command palette in every modern GTK application.
+
+When in a GTK 3 application and Plotinus is enabled, you can press
+`Ctrl+Shift+P` to open the command palette. The command
+palette provides a searchable list of of all menu items in the application.
+
+To enable Plotinus, add the following to your
+{file}`configuration.nix`:
+```
+programs.plotinus.enable = true;
+```
diff --git a/nixpkgs/nixos/modules/programs/plotinus.nix b/nixpkgs/nixos/modules/programs/plotinus.nix
index a011bb862aea..c2b6884d6490 100644
--- a/nixpkgs/nixos/modules/programs/plotinus.nix
+++ b/nixpkgs/nixos/modules/programs/plotinus.nix
@@ -8,7 +8,7 @@ in
 {
   meta = {
     maintainers = pkgs.plotinus.meta.maintainers;
-    doc = ./plotinus.xml;
+    doc = ./plotinus.md;
   };
 
   ###### interface
diff --git a/nixpkgs/nixos/modules/programs/plotinus.xml b/nixpkgs/nixos/modules/programs/plotinus.xml
deleted file mode 100644
index 8fc8c22c6d76..000000000000
--- a/nixpkgs/nixos/modules/programs/plotinus.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-program-plotinus">
- <title>Plotinus</title>
- <para>
-  <emphasis>Source:</emphasis>
-  <filename>modules/programs/plotinus.nix</filename>
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis>
-  <link xlink:href="https://github.com/p-e-w/plotinus"/>
- </para>
- <para>
-  Plotinus is a searchable command palette in every modern GTK application.
- </para>
- <para>
-  When in a GTK 3 application and Plotinus is enabled, you can press
-  <literal>Ctrl+Shift+P</literal> to open the command palette. The command
-  palette provides a searchable list of of all menu items in the application.
- </para>
- <para>
-  To enable Plotinus, add the following to your
-  <filename>configuration.nix</filename>:
-<programlisting>
-<xref linkend="opt-programs.plotinus.enable"/> = true;
-</programlisting>
- </para>
-</chapter>
diff --git a/nixpkgs/nixos/modules/programs/proxychains.nix b/nixpkgs/nixos/modules/programs/proxychains.nix
index 5d932b2d8423..9bdd5d405668 100644
--- a/nixpkgs/nixos/modules/programs/proxychains.nix
+++ b/nixpkgs/nixos/modules/programs/proxychains.nix
@@ -22,7 +22,7 @@ let
 
   proxyOptions = {
     options = {
-      enable = mkEnableOption "this proxy";
+      enable = mkEnableOption (lib.mdDoc "this proxy");
 
       type = mkOption {
         type = types.enum [ "http" "socks4" "socks5" ];
@@ -49,7 +49,11 @@ in {
 
     programs.proxychains = {
 
-      enable = mkEnableOption "installing proxychains configuration";
+      enable = mkEnableOption (lib.mdDoc "installing proxychains configuration");
+
+      package = mkPackageOptionMD pkgs "proxychains" {
+        example = "pkgs.proxychains-ng";
+      };
 
       chain = {
         type = mkOption {
@@ -86,7 +90,7 @@ in {
         description = lib.mdDoc "Proxy DNS requests - no leak for DNS data.";
       };
 
-      quietMode = mkEnableOption "Quiet mode (no output from the library).";
+      quietMode = mkEnableOption (lib.mdDoc "Quiet mode (no output from the library)");
 
       remoteDNSSubnet = mkOption {
         type = types.enum [ 10 127 224 ];
@@ -159,7 +163,7 @@ in {
       };
 
     environment.etc."proxychains.conf".text = configFile;
-    environment.systemPackages = [ pkgs.proxychains ];
+    environment.systemPackages = [ cfg.package ];
   };
 
 }
diff --git a/nixpkgs/nixos/modules/programs/qdmr.nix b/nixpkgs/nixos/modules/programs/qdmr.nix
new file mode 100644
index 000000000000..1bb81317bda8
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/qdmr.nix
@@ -0,0 +1,25 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.programs.qdmr;
+in {
+  meta.maintainers = [ lib.maintainers.janik ];
+
+  options = {
+    programs.qdmr = {
+      enable = lib.mkEnableOption (lib.mdDoc "QDMR - a GUI application and command line tool for programming DMR radios");
+      package = lib.mkPackageOptionMD pkgs "qdmr" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+    users.groups.dialout = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/regreet.nix b/nixpkgs/nixos/modules/programs/regreet.nix
new file mode 100644
index 000000000000..f6c750a45bf5
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/regreet.nix
@@ -0,0 +1,75 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+let
+  cfg = config.programs.regreet;
+  settingsFormat = pkgs.formats.toml { };
+in
+{
+  options.programs.regreet = {
+    enable = lib.mkEnableOption null // {
+      description = lib.mdDoc ''
+        Enable ReGreet, a clean and customizable greeter for greetd.
+
+        To use ReGreet, {option}`services.greetd` has to be enabled and
+        {option}`services.greetd.settings.default_session` should contain the
+        appropriate configuration to launch
+        {option}`config.programs.regreet.package`. For examples, see the
+        [ReGreet Readme](https://github.com/rharish101/ReGreet#set-as-default-session).
+
+        A minimal configuration that launches ReGreet in {command}`cage` is
+        enabled by this module by default.
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs [ "greetd" "regreet" ] { };
+
+    settings = lib.mkOption {
+      type = lib.types.either lib.types.path settingsFormat.type;
+      default = { };
+      description = lib.mdDoc ''
+        ReGreet configuration file. Refer
+        <https://github.com/rharish101/ReGreet/blob/main/regreet.sample.toml>
+        for options.
+      '';
+    };
+
+    extraCss = lib.mkOption {
+      type = lib.types.either lib.types.path lib.types.lines;
+      default = "";
+      description = lib.mdDoc ''
+        Extra CSS rules to apply on top of the GTK theme. Refer to
+        [GTK CSS Properties](https://docs.gtk.org/gtk4/css-properties.html) for
+        modifiable properties.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.greetd = {
+      enable = lib.mkDefault true;
+      settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}";
+    };
+
+    environment.etc = {
+      "greetd/regreet.css" =
+        if lib.isPath cfg.extraCss
+        then {source = cfg.extraCss;}
+        else {text = cfg.extraCss;};
+
+      "greetd/regreet.toml".source =
+        if lib.isPath cfg.settings
+        then cfg.settings
+        else settingsFormat.generate "regreet.toml" cfg.settings;
+    };
+
+    systemd.tmpfiles.rules = let
+      user = config.services.greetd.settings.default_session.user;
+    in [
+      "d /var/log/regreet 0755 greeter ${user} - -"
+      "d /var/cache/regreet 0755 greeter ${user} - -"
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/rog-control-center.nix b/nixpkgs/nixos/modules/programs/rog-control-center.nix
new file mode 100644
index 000000000000..4aef5143ac7f
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/rog-control-center.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.rog-control-center;
+in
+{
+  options = {
+    programs.rog-control-center = {
+      enable = lib.mkEnableOption (lib.mdDoc "the rog-control-center application");
+
+      autoStart = lib.mkOption {
+        default = false;
+        type = lib.types.bool;
+        description = lib.mdDoc "Whether rog-control-center should be started automatically.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.asusctl
+      (lib.mkIf cfg.autoStart (pkgs.makeAutostartItem { name = "rog-control-center"; package = pkgs.asusctl; }))
+    ];
+
+    services.asusd.enable = true;
+  };
+
+  meta.maintainers = pkgs.asusctl.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/programs/rust-motd.nix b/nixpkgs/nixos/modules/programs/rust-motd.nix
new file mode 100644
index 000000000000..d5f1820ba752
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/rust-motd.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.rust-motd;
+  format = pkgs.formats.toml { };
+in {
+  options.programs.rust-motd = {
+    enable = mkEnableOption (lib.mdDoc "rust-motd");
+    enableMotdInSSHD = mkOption {
+      default = true;
+      type = types.bool;
+      description = mdDoc ''
+        Whether to let `openssh` print the
+        result when entering a new `ssh`-session.
+        By default either nothing or a static file defined via
+        [](#opt-users.motd) is printed. Because of that,
+        the latter option is incompatible with this module.
+      '';
+    };
+    refreshInterval = mkOption {
+      default = "*:0/5";
+      type = types.str;
+      description = mdDoc ''
+        Interval in which the {manpage}`motd(5)` file is refreshed.
+        For possible formats, please refer to {manpage}`systemd.time(7)`.
+      '';
+    };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = format.type;
+      };
+      description = mdDoc ''
+        Settings on what to generate. Please read the
+        [upstream documentation](https://github.com/rust-motd/rust-motd/blob/main/README.md#configuration)
+        for further information.
+      '';
+    };
+  };
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = config.users.motd == null;
+        message = ''
+          `programs.rust-motd` is incompatible with `users.motd`!
+        '';
+      }
+    ];
+    systemd.services.rust-motd = {
+      path = with pkgs; [ bash ];
+      documentation = [ "https://github.com/rust-motd/rust-motd/blob/v${pkgs.rust-motd.version}/README.md" ];
+      description = "motd generator";
+      serviceConfig = {
+        ExecStart = "${pkgs.writeShellScript "update-motd" ''
+          ${pkgs.rust-motd}/bin/rust-motd ${format.generate "motd.conf" cfg.settings} > motd
+        ''}";
+        CapabilityBoundingSet = [ "" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "full";
+        StateDirectory = "rust-motd";
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        WorkingDirectory = "/var/lib/rust-motd";
+      };
+    };
+    systemd.timers.rust-motd = {
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = cfg.refreshInterval;
+    };
+    security.pam.services.sshd.text = mkIf cfg.enableMotdInSSHD (mkDefault (mkAfter ''
+      session optional ${pkgs.pam}/lib/security/pam_motd.so motd=/var/lib/rust-motd/motd
+    ''));
+    services.openssh.extraConfig = mkIf (cfg.settings ? last_login && cfg.settings.last_login != {}) ''
+      PrintLastLog no
+    '';
+  };
+  meta.maintainers = with maintainers; [ ma27 ];
+}
diff --git a/nixpkgs/nixos/modules/programs/seahorse.nix b/nixpkgs/nixos/modules/programs/seahorse.nix
index c0a356bff57c..5e179c1446ed 100644
--- a/nixpkgs/nixos/modules/programs/seahorse.nix
+++ b/nixpkgs/nixos/modules/programs/seahorse.nix
@@ -20,7 +20,7 @@ with lib;
 
     programs.seahorse = {
 
-      enable = mkEnableOption "Seahorse, a GNOME application for managing encryption keys and passwords in the GNOME Keyring";
+      enable = mkEnableOption (lib.mdDoc "Seahorse, a GNOME application for managing encryption keys and passwords in the GNOME Keyring");
 
     };
 
diff --git a/nixpkgs/nixos/modules/programs/sedutil.nix b/nixpkgs/nixos/modules/programs/sedutil.nix
index 7efc80f4abba..d5e20a8815d4 100644
--- a/nixpkgs/nixos/modules/programs/sedutil.nix
+++ b/nixpkgs/nixos/modules/programs/sedutil.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.sedutil;
 
 in {
-  options.programs.sedutil.enable = mkEnableOption "sedutil";
+  options.programs.sedutil.enable = mkEnableOption (lib.mdDoc "sedutil");
 
   config = mkIf cfg.enable {
     boot.kernelParams = [
diff --git a/nixpkgs/nixos/modules/programs/shadow.nix b/nixpkgs/nixos/modules/programs/shadow.nix
index fab809f279a3..35267acd6bb7 100644
--- a/nixpkgs/nixos/modules/programs/shadow.nix
+++ b/nixpkgs/nixos/modules/programs/shadow.nix
@@ -41,6 +41,8 @@ let
       # This should be made configurable.
       #CHFN_RESTRICT frwh
 
+      # The default crypt() method, keep in sync with the PAM default
+      ENCRYPT_METHOD YESCRYPT
     '';
 
   mkSetuidRoot = source:
diff --git a/nixpkgs/nixos/modules/programs/sharing.nix b/nixpkgs/nixos/modules/programs/sharing.nix
new file mode 100644
index 000000000000..9ab51859dc51
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/sharing.nix
@@ -0,0 +1,19 @@
+{ config, pkgs, lib, ... }:
+with lib;
+{
+  options.programs.sharing = {
+    enable = mkEnableOption (lib.mdDoc ''
+      sharing, a CLI tool for sharing files.
+
+      Note that it will opens the 7478 port for TCP in the firewall, which is needed for it to function properly
+    '');
+  };
+  config =
+    let
+      cfg = config.programs.sharing;
+    in
+      mkIf cfg.enable {
+        environment.systemPackages = [ pkgs.sharing ];
+        networking.firewall.allowedTCPPorts = [ 7478 ];
+      };
+}
diff --git a/nixpkgs/nixos/modules/programs/singularity.nix b/nixpkgs/nixos/modules/programs/singularity.nix
index db935abe4bb4..05fdb4842c54 100644
--- a/nixpkgs/nixos/modules/programs/singularity.nix
+++ b/nixpkgs/nixos/modules/programs/singularity.nix
@@ -3,32 +3,90 @@
 with lib;
 let
   cfg = config.programs.singularity;
-  singularity = pkgs.singularity.overrideAttrs (attrs : {
-    installPhase = attrs.installPhase + ''
-      mv $out/libexec/singularity/bin/starter-suid $out/libexec/singularity/bin/starter-suid.orig
-      ln -s /run/wrappers/bin/singularity-suid $out/libexec/singularity/bin/starter-suid
-    '';
-  });
-in {
+in
+{
+
   options.programs.singularity = {
-    enable = mkEnableOption "Singularity";
+    enable = mkEnableOption (mdDoc "singularity") // {
+      description = mdDoc ''
+        Whether to install Singularity/Apptainer with system-level overriding such as SUID support.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.singularity;
+      defaultText = literalExpression "pkgs.singularity";
+      example = literalExpression "pkgs.apptainer";
+      description = mdDoc ''
+        Singularity/Apptainer package to override and install.
+      '';
+    };
+    packageOverriden = mkOption {
+      type = types.nullOr types.package;
+      default = null;
+      description = mdDoc ''
+        This option provides access to the overridden result of `programs.singularity.package`.
+
+        For example, the following configuration makes all the Nixpkgs packages use the overridden `singularity`:
+        ```Nix
+        { config, lib, pkgs, ... }:
+        {
+          nixpkgs.overlays = [
+            (final: prev: {
+              _singularity-orig = prev.singularity;
+              singularity = config.programs.singularity.packageOverriden;
+            })
+          ];
+          programs.singularity.enable = true;
+          programs.singularity.package = pkgs._singularity-orig;
+        }
+        ```
+
+        Use `lib.mkForce` to forcefully specify the overridden package.
+      '';
+    };
+    enableFakeroot = mkOption {
+      type = types.bool;
+      default = true;
+      example = false;
+      description = mdDoc ''
+        Whether to enable the `--fakeroot` support of Singularity/Apptainer.
+      '';
+    };
+    enableSuid = mkOption {
+      type = types.bool;
+      default = true;
+      example = false;
+      description = mdDoc ''
+        Whether to enable the SUID support of Singularity/Apptainer.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
-      environment.systemPackages = [ singularity ];
-      security.wrappers.singularity-suid =
-      { setuid = true;
-        owner = "root";
-        group = "root";
-        source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
-      };
-      systemd.tmpfiles.rules = [
-        "d /var/singularity/mnt/session 0770 root root -"
-        "d /var/singularity/mnt/final 0770 root root -"
-        "d /var/singularity/mnt/overlay 0770 root root -"
-        "d /var/singularity/mnt/container 0770 root root -"
-        "d /var/singularity/mnt/source 0770 root root -"
-      ];
+    programs.singularity.packageOverriden = (cfg.package.override (
+      optionalAttrs cfg.enableFakeroot {
+        newuidmapPath = "/run/wrappers/bin/newuidmap";
+        newgidmapPath = "/run/wrappers/bin/newgidmap";
+      } // optionalAttrs cfg.enableSuid {
+        enableSuid = true;
+        starterSuidPath = "/run/wrappers/bin/${cfg.package.projectName}-suid";
+      }
+    ));
+    environment.systemPackages = [ cfg.packageOverriden ];
+    security.wrappers."${cfg.packageOverriden.projectName}-suid" = mkIf cfg.enableSuid {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${cfg.packageOverriden}/libexec/${cfg.packageOverriden.projectName}/bin/starter-suid.orig";
+    };
+    systemd.tmpfiles.rules = [
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/session 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/final 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/overlay 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/container 0770 root root -"
+      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/source 0770 root root -"
+    ];
   };
 
 }
diff --git a/nixpkgs/nixos/modules/programs/skim.nix b/nixpkgs/nixos/modules/programs/skim.nix
new file mode 100644
index 000000000000..8dadf322606e
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/skim.nix
@@ -0,0 +1,34 @@
+{ pkgs, config, lib, ... }:
+let
+  inherit (lib) mdDoc mkEnableOption mkPackageOptionMD optional optionalString;
+  cfg = config.programs.skim;
+in
+{
+  options = {
+    programs.skim = {
+      fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with skim");
+      keybindings = mkEnableOption (mdDoc "skim keybindings");
+      package = mkPackageOptionMD pkgs "skim" {};
+    };
+  };
+
+  config = {
+    environment.systemPackages = optional (cfg.keybindings || cfg.fuzzyCompletion) cfg.package;
+
+    programs.bash.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
+      source ${cfg.package}/share/skim/completion.bash
+    '' + optionalString cfg.keybindings ''
+      source ${cfg.package}/share/skim/key-bindings.bash
+    '';
+
+    programs.zsh.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
+      source ${cfg.package}/share/skim/completion.zsh
+    '' + optionalString cfg.keybindings ''
+      source ${cfg.package}/share/skim/key-bindings.zsh
+    '';
+
+    programs.fish.interactiveShellInit = optionalString cfg.keybindings ''
+      source ${cfg.package}/share/skim/key-bindings.fish && skim_key_bindings
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/sniffnet.nix b/nixpkgs/nixos/modules/programs/sniffnet.nix
new file mode 100644
index 000000000000..98e9f628a9bc
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/sniffnet.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.sniffnet;
+in
+
+{
+  options = {
+    programs.sniffnet = {
+      enable = lib.mkEnableOption (lib.mdDoc "sniffnet");
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers.sniffnet = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw,cap_net_admin=eip";
+      source = "${pkgs.sniffnet}/bin/sniffnet";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ figsoda ];
+}
diff --git a/nixpkgs/nixos/modules/programs/ssh.nix b/nixpkgs/nixos/modules/programs/ssh.nix
index 9c4a95ef22c6..7c85d1e7c3d5 100644
--- a/nixpkgs/nixos/modules/programs/ssh.nix
+++ b/nixpkgs/nixos/modules/programs/ssh.nix
@@ -14,6 +14,7 @@ let
     ''
       #! ${pkgs.runtimeShell} -e
       export DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^DISPLAY=\(.*\)/\1/; t; d')"
+      export WAYLAND_DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^WAYLAND_DISPLAY=\(.*\)/\1/; t; d')"
       exec ${askPassword} "$@"
     '';
 
@@ -25,7 +26,7 @@ let
       + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile)
     )) + "\n";
 
-  knownHostsFiles = [ "/etc/ssh/ssh_known_hosts" "/etc/ssh/ssh_known_hosts2" ]
+  knownHostsFiles = [ "/etc/ssh/ssh_known_hosts" ]
     ++ map pkgs.copyPathToStore cfg.knownHostsFiles;
 
 in
@@ -93,10 +94,10 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Extra configuration text prepended to <filename>ssh_config</filename>. Other generated
-          options will be added after a <literal>Host *</literal> pattern.
-          See <citerefentry><refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        description = lib.mdDoc ''
+          Extra configuration text prepended to {file}`ssh_config`. Other generated
+          options will be added after a `Host *` pattern.
+          See {manpage}`ssh_config(5)`
           for help.
         '';
       };
@@ -231,15 +232,14 @@ in
         description = lib.mdDoc ''
           Files containing SSH host keys to set as global known hosts.
           `/etc/ssh/ssh_known_hosts` (which is
-          generated by {option}`programs.ssh.knownHosts`) and
-          `/etc/ssh/ssh_known_hosts2` are always
-          included.
+          generated by {option}`programs.ssh.knownHosts`) is
+          always included.
         '';
         example = literalExpression ''
           [
             ./known_hosts
             (writeText "github.keys" '''
-              github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
+              github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
               github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
               github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
             ''')
@@ -281,7 +281,7 @@ in
   config = {
 
     programs.ssh.setXAuthLocation =
-      mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.forwardX11);
+      mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.settings.X11Forwarding);
 
     assertions =
       [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation;
diff --git a/nixpkgs/nixos/modules/programs/starship.nix b/nixpkgs/nixos/modules/programs/starship.nix
index ade80b9999e1..cacad8eafe3d 100644
--- a/nixpkgs/nixos/modules/programs/starship.nix
+++ b/nixpkgs/nixos/modules/programs/starship.nix
@@ -9,9 +9,26 @@ let
 
   settingsFile = settingsFormat.generate "starship.toml" cfg.settings;
 
-in {
+  initOption =
+    if cfg.interactiveOnly then
+      "promptInit"
+    else
+      "shellInit";
+
+in
+{
   options.programs.starship = {
-    enable = mkEnableOption "the Starship shell prompt";
+    enable = mkEnableOption (lib.mdDoc "the Starship shell prompt");
+
+    interactiveOnly = mkOption {
+      default = true;
+      example = false;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to enable starship only when the shell is interactive.
+        Some plugins require this to be set to false to function correctly.
+      '';
+    };
 
     settings = mkOption {
       inherit (settingsFormat) type;
@@ -25,21 +42,21 @@ in {
   };
 
   config = mkIf cfg.enable {
-    programs.bash.promptInit = ''
+    programs.bash.${initOption} = ''
       if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
         export STARSHIP_CONFIG=${settingsFile}
         eval "$(${pkgs.starship}/bin/starship init bash)"
       fi
     '';
 
-    programs.fish.promptInit = ''
+    programs.fish.${initOption} = ''
       if test "$TERM" != "dumb" -a \( -z "$INSIDE_EMACS" -o "$INSIDE_EMACS" = "vterm" \)
         set -x STARSHIP_CONFIG ${settingsFile}
         eval (${pkgs.starship}/bin/starship init fish)
       end
     '';
 
-    programs.zsh.promptInit = ''
+    programs.zsh.${initOption} = ''
       if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
         export STARSHIP_CONFIG=${settingsFile}
         eval "$(${pkgs.starship}/bin/starship init zsh)"
diff --git a/nixpkgs/nixos/modules/programs/steam.nix b/nixpkgs/nixos/modules/programs/steam.nix
index d80718e792af..c63b31bde11f 100644
--- a/nixpkgs/nixos/modules/programs/steam.nix
+++ b/nixpkgs/nixos/modules/programs/steam.nix
@@ -4,16 +4,67 @@ with lib;
 
 let
   cfg = config.programs.steam;
+  gamescopeCfg = config.programs.gamescope;
 
-  steam = pkgs.steam.override {
-    extraLibraries = pkgs: with config.hardware.opengl;
-      if pkgs.hostPlatform.is64bit
-      then [ package ] ++ extraPackages
-      else [ package32 ] ++ extraPackages32;
-  };
+  steam-gamescope = let
+    exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env);
+  in
+    pkgs.writeShellScriptBin "steam-gamescope" ''
+      ${builtins.concatStringsSep "\n" exports}
+      gamescope --steam ${toString cfg.gamescopeSession.args} -- steam -tenfoot -pipewire-dmabuf
+    '';
+
+  gamescopeSessionFile =
+    (pkgs.writeTextDir "share/wayland-sessions/steam.desktop" ''
+      [Desktop Entry]
+      Name=Steam
+      Comment=A digital distribution platform
+      Exec=${steam-gamescope}/bin/steam-gamescope
+      Type=Application
+    '').overrideAttrs (_: { passthru.providedSessions = [ "steam" ]; });
 in {
   options.programs.steam = {
-    enable = mkEnableOption "steam";
+    enable = mkEnableOption (lib.mdDoc "steam");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.steam;
+      defaultText = literalExpression "pkgs.steam";
+      example = literalExpression ''
+        pkgs.steam-small.override {
+          extraEnv = {
+            MANGOHUD = true;
+            OBS_VKCAPTURE = true;
+            RADV_TEX_ANISO = 16;
+          };
+          extraLibraries = p: with p; [
+            atk
+          ];
+        }
+      '';
+      apply = steam: steam.override (prev: {
+        extraLibraries = pkgs: let
+          prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
+          additionalLibs = with config.hardware.opengl;
+            if pkgs.stdenv.hostPlatform.is64bit
+            then [ package ] ++ extraPackages
+            else [ package32 ] ++ extraPackages32;
+        in prevLibs ++ additionalLibs;
+      } // optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice)
+      {
+        buildFHSEnv = pkgs.buildFHSEnv.override {
+          # use the setuid wrapped bubblewrap
+          bubblewrap = "${config.security.wrapperDir}/..";
+        };
+      });
+      description = lib.mdDoc ''
+        The Steam package to use. Additional libraries are added from the system
+        configuration to ensure graphics work properly.
+
+        Use this option to customise the Steam package rather than adding your
+        custom Steam to {option}`environment.systemPackages` yourself.
+      '';
+    };
 
     remotePlay.openFirewall = mkOption {
       type = types.bool;
@@ -30,6 +81,31 @@ in {
         Open ports in the firewall for Source Dedicated Server.
       '';
     };
+
+    gamescopeSession = mkOption {
+      description = mdDoc "Run a GameScope driven Steam session from your display-manager";
+      default = {};
+      type = types.submodule {
+        options = {
+          enable = mkEnableOption (mdDoc "GameScope Session");
+          args = mkOption {
+            type = types.listOf types.string;
+            default = [ ];
+            description = mdDoc ''
+              Arguments to be passed to GameScope for the session.
+            '';
+          };
+
+          env = mkOption {
+            type = types.attrsOf types.string;
+            default = { };
+            description = mdDoc ''
+              Environmental variables to be passed to GameScope for the session.
+            '';
+          };
+        };
+      };
+    };
   };
 
   config = mkIf cfg.enable {
@@ -39,12 +115,28 @@ in {
       driSupport32Bit = true;
     };
 
+    security.wrappers = mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
+      # needed or steam fails
+      bwrap = {
+        owner = "root";
+        group = "root";
+        source = "${pkgs.bubblewrap}/bin/bwrap";
+        setuid = true;
+      };
+    };
+
+    programs.gamescope.enable = mkDefault cfg.gamescopeSession.enable;
+    services.xserver.displayManager.sessionPackages = mkIf cfg.gamescopeSession.enable [ gamescopeSessionFile ];
+
     # optionally enable 32bit pulseaudio support if pulseaudio is enabled
     hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
 
     hardware.steam-hardware.enable = true;
 
-    environment.systemPackages = [ steam steam.run ];
+    environment.systemPackages = [
+      cfg.package
+      cfg.package.run
+    ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope;
 
     networking.firewall = lib.mkMerge [
       (mkIf cfg.remotePlay.openFirewall {
diff --git a/nixpkgs/nixos/modules/programs/streamdeck-ui.nix b/nixpkgs/nixos/modules/programs/streamdeck-ui.nix
index 04aa0a80e889..4c055029e39b 100644
--- a/nixpkgs/nixos/modules/programs/streamdeck-ui.nix
+++ b/nixpkgs/nixos/modules/programs/streamdeck-ui.nix
@@ -4,24 +4,30 @@ with lib;
 
 let
   cfg = config.programs.streamdeck-ui;
-in {
+in
+{
   options.programs.streamdeck-ui = {
-    enable = mkEnableOption "streamdeck-ui";
+    enable = mkEnableOption (lib.mdDoc "streamdeck-ui");
 
     autoStart = mkOption {
       default = true;
       type = types.bool;
       description = lib.mdDoc "Whether streamdeck-ui should be started automatically.";
     };
+
+    package = mkPackageOptionMD pkgs "streamdeck-ui" {
+      default = [ "streamdeck-ui" ];
+    };
+
   };
 
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs; [
-      streamdeck-ui
-      (mkIf cfg.autoStart (makeAutostartItem { name = "streamdeck-ui"; package = streamdeck-ui; }))
+      cfg.package
+      (mkIf cfg.autoStart (makeAutostartItem { name = "streamdeck-ui"; package = cfg.package; }))
     ];
 
-    services.udev.packages = with pkgs; [ streamdeck-ui ];
+    services.udev.packages = [ cfg.package ];
   };
 
   meta.maintainers = with maintainers; [ majiir ];
diff --git a/nixpkgs/nixos/modules/programs/sysdig.nix b/nixpkgs/nixos/modules/programs/sysdig.nix
index fbbf29065564..ccb1e1d4c5f1 100644
--- a/nixpkgs/nixos/modules/programs/sysdig.nix
+++ b/nixpkgs/nixos/modules/programs/sysdig.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.programs.sysdig;
 in {
-  options.programs.sysdig.enable = mkEnableOption "sysdig";
+  options.programs.sysdig.enable = mkEnableOption (lib.mdDoc "sysdig");
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.sysdig ];
diff --git a/nixpkgs/nixos/modules/programs/system-config-printer.nix b/nixpkgs/nixos/modules/programs/system-config-printer.nix
index 34592dd7064b..7c7eea580545 100644
--- a/nixpkgs/nixos/modules/programs/system-config-printer.nix
+++ b/nixpkgs/nixos/modules/programs/system-config-printer.nix
@@ -10,7 +10,7 @@ with lib;
 
     programs.system-config-printer = {
 
-      enable = mkEnableOption "system-config-printer, a Graphical user interface for CUPS administration";
+      enable = mkEnableOption (lib.mdDoc "system-config-printer, a Graphical user interface for CUPS administration");
 
     };
 
diff --git a/nixpkgs/nixos/modules/programs/thefuck.nix b/nixpkgs/nixos/modules/programs/thefuck.nix
index 18d09e26866c..e057d1ca657d 100644
--- a/nixpkgs/nixos/modules/programs/thefuck.nix
+++ b/nixpkgs/nixos/modules/programs/thefuck.nix
@@ -16,13 +16,13 @@ in
   {
     options = {
       programs.thefuck = {
-        enable = mkEnableOption "thefuck";
+        enable = mkEnableOption (lib.mdDoc "thefuck");
 
         alias = mkOption {
           default = "fuck";
           type = types.str;
 
-          description = ''
+          description = lib.mdDoc ''
             `thefuck` needs an alias to be configured.
             The default value is `fuck`, but you can use anything else as well.
           '';
diff --git a/nixpkgs/nixos/modules/programs/thunar.nix b/nixpkgs/nixos/modules/programs/thunar.nix
index a67d8ae064df..cb85b3886c13 100644
--- a/nixpkgs/nixos/modules/programs/thunar.nix
+++ b/nixpkgs/nixos/modules/programs/thunar.nix
@@ -11,7 +11,7 @@ in {
 
   options = {
     programs.thunar = {
-      enable = mkEnableOption "Thunar, the Xfce file manager";
+      enable = mkEnableOption (lib.mdDoc "Thunar, the Xfce file manager");
 
       plugins = mkOption {
         default = [];
diff --git a/nixpkgs/nixos/modules/programs/tmux.nix b/nixpkgs/nixos/modules/programs/tmux.nix
index cf7ea4cfcf76..4f452f1d7f9b 100644
--- a/nixpkgs/nixos/modules/programs/tmux.nix
+++ b/nixpkgs/nixos/modules/programs/tmux.nix
@@ -1,7 +1,7 @@
 { config, pkgs, lib, ... }:
 
 let
-  inherit (lib) mkOption mkIf types;
+  inherit (lib) mkOption mkIf types optionalString;
 
   cfg = config.programs.tmux;
 
@@ -17,17 +17,17 @@ let
     set  -g base-index      ${toString cfg.baseIndex}
     setw -g pane-base-index ${toString cfg.baseIndex}
 
-    ${if cfg.newSession then "new-session" else ""}
+    ${optionalString cfg.newSession "new-session"}
 
-    ${if cfg.reverseSplit then ''
+    ${optionalString cfg.reverseSplit ''
     bind v split-window -h
     bind s split-window -v
-    '' else ""}
+    ''}
 
     set -g status-keys ${cfg.keyMode}
     set -g mode-keys   ${cfg.keyMode}
 
-    ${if cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize then ''
+    ${optionalString (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) ''
     bind h select-pane -L
     bind j select-pane -D
     bind k select-pane -U
@@ -37,15 +37,15 @@ let
     bind -r J resize-pane -D ${toString cfg.resizeAmount}
     bind -r K resize-pane -U ${toString cfg.resizeAmount}
     bind -r L resize-pane -R ${toString cfg.resizeAmount}
-    '' else ""}
+    ''}
 
-    ${if (cfg.shortcut != defaultShortcut) then ''
+    ${optionalString (cfg.shortcut != defaultShortcut) ''
     # rebind main key: C-${cfg.shortcut}
     unbind C-${defaultShortcut}
     set -g prefix C-${cfg.shortcut}
     bind ${cfg.shortcut} send-prefix
     bind C-${cfg.shortcut} last-window
-    '' else ""}
+    ''}
 
     setw -g aggressive-resize ${boolToStr cfg.aggressiveResize}
     setw -g clock-mode-style  ${if cfg.clock24 then "24" else "12"}
@@ -160,7 +160,10 @@ in {
         default = defaultTerminal;
         example = "screen-256color";
         type = types.str;
-        description = lib.mdDoc "Set the $TERM variable.";
+        description = lib.mdDoc ''
+          Set the $TERM variable. Use tmux-direct if italics or 24bit true color
+          support is needed.
+        '';
       };
 
       secureSocket = mkOption {
@@ -178,6 +181,16 @@ in {
         description = lib.mdDoc "List of plugins to install.";
         example = lib.literalExpression "[ pkgs.tmuxPlugins.nord ]";
       };
+
+      withUtempter = mkOption {
+        description = lib.mdDoc ''
+          Whether to enable libutempter for tmux.
+          This is required so that tmux can write to /var/run/utmp (which can be queried with `who` to display currently connected user sessions).
+          Note, this will add a guid wrapper for the group utmp!
+        '';
+        default = true;
+        type = types.bool;
+      };
     };
   };
 
@@ -193,6 +206,15 @@ in {
         TMUX_TMPDIR = lib.optional cfg.secureSocket ''''${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}'';
       };
     };
+    security.wrappers = mkIf cfg.withUtempter {
+      utempter = {
+        source = "${pkgs.libutempter}/lib/utempter/utempter";
+        owner = "root";
+        group = "utmp";
+        setuid = false;
+        setgid = true;
+      };
+    };
   };
 
   imports = [
diff --git a/nixpkgs/nixos/modules/programs/trippy.nix b/nixpkgs/nixos/modules/programs/trippy.nix
new file mode 100644
index 000000000000..6e31aea43e75
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/trippy.nix
@@ -0,0 +1,24 @@
+{ lib, config, pkgs, ... }:
+
+let
+  cfg = config.programs.trippy;
+in
+
+{
+  options = {
+    programs.trippy = {
+      enable = lib.mkEnableOption (lib.mdDoc "trippy");
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers.trip = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw+p";
+      source = lib.getExe pkgs.trippy;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ figsoda ];
+}
diff --git a/nixpkgs/nixos/modules/programs/tsm-client.nix b/nixpkgs/nixos/modules/programs/tsm-client.nix
index 0a3af3744a78..41560544c2c7 100644
--- a/nixpkgs/nixos/modules/programs/tsm-client.nix
+++ b/nixpkgs/nixos/modules/programs/tsm-client.nix
@@ -6,7 +6,7 @@ let
   inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs;
   inherit (lib.modules) mkDefault mkIf;
   inherit (lib.options) literalExpression mkEnableOption mkOption;
-  inherit (lib.strings) concatStringsSep optionalString toLower;
+  inherit (lib.strings) concatLines optionalString toLower;
   inherit (lib.types) addCheck attrsOf lines nonEmptyStr nullOr package path port str strMatching submodule;
 
   # Checks if given list of strings contains unique
@@ -65,18 +65,18 @@ let
         directive in {file}`dsm.sys`.
       '';
     };
-    options.genPasswd = mkEnableOption ''
+    options.genPasswd = mkEnableOption (lib.mdDoc ''
       automatic client password generation.
       This option influences the
-      <literal>passwordaccess</literal>
-      directive in <filename>dsm.sys</filename>.
+      `passwordaccess`
+      directive in {file}`dsm.sys`.
       The password will be stored in the directory
-      given by the option <option>passwdDir</option>.
-      <emphasis>Caution</emphasis>:
+      given by the option {option}`passwdDir`.
+      *Caution*:
       If this option is enabled and the server forces
       to renew the password (e.g. on first connection),
       a random password will be generated and stored
-    '';
+    '');
     options.passwdDir = mkOption {
       type = path;
       example = "/home/alice/tsm-password";
@@ -95,13 +95,13 @@ let
         exclude.dir     /nix/store
         include.encrypt /home/.../*
       '';
-      description = ''
-        <literal>include.*</literal> and
-        <literal>exclude.*</literal> directives to be
+      description = lib.mdDoc ''
+        `include.*` and
+        `exclude.*` directives to be
         used when sending files to the IBM TSM server.
         The lines will be written into a file that the
-        <literal>inclexcl</literal>
-        directive in <filename>dsm.sys</filename> points to.
+        `inclexcl`
+        directive in {file}`dsm.sys` points to.
       '';
     };
     options.extraConfig = mkOption {
@@ -140,7 +140,7 @@ let
       type = str;
       internal = true;
       visible = false;
-      description = "Server stanza text generated from the options.";
+      description = lib.mdDoc "Server stanza text generated from the options.";
     };
     config.name = mkDefault name;
     # Client system-options file directives are explained here:
@@ -164,7 +164,7 @@ let
         mkLine = k: v: k + optionalString (v!="") "  ${v}";
         lines = mapAttrsToList mkLine attrset;
       in
-        concatStringsSep "\n" lines;
+        concatLines lines;
     config.stanza = ''
       server  ${config.name}
       ${config.text}
@@ -172,11 +172,11 @@ let
   };
 
   options.programs.tsmClient = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       IBM Spectrum Protect (Tivoli Storage Manager, TSM)
       client command line applications with a
       client system-options file "dsm.sys"
-    '';
+    '');
     servers = mkOption {
       type = attrsOf (submodule [ serverOptions ]);
       default = {};
@@ -223,7 +223,7 @@ let
       description = lib.mdDoc ''
         The TSM client derivation to be
         added to the system environment.
-        It will called with `.override`
+        It will be used with `.override`
         to add paths to the client system-options file.
       '';
     };
@@ -263,7 +263,7 @@ let
 
     ${optionalString (cfg.defaultServername!=null) "defaultserver  ${cfg.defaultServername}"}
 
-    ${concatStringsSep "\n" (mapAttrsToList (k: v: v.stanza) cfg.servers)}
+    ${concatLines (mapAttrsToList (k: v: v.stanza) cfg.servers)}
   '';
 
 in
diff --git a/nixpkgs/nixos/modules/programs/turbovnc.nix b/nixpkgs/nixos/modules/programs/turbovnc.nix
index a0e4a36cfd99..511b6badc041 100644
--- a/nixpkgs/nixos/modules/programs/turbovnc.nix
+++ b/nixpkgs/nixos/modules/programs/turbovnc.nix
@@ -39,7 +39,7 @@ in
   config = mkIf cfg.ensureHeadlessSoftwareOpenGL {
 
     # TurboVNC has builtin support for Mesa llvmpipe's `swrast`
-    # software rendering to implemnt GLX (OpenGL on Xorg).
+    # software rendering to implement GLX (OpenGL on Xorg).
     # However, just building TurboVNC with support for that is not enough
     # (it only takes care of the X server side part of OpenGL);
     # the indiviudual applications (e.g. `glxgears`) also need to directly load
diff --git a/nixpkgs/nixos/modules/programs/udevil.nix b/nixpkgs/nixos/modules/programs/udevil.nix
index 0dc08c435df4..b0f00b4b541b 100644
--- a/nixpkgs/nixos/modules/programs/udevil.nix
+++ b/nixpkgs/nixos/modules/programs/udevil.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.udevil;
 
 in {
-  options.programs.udevil.enable = mkEnableOption "udevil";
+  options.programs.udevil.enable = mkEnableOption (lib.mdDoc "udevil");
 
   config = mkIf cfg.enable {
     security.wrappers.udevil =
diff --git a/nixpkgs/nixos/modules/programs/usbtop.nix b/nixpkgs/nixos/modules/programs/usbtop.nix
index c1b6ee38caa1..e262ae3745be 100644
--- a/nixpkgs/nixos/modules/programs/usbtop.nix
+++ b/nixpkgs/nixos/modules/programs/usbtop.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.usbtop;
 in {
   options = {
-    programs.usbtop.enable = mkEnableOption "usbtop and required kernel module";
+    programs.usbtop.enable = mkEnableOption (lib.mdDoc "usbtop and required kernel module");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/programs/vim.nix b/nixpkgs/nixos/modules/programs/vim.nix
index 15983e371f0e..b12a45166d56 100644
--- a/nixpkgs/nixos/modules/programs/vim.nix
+++ b/nixpkgs/nixos/modules/programs/vim.nix
@@ -19,7 +19,7 @@ in {
       type = types.package;
       default = pkgs.vim;
       defaultText = literalExpression "pkgs.vim";
-      example = literalExpression "pkgs.vimHugeX";
+      example = literalExpression "pkgs.vim-full";
       description = lib.mdDoc ''
         vim package to use.
       '';
diff --git a/nixpkgs/nixos/modules/programs/wayland/river.nix b/nixpkgs/nixos/modules/programs/wayland/river.nix
new file mode 100644
index 000000000000..71232a7d2618
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/wayland/river.nix
@@ -0,0 +1,59 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.programs.river;
+in {
+  options.programs.river = {
+    enable = mkEnableOption (lib.mdDoc "river, a dynamic tiling Wayland compositor");
+
+    package = mkOption {
+      type = with types; nullOr package;
+      default = pkgs.river;
+      defaultText = literalExpression "pkgs.river";
+      description = lib.mdDoc ''
+        River package to use.
+        Set to `null` to not add any River package to your path.
+        This should be done if you want to use the Home Manager River module to install River.
+      '';
+    };
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = with pkgs; [
+        swaylock
+        foot
+        dmenu
+      ];
+      defaultText = literalExpression ''
+        with pkgs; [ swaylock foot dmenu ];
+      '';
+      example = literalExpression ''
+        with pkgs; [
+          termite rofi light
+        ]
+      '';
+      description = lib.mdDoc ''
+        Extra packages to be installed system wide. See
+        [Common X11 apps used on i3 with Wayland alternatives](https://github.com/swaywm/sway/wiki/i3-Migration-Guide#common-x11-apps-used-on-i3-with-wayland-alternatives)
+        for a list of useful software.
+      '';
+    };
+  };
+
+  config =
+    mkIf cfg.enable (mkMerge [
+      {
+        environment.systemPackages = optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
+
+        # To make a river session available if a display manager like SDDM is enabled:
+        services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ];
+      }
+      (import ./wayland-session.nix { inherit lib pkgs; })
+    ]);
+
+  meta.maintainers = with lib.maintainers; [ GaetanLepage ];
+}
diff --git a/nixpkgs/nixos/modules/programs/sway.nix b/nixpkgs/nixos/modules/programs/wayland/sway.nix
index af2b261d3c5f..698d9c2b46c4 100644
--- a/nixpkgs/nixos/modules/programs/sway.nix
+++ b/nixpkgs/nixos/modules/programs/wayland/sway.nix
@@ -26,7 +26,7 @@ let
     };
   };
 
-  swayPackage = pkgs.sway.override {
+  defaultSwayPackage = pkgs.sway.override {
     extraSessionCommands = cfg.extraSessionCommands;
     extraOptions = cfg.extraOptions;
     withBaseWrapper = cfg.wrapperFeatures.base;
@@ -35,12 +35,25 @@ let
   };
 in {
   options.programs.sway = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       Sway, the i3-compatible tiling Wayland compositor. You can manually launch
       Sway by executing "exec sway" on a TTY. Copy /etc/sway/config to
       ~/.config/sway/config to modify the default configuration. See
-      <link xlink:href="https://github.com/swaywm/sway/wiki"/> and
-      "man 5 sway" for more information'';
+      <https://github.com/swaywm/sway/wiki> and
+      "man 5 sway" for more information'');
+
+    package = mkOption {
+      type = with types; nullOr package;
+      default = defaultSwayPackage;
+      defaultText = literalExpression "pkgs.sway";
+      description = lib.mdDoc ''
+        Sway package to use. Will override the options
+        'wrapperFeatures', 'extraSessionCommands', and 'extraOptions'.
+        Set to `null` to not add any Sway package to your
+        path. This should be done if you want to use the Home Manager Sway
+        module to install Sway.
+      '';
+    };
 
     wrapperFeatures = mkOption {
       type = wrapperOptions;
@@ -79,7 +92,6 @@ in {
         "--verbose"
         "--debug"
         "--unsupported-gpu"
-        "--my-next-gpu-wont-be-nvidia"
       ];
       description = lib.mdDoc ''
         Command line arguments passed to launch Sway. Please DO NOT report
@@ -111,40 +123,36 @@ in {
 
   };
 
-  config = mkIf cfg.enable {
-    assertions = [
+  config = mkIf cfg.enable
+    (mkMerge [
       {
-        assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
-        message = ''
-          The extraSessionCommands for Sway will not be run if
-          wrapperFeatures.base is disabled.
-        '';
-      }
-    ];
-    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" ''
-          # Import the most important environment variables into the D-Bus and systemd
-          # user environments (e.g. required for screen sharing and Pinentry prompts):
-          exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
-        '';
-      };
-    };
-    security.polkit.enable = true;
-    security.pam.services.swaylock = {};
-    hardware.opengl.enable = mkDefault true;
-    fonts.enableDefaultFonts = mkDefault true;
-    programs.dconf.enable = mkDefault true;
-    # To make a Sway session available if a display manager like SDDM is enabled:
-    services.xserver.displayManager.sessionPackages = [ swayPackage ];
-    programs.xwayland.enable = mkDefault true;
-    # For screen sharing (this option only has an effect with xdg.portal.enable):
-    xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-wlr ];
-  };
+        assertions = [
+          {
+            assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
+            message = ''
+              The extraSessionCommands for Sway will not be run if
+              wrapperFeatures.base is disabled.
+            '';
+          }
+        ];
+        environment = {
+          systemPackages = optional (cfg.package != null) cfg.package ++ cfg.extraPackages;
+          # Needed for the default wallpaper:
+          pathsToLink = optionals (cfg.package != null) [ "/share/backgrounds/sway" ];
+          etc = {
+            "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
+              # Import the most important environment variables into the D-Bus and systemd
+              # user environments (e.g. required for screen sharing and Pinentry prompts):
+              exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
+            '';
+          } // optionalAttrs (cfg.package != null) {
+            "sway/config".source = mkOptionDefault "${cfg.package}/etc/sway/config";
+          };
+        };
+        # To make a Sway session available if a display manager like SDDM is enabled:
+        services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ]; }
+      (import ./wayland-session.nix { inherit lib pkgs; })
+    ]);
 
   meta.maintainers = with lib.maintainers; [ primeos colemickens ];
 }
diff --git a/nixpkgs/nixos/modules/programs/waybar.nix b/nixpkgs/nixos/modules/programs/wayland/waybar.nix
index 22530e6c7d4d..2c49ae140813 100644
--- a/nixpkgs/nixos/modules/programs/waybar.nix
+++ b/nixpkgs/nixos/modules/programs/wayland/waybar.nix
@@ -2,17 +2,22 @@
 
 with lib;
 
+let
+  cfg = config.programs.waybar;
+in
 {
   options.programs.waybar = {
-    enable = mkEnableOption "waybar";
+    enable = mkEnableOption (lib.mdDoc "waybar");
+    package = mkPackageOptionMD pkgs "waybar" { };
   };
 
-  config = mkIf config.programs.waybar.enable {
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
     systemd.user.services.waybar = {
       description = "Waybar as systemd service";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
-      script = "${pkgs.waybar}/bin/waybar";
+      script = "${cfg.package}/bin/waybar";
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/wayland/wayland-session.nix b/nixpkgs/nixos/modules/programs/wayland/wayland-session.nix
new file mode 100644
index 000000000000..3cbfef4d61de
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/wayland/wayland-session.nix
@@ -0,0 +1,23 @@
+{ lib, pkgs, ... }: with lib; {
+    security = {
+      polkit.enable = true;
+      pam.services.swaylock = {};
+    };
+
+    hardware.opengl.enable = mkDefault true;
+    fonts.enableDefaultFonts = mkDefault true;
+
+    programs = {
+      dconf.enable = mkDefault true;
+      xwayland.enable = mkDefault true;
+    };
+
+    xdg.portal = {
+      enable = mkDefault true;
+
+      extraPortals = [
+        # For screen sharing
+        pkgs.xdg-desktop-portal-wlr
+      ];
+    };
+}
diff --git a/nixpkgs/nixos/modules/programs/weylus.nix b/nixpkgs/nixos/modules/programs/weylus.nix
index 0a506bfa2785..a5775f3b981c 100644
--- a/nixpkgs/nixos/modules/programs/weylus.nix
+++ b/nixpkgs/nixos/modules/programs/weylus.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.programs.weylus = with types; {
-    enable = mkEnableOption "weylus";
+    enable = mkEnableOption (lib.mdDoc "weylus");
 
     openFirewall = mkOption {
       type = bool;
@@ -29,7 +29,7 @@ in
     package = mkOption {
       type = package;
       default = pkgs.weylus;
-      defaultText = "pkgs.weylus";
+      defaultText = lib.literalExpression "pkgs.weylus";
       description = lib.mdDoc "Weylus package to install.";
     };
   };
diff --git a/nixpkgs/nixos/modules/programs/wireshark.nix b/nixpkgs/nixos/modules/programs/wireshark.nix
index 088c2bb7958a..834b0ba35695 100644
--- a/nixpkgs/nixos/modules/programs/wireshark.nix
+++ b/nixpkgs/nixos/modules/programs/wireshark.nix
@@ -33,7 +33,7 @@ in {
 
     security.wrappers.dumpcap = {
       source = "${wireshark}/bin/dumpcap";
-      capabilities = "cap_net_raw+p";
+      capabilities = "cap_net_raw,cap_net_admin+eip";
       owner = "root";
       group = "wireshark";
       permissions = "u+rx,g+x";
diff --git a/nixpkgs/nixos/modules/programs/wshowkeys.nix b/nixpkgs/nixos/modules/programs/wshowkeys.nix
index f7b71d2bb0c8..ebb5c5509442 100644
--- a/nixpkgs/nixos/modules/programs/wshowkeys.nix
+++ b/nixpkgs/nixos/modules/programs/wshowkeys.nix
@@ -9,10 +9,10 @@ in {
 
   options = {
     programs.wshowkeys = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         wshowkeys (displays keypresses on screen on supported Wayland
         compositors). It requires root permissions to read input events, but
-        these permissions are dropped after startup'';
+        these permissions are dropped after startup'');
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/xastir.nix b/nixpkgs/nixos/modules/programs/xastir.nix
new file mode 100644
index 000000000000..6d5fc59aac50
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/xastir.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.xastir;
+in {
+  meta.maintainers = with maintainers; [ melling ];
+
+  options.programs.xastir = {
+    enable = mkEnableOption (mdDoc "Xastir Graphical APRS client");
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ xastir ];
+    security.wrappers.xastir = {
+      source = "${pkgs.xastir}/bin/xastir";
+      capabilities = "cap_net_raw+p";
+      owner = "root";
+      group = "root";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/programs/xfconf.nix b/nixpkgs/nixos/modules/programs/xfconf.nix
index 8e854b40e513..b0f45339335d 100644
--- a/nixpkgs/nixos/modules/programs/xfconf.nix
+++ b/nixpkgs/nixos/modules/programs/xfconf.nix
@@ -11,7 +11,7 @@ in {
 
   options = {
     programs.xfconf = {
-      enable = mkEnableOption "Xfconf, the Xfce configuration storage system";
+      enable = mkEnableOption (lib.mdDoc "Xfconf, the Xfce configuration storage system");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/programs/xfs_quota.nix b/nixpkgs/nixos/modules/programs/xfs_quota.nix
index a1e9ff941c6b..0fc2958b3f38 100644
--- a/nixpkgs/nixos/modules/programs/xfs_quota.nix
+++ b/nixpkgs/nixos/modules/programs/xfs_quota.nix
@@ -94,7 +94,7 @@ in
         '';
 
         wantedBy = [ "multi-user.target" ];
-        after = [ ((replaceChars [ "/" ] [ "-" ] opts.fileSystem) + ".mount") ];
+        after = [ ((replaceStrings [ "/" ] [ "-" ] opts.fileSystem) + ".mount") ];
 
         restartTriggers = [ config.environment.etc.projects.source ];
 
diff --git a/nixpkgs/nixos/modules/programs/xonsh.nix b/nixpkgs/nixos/modules/programs/xonsh.nix
index 3223761f9340..7202ed06c6af 100644
--- a/nixpkgs/nixos/modules/programs/xonsh.nix
+++ b/nixpkgs/nixos/modules/programs/xonsh.nix
@@ -46,8 +46,8 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.etc.xonshrc.text = ''
-      # /etc/xonshrc: DO NOT EDIT -- this file has been generated automatically.
+    environment.etc."xonsh/xonshrc".text = ''
+      # /etc/xonsh/xonshrc: DO NOT EDIT -- this file has been generated automatically.
 
 
       if not ''${...}.get('__NIXOS_SET_ENVIRONMENT_DONE'):
diff --git a/nixpkgs/nixos/modules/programs/xss-lock.nix b/nixpkgs/nixos/modules/programs/xss-lock.nix
index c14c09721d6d..87b3957ab834 100644
--- a/nixpkgs/nixos/modules/programs/xss-lock.nix
+++ b/nixpkgs/nixos/modules/programs/xss-lock.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.programs.xss-lock = {
-    enable = mkEnableOption "xss-lock";
+    enable = mkEnableOption (lib.mdDoc "xss-lock");
 
     lockerCommand = mkOption {
       default = "${pkgs.i3lock}/bin/i3lock";
diff --git a/nixpkgs/nixos/modules/programs/xwayland.nix b/nixpkgs/nixos/modules/programs/xwayland.nix
index 9296116dca8e..8d13e4c22b5b 100644
--- a/nixpkgs/nixos/modules/programs/xwayland.nix
+++ b/nixpkgs/nixos/modules/programs/xwayland.nix
@@ -10,7 +10,7 @@ in
 {
   options.programs.xwayland = {
 
-    enable = mkEnableOption "Xwayland (an X server for interfacing X11 apps with the Wayland protocol)";
+    enable = mkEnableOption (lib.mdDoc "Xwayland (an X server for interfacing X11 apps with the Wayland protocol)");
 
     defaultFontPath = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/programs/yabar.nix b/nixpkgs/nixos/modules/programs/yabar.nix
index 0ec668ada8eb..58ffe555715d 100644
--- a/nixpkgs/nixos/modules/programs/yabar.nix
+++ b/nixpkgs/nixos/modules/programs/yabar.nix
@@ -41,7 +41,7 @@ let
 in
   {
     options.programs.yabar = {
-      enable = mkEnableOption "yabar";
+      enable = mkEnableOption (lib.mdDoc "yabar");
 
       package = mkOption {
         default = pkgs.yabar-unstable;
@@ -62,7 +62,7 @@ in
           to use `yabar-unstable'.
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           The package which contains the `yabar` binary.
 
           Nixpkgs provides the `yabar` and `yabar-unstable`
diff --git a/nixpkgs/nixos/modules/programs/zmap.nix b/nixpkgs/nixos/modules/programs/zmap.nix
index 2e27fce4d7c6..056f78883061 100644
--- a/nixpkgs/nixos/modules/programs/zmap.nix
+++ b/nixpkgs/nixos/modules/programs/zmap.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.zmap;
 in {
   options.programs.zmap = {
-    enable = mkEnableOption "ZMap";
+    enable = mkEnableOption (lib.mdDoc "ZMap");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.md b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.md
new file mode 100644
index 000000000000..73d425244ce7
--- /dev/null
+++ b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.md
@@ -0,0 +1,109 @@
+# Oh my ZSH {#module-programs-zsh-ohmyzsh}
+
+[`oh-my-zsh`](https://ohmyz.sh/) is a framework to manage your [ZSH](https://www.zsh.org/)
+configuration including completion scripts for several CLI tools or custom
+prompt themes.
+
+## Basic usage {#module-programs-oh-my-zsh-usage}
+
+The module uses the `oh-my-zsh` package with all available
+features. The initial setup using Nix expressions is fairly similar to the
+configuration format of `oh-my-zsh`.
+```
+{
+  programs.zsh.ohMyZsh = {
+    enable = true;
+    plugins = [ "git" "python" "man" ];
+    theme = "agnoster";
+  };
+}
+```
+For a detailed explanation of these arguments please refer to the
+[`oh-my-zsh` docs](https://github.com/robbyrussell/oh-my-zsh/wiki).
+
+The expression generates the needed configuration and writes it into your
+`/etc/zshrc`.
+
+## Custom additions {#module-programs-oh-my-zsh-additions}
+
+Sometimes third-party or custom scripts such as a modified theme may be
+needed. `oh-my-zsh` provides the
+[`ZSH_CUSTOM`](https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals)
+environment variable for this which points to a directory with additional
+scripts.
+
+The module can do this as well:
+```
+{
+  programs.zsh.ohMyZsh.custom = "~/path/to/custom/scripts";
+}
+```
+
+## Custom environments {#module-programs-oh-my-zsh-environments}
+
+There are several extensions for `oh-my-zsh` packaged in
+`nixpkgs`. One of them is
+[nix-zsh-completions](https://github.com/spwhitt/nix-zsh-completions)
+which bundles completion scripts and a plugin for `oh-my-zsh`.
+
+Rather than using a single mutable path for `ZSH_CUSTOM`,
+it's also possible to generate this path from a list of Nix packages:
+```
+{ pkgs, ... }:
+{
+  programs.zsh.ohMyZsh.customPkgs = [
+    pkgs.nix-zsh-completions
+    # and even more...
+  ];
+}
+```
+Internally a single store path will be created using
+`buildEnv`. Please refer to the docs of
+[`buildEnv`](https://nixos.org/nixpkgs/manual/#sec-building-environment)
+for further reference.
+
+*Please keep in mind that this is not compatible with
+`programs.zsh.ohMyZsh.custom` as it requires an immutable
+store path while `custom` shall remain mutable! An
+evaluation failure will be thrown if both `custom` and
+`customPkgs` are set.*
+
+## Package your own customizations {#module-programs-oh-my-zsh-packaging-customizations}
+
+If third-party customizations (e.g. new themes) are supposed to be added to
+`oh-my-zsh` there are several pitfalls to keep in mind:
+
+  - To comply with the default structure of `ZSH` the entire
+    output needs to be written to `$out/share/zsh.`
+
+  - Completion scripts are supposed to be stored at
+    `$out/share/zsh/site-functions`. This directory is part of the
+    [`fpath`](http://zsh.sourceforge.net/Doc/Release/Functions.html)
+    and the package should be compatible with pure `ZSH`
+    setups. The module will automatically link the contents of
+    `site-functions` to completions directory in the proper
+    store path.
+
+  - The `plugins` directory needs the structure
+    `pluginname/pluginname.plugin.zsh` as structured in the
+    [upstream repo.](https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins)
+
+A derivation for `oh-my-zsh` may look like this:
+```
+{ stdenv, fetchFromGitHub }:
+
+stdenv.mkDerivation rec {
+  name = "exemplary-zsh-customization-${version}";
+  version = "1.0.0";
+  src = fetchFromGitHub {
+    # path to the upstream repository
+  };
+
+  dontBuild = true;
+  installPhase = ''
+    mkdir -p $out/share/zsh/site-functions
+    cp {themes,plugins} $out/share/zsh
+    cp completions $out/share/zsh/site-functions
+  '';
+}
+```
diff --git a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix
index b253b803edcc..83eee1c88b3c 100644
--- a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -49,7 +49,7 @@ in
         package = mkOption {
           default = pkgs.oh-my-zsh;
           defaultText = literalExpression "pkgs.oh-my-zsh";
-          description = ''
+          description = lib.mdDoc ''
             Package to install for `oh-my-zsh` usage.
           '';
 
@@ -67,7 +67,7 @@ in
         custom = mkOption {
           default = null;
           type = with types; nullOr str;
-          description = ''
+          description = lib.mdDoc ''
             Path to a custom oh-my-zsh package to override config of oh-my-zsh.
             (Can't be used along with `customPkgs`).
           '';
@@ -76,7 +76,7 @@ in
         customPkgs = mkOption {
           default = [];
           type = types.listOf types.package;
-          description = ''
+          description = lib.mdDoc ''
             List of custom packages that should be loaded into `oh-my-zsh`.
           '';
         };
@@ -92,7 +92,7 @@ in
         cacheDir = mkOption {
           default = "$HOME/.cache/oh-my-zsh";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Cache directory to be used by `oh-my-zsh`.
             Without this option it would default to the read-only nix store.
           '';
@@ -142,5 +142,5 @@ in
 
     };
 
-    meta.doc = ./oh-my-zsh.xml;
+    meta.doc = ./oh-my-zsh.md;
   }
diff --git a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml b/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml
deleted file mode 100644
index 14a7228ad9b0..000000000000
--- a/nixpkgs/nixos/modules/programs/zsh/oh-my-zsh.xml
+++ /dev/null
@@ -1,155 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-programs-zsh-ohmyzsh">
- <title>Oh my ZSH</title>
- <para>
-  <literal><link xlink:href="https://ohmyz.sh/">oh-my-zsh</link></literal> is a
-  framework to manage your <link xlink:href="https://www.zsh.org/">ZSH</link>
-  configuration including completion scripts for several CLI tools or custom
-  prompt themes.
- </para>
- <section xml:id="module-programs-oh-my-zsh-usage">
-  <title>Basic usage</title>
-
-  <para>
-   The module uses the <literal>oh-my-zsh</literal> package with all available
-   features. The initial setup using Nix expressions is fairly similar to the
-   configuration format of <literal>oh-my-zsh</literal>.
-<programlisting>
-{
-  programs.zsh.ohMyZsh = {
-    enable = true;
-    plugins = [ "git" "python" "man" ];
-    theme = "agnoster";
-  };
-}
-</programlisting>
-   For a detailed explanation of these arguments please refer to the
-   <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal>
-   docs</link>.
-  </para>
-
-  <para>
-   The expression generates the needed configuration and writes it into your
-   <literal>/etc/zshrc</literal>.
-  </para>
- </section>
- <section xml:id="module-programs-oh-my-zsh-additions">
-  <title>Custom additions</title>
-
-  <para>
-   Sometimes third-party or custom scripts such as a modified theme may be
-   needed. <literal>oh-my-zsh</literal> provides the
-   <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link>
-   environment variable for this which points to a directory with additional
-   scripts.
-  </para>
-
-  <para>
-   The module can do this as well:
-<programlisting>
-{
-  programs.zsh.ohMyZsh.custom = "~/path/to/custom/scripts";
-}
-</programlisting>
-  </para>
- </section>
- <section xml:id="module-programs-oh-my-zsh-environments">
-  <title>Custom environments</title>
-
-  <para>
-   There are several extensions for <literal>oh-my-zsh</literal> packaged in
-   <literal>nixpkgs</literal>. One of them is
-   <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link>
-   which bundles completion scripts and a plugin for
-   <literal>oh-my-zsh</literal>.
-  </para>
-
-  <para>
-   Rather than using a single mutable path for <literal>ZSH_CUSTOM</literal>,
-   it's also possible to generate this path from a list of Nix packages:
-<programlisting>
-{ pkgs, ... }:
-{
-  programs.zsh.ohMyZsh.customPkgs = [
-    pkgs.nix-zsh-completions
-    # and even more...
-  ];
-}
-</programlisting>
-   Internally a single store path will be created using
-   <literal>buildEnv</literal>. Please refer to the docs of
-   <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link>
-   for further reference.
-  </para>
-
-  <para>
-   <emphasis>Please keep in mind that this is not compatible with
-   <literal>programs.zsh.ohMyZsh.custom</literal> as it requires an immutable
-   store path while <literal>custom</literal> shall remain mutable! An
-   evaluation failure will be thrown if both <literal>custom</literal> and
-   <literal>customPkgs</literal> are set.</emphasis>
-  </para>
- </section>
- <section xml:id="module-programs-oh-my-zsh-packaging-customizations">
-  <title>Package your own customizations</title>
-
-  <para>
-   If third-party customizations (e.g. new themes) are supposed to be added to
-   <literal>oh-my-zsh</literal> there are several pitfalls to keep in mind:
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     To comply with the default structure of <literal>ZSH</literal> the entire
-     output needs to be written to <literal>$out/share/zsh.</literal>
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Completion scripts are supposed to be stored at
-     <literal>$out/share/zsh/site-functions</literal>. This directory is part
-     of the
-     <literal><link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html">fpath</link></literal>
-     and the package should be compatible with pure <literal>ZSH</literal>
-     setups. The module will automatically link the contents of
-     <literal>site-functions</literal> to completions directory in the proper
-     store path.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     The <literal>plugins</literal> directory needs the structure
-     <literal>pluginname/pluginname.plugin.zsh</literal> as structured in the
-     <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream
-     repo.</link>
-    </para>
-   </listitem>
-  </itemizedlist>
-
-  <para>
-   A derivation for <literal>oh-my-zsh</literal> may look like this:
-<programlisting>
-{ stdenv, fetchFromGitHub }:
-
-stdenv.mkDerivation rec {
-  name = "exemplary-zsh-customization-${version}";
-  version = "1.0.0";
-  src = fetchFromGitHub {
-    # path to the upstream repository
-  };
-
-  dontBuild = true;
-  installPhase = ''
-    mkdir -p $out/share/zsh/site-functions
-    cp {themes,plugins} $out/share/zsh
-    cp completions $out/share/zsh/site-functions
-  '';
-}
-</programlisting>
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix
index 62f497a45dd0..be93c96b2bc8 100644
--- a/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-autoenv.nix
@@ -7,11 +7,11 @@ let
 in {
   options = {
     programs.zsh.zsh-autoenv = {
-      enable = mkEnableOption "zsh-autoenv";
+      enable = mkEnableOption (lib.mdDoc "zsh-autoenv");
       package = mkOption {
         default = pkgs.zsh-autoenv;
         defaultText = literalExpression "pkgs.zsh-autoenv";
-        description = ''
+        description = lib.mdDoc ''
           Package to install for `zsh-autoenv` usage.
         '';
 
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
index b6c36a082e77..d3a9c372e89b 100644
--- a/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -12,7 +12,7 @@ in
 
   options.programs.zsh.autosuggestions = {
 
-    enable = mkEnableOption "zsh-autosuggestions";
+    enable = mkEnableOption (lib.mdDoc "zsh-autosuggestions");
 
     highlightStyle = mkOption {
       type = types.str;
@@ -24,7 +24,7 @@ in
     strategy = mkOption {
       type = types.listOf (types.enum [ "history" "completion" "match_prev_cmd" ]);
       default = [ "history" ];
-      description = ''
+      description = lib.mdDoc ''
         `ZSH_AUTOSUGGEST_STRATEGY` is an array that specifies how suggestions should be generated.
         The strategies in the array are tried successively until a suggestion is found.
         There are currently three built-in strategies to choose from:
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index 37e1c2ebc384..cec4be1cb01e 100644
--- a/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -15,7 +15,7 @@ in
 
   options = {
     programs.zsh.syntaxHighlighting = {
-      enable = mkEnableOption "zsh-syntax-highlighting";
+      enable = mkEnableOption (lib.mdDoc "zsh-syntax-highlighting");
 
       highlighters = mkOption {
         default = [ "main" ];
@@ -26,6 +26,7 @@ in
           "brackets"
           "pattern"
           "cursor"
+          "regexp"
           "root"
           "line"
         ]));
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh.nix b/nixpkgs/nixos/modules/programs/zsh/zsh.nix
index 0c59d20fee46..6bb21cb3ef66 100644
--- a/nixpkgs/nixos/modules/programs/zsh/zsh.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh.nix
@@ -12,7 +12,7 @@ let
   opt = options.programs.zsh;
 
   zshAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+    mapAttrsFlatten (k: v: "alias -- ${k}=${escapeShellArg v}")
       (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
@@ -173,10 +173,10 @@ in
         # This file is read for all shells.
 
         # Only execute this file once per shell.
-        if [ -n "$__ETC_ZSHENV_SOURCED" ]; then return; fi
+        if [ -n "''${__ETC_ZSHENV_SOURCED-}" ]; then return; fi
         __ETC_ZSHENV_SOURCED=1
 
-        if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+        if [ -z "''${__NIXOS_SET_ENVIRONMENT_DONE-}" ]; then
             . ${config.system.build.setEnvironment}
         fi
 
@@ -184,7 +184,7 @@ in
 
         # Tell zsh how to find installed completions.
         for p in ''${(z)NIX_PROFILES}; do
-            fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions)
+            fpath=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions $fpath)
         done
 
         # Setup custom shell init stuff.
@@ -206,7 +206,7 @@ in
         ${zshStartupNotes}
 
         # Only execute this file once per shell.
-        if [ -n "$__ETC_ZPROFILE_SOURCED" ]; then return; fi
+        if [ -n "''${__ETC_ZPROFILE_SOURCED-}" ]; then return; fi
         __ETC_ZPROFILE_SOURCED=1
 
         # Setup custom login shell init stuff.
@@ -236,6 +236,9 @@ in
           setopt ${concatStringsSep " " cfg.setOptions}
         ''}
 
+        # Alternative method of determining short and full hostname.
+        HOST=${config.networking.fqdnOrHostName}
+
         # Setup command line history.
         # Don't export these, otherwise other shells (bash) will try to use same HISTFILE.
         SAVEHIST=${toString cfg.histSize}
diff --git a/nixpkgs/nixos/modules/rename.nix b/nixpkgs/nixos/modules/rename.nix
index f86aa2fa5c1b..c8e540932efa 100644
--- a/nixpkgs/nixos/modules/rename.nix
+++ b/nixpkgs/nixos/modules/rename.nix
@@ -1,7 +1,10 @@
 { lib, pkgs, ... }:
 
-with lib;
-
+let
+  inherit (lib)
+    mkAliasOptionModuleMD
+    mkRemovedOptionModule;
+in
 {
   imports = [
     /*
@@ -14,7 +17,7 @@ with lib;
 
     # This alias module can't be where _module.check is defined because it would
     # be added to submodules as well there
-    (mkAliasOptionModule [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ])
+    (mkAliasOptionModuleMD [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ])
 
     # Completely removed modules
     (mkRemovedOptionModule [ "environment" "blcr" "enable" ] "The BLCR module has been removed")
@@ -36,6 +39,7 @@ with lib;
     '')
     (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.")
     (mkRemovedOptionModule [ "networking" "wicd" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "programs" "gnome-documents" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "programs" "tilp2" ] "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"))
@@ -43,12 +47,13 @@ with lib;
         The hidepid module was removed, since the underlying machinery
         is broken when using cgroups-v2.
     '')
+    (mkRemovedOptionModule [ "services" "baget" "enable" ] "The baget module was removed due to the upstream package being unmaintained.")
     (mkRemovedOptionModule [ "services" "beegfs" ] "The BeeGFS module has been removed")
     (mkRemovedOptionModule [ "services" "beegfsEnable" ] "The BeeGFS module has been removed")
     (mkRemovedOptionModule [ "services" "cgmanager" "enable"] "cgmanager was deprecated by lxc and therefore 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" "dd-agent" ] "dd-agent was removed from nixpkgs in favor of the newer datadog-agent.")
     (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "flashpolicyd" ] "The flashpolicyd module has been removed. Adobe Flash Player is deprecated.")
@@ -56,6 +61,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed")
     (mkRemovedOptionModule [ "services" "fprot" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "frab" ] "The frab module has been removed")
+    (mkRemovedOptionModule [ "services" "ihatemoney" ] "The ihatemoney module has been removed for lack of downstream maintainer")
     (mkRemovedOptionModule [ "services" "kippo" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "mailpile" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "marathon" ] "The corresponding package was removed from nixpkgs.")
@@ -104,6 +110,9 @@ with lib;
     (mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
+
+    (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx" ] "The fcitx module has been removed. Please use fcitx5 instead")
 
     # Do NOT add any option renames here, see top of the file
   ];
diff --git a/nixpkgs/nixos/modules/security/acme/default.md b/nixpkgs/nixos/modules/security/acme/default.md
new file mode 100644
index 000000000000..8ff97b55f685
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/acme/default.md
@@ -0,0 +1,354 @@
+# SSL/TLS Certificates with ACME {#module-security-acme}
+
+NixOS supports automatic domain validation & 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
+[lego](https://go-acme.github.io/lego/) is used under
+the hood.
+
+Automatic cert validation and configuration for Apache and Nginx virtual
+hosts is included in NixOS, however if you would like to generate a wildcard
+cert or you are not using a web server you will have to configure DNS
+based validation.
+
+## Prerequisites {#module-security-acme-prerequisites}
+
+To use the ACME module, you must accept the provider's terms of service
+by setting [](#opt-security.acme.acceptTerms)
+to `true`. The Let's Encrypt ToS can be found
+[here](https://letsencrypt.org/repository/).
+
+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
+[](#opt-security.acme.defaults.email)
+and/or on a per-cert basis with
+[](#opt-security.acme.certs._name_.email).
+This address is only used for registration and renewal reminders,
+and cannot be used to administer the certificates in any way.
+
+Alternatively, you can use a different ACME server by changing the
+[](#opt-security.acme.defaults.server) option
+to a provider of your choosing, or just change the server for one cert with
+[](#opt-security.acme.certs._name_.server).
+
+You will need an HTTP server or DNS server for verification. For HTTP,
+the server must have a webroot defined that can serve
+{file}`.well-known/acme-challenge`. This directory must be
+writeable by the user that will run the ACME client. For DNS, you must
+set up credentials with your provider/server for use with lego.
+
+## Using ACME certificates in Nginx {#module-security-acme-nginx}
+
+NixOS supports fetching ACME certificates for you by setting
+`enableACME = true;` 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
+`foo.example.com` the config would look like this:
+
+```
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+services.nginx = {
+  enable = true;
+  virtualHosts = {
+    "foo.example.com" = {
+      forceSSL = true;
+      enableACME = true;
+      # All serverAliases will be added as extra domain names on the certificate.
+      serverAliases = [ "bar.example.com" ];
+      locations."/" = {
+        root = "/var/www";
+      };
+    };
+
+    # We can also add a different vhost and reuse the same certificate
+    # but we have to append extraDomainNames manually beforehand:
+    # security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ];
+    "baz.example.com" = {
+      forceSSL = true;
+      useACMEHost = "foo.example.com";
+      locations."/" = {
+        root = "/var/www";
+      };
+    };
+  };
+}
+```
+
+## Using ACME certificates in Apache/httpd {#module-security-acme-httpd}
+
+Using ACME certificates with Apache virtual hosts is identical
+to using them with Nginx. The attribute names are all the same, just replace
+"nginx" with "httpd" where appropriate.
+
+## Manual configuration of HTTP-01 validation {#module-security-acme-configuring}
+
+First off you will need to set up a virtual host to serve the challenges.
+This example uses a vhost called `certs.example.com`, with
+the intent that you will generate certs for all your vhosts and redirect
+everyone to HTTPS.
+
+```
+security.acme.acceptTerms = true;
+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
+# this is to add the Nginx user to the ACME group.
+users.users.nginx.extraGroups = [ "acme" ];
+
+services.nginx = {
+  enable = true;
+  virtualHosts = {
+    "acmechallenge.example.com" = {
+      # Catchall vhost, will redirect users to HTTPS for all vhosts
+      serverAliases = [ "*.example.com" ];
+      locations."/.well-known/acme-challenge" = {
+        root = "/var/lib/acme/.challenges";
+      };
+      locations."/" = {
+        return = "301 https://$host$request_uri";
+      };
+    };
+  };
+}
+# Alternative config for Apache
+users.users.wwwrun.extraGroups = [ "acme" ];
+services.httpd = {
+  enable = true;
+  virtualHosts = {
+    "acmechallenge.example.com" = {
+      # Catchall vhost, will redirect users to HTTPS for all vhosts
+      serverAliases = [ "*.example.com" ];
+      # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user.
+      # By default, this is the case.
+      documentRoot = "/var/lib/acme/.challenges";
+      extraConfig = ''
+        RewriteEngine On
+        RewriteCond %{HTTPS} off
+        RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC]
+        RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
+      '';
+    };
+  };
+}
+```
+
+Now you need to configure ACME to generate a certificate.
+
+```
+security.acme.certs."foo.example.com" = {
+  webroot = "/var/lib/acme/.challenges";
+  email = "foo@example.com";
+  # Ensure that the web server you use can read the generated certs
+  # Take a look at the group option for the web server you choose.
+  group = "nginx";
+  # Since we have a wildcard vhost to handle port 80,
+  # we can generate certs for anything!
+  # Just make sure your DNS resolves them.
+  extraDomainNames = [ "mail.example.com" ];
+};
+```
+
+The private key {file}`key.pem` and certificate
+{file}`fullchain.pem` will be put into
+{file}`/var/lib/acme/foo.example.com`.
+
+Refer to [](#ch-options) for all available configuration
+options for the [security.acme](#opt-security.acme.certs)
+module.
+
+## Configuring ACME for DNS validation {#module-security-acme-config-dns}
+
+This is useful if you want to generate a wildcard certificate, since
+ACME servers will only hand out wildcard certs over DNS validation.
+There are a number of supported DNS providers and servers you can utilise,
+see the [lego docs](https://go-acme.github.io/lego/dns/)
+for provider/server specific configuration values. For the sake of these
+docs, we will provide a fully self-hosted example using bind.
+
+```
+services.bind = {
+  enable = true;
+  extraConfig = ''
+    include "/var/lib/secrets/dnskeys.conf";
+  '';
+  zones = [
+    rec {
+      name = "example.com";
+      file = "/var/db/bind/${name}";
+      master = true;
+      extraConfig = "allow-update { key rfc2136key.example.com.; };";
+    }
+  ];
+}
+
+# Now we can configure ACME
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+security.acme.certs."example.com" = {
+  domain = "*.example.com";
+  dnsProvider = "rfc2136";
+  credentialsFile = "/var/lib/secrets/certs.secret";
+  # We don't need to wait for propagation since this is a local DNS server
+  dnsPropagationCheck = false;
+};
+```
+
+The {file}`dnskeys.conf` and {file}`certs.secret`
+must be kept secure and thus you should not keep their contents in your
+Nix config. Instead, generate them one time with a systemd service:
+
+```
+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
+    chmod 755 /var/lib/secrets
+    tsig-keygen rfc2136key.example.com > /var/lib/secrets/dnskeys.conf
+    chown named:root /var/lib/secrets/dnskeys.conf
+    chmod 400 /var/lib/secrets/dnskeys.conf
+
+    # extract secret value from the dnskeys.conf
+    while read x y; do if [ "$x" = "secret" ]; then secret="''${y:1:''${#y}-3}"; fi; done < /var/lib/secrets/dnskeys.conf
+
+    cat > /var/lib/secrets/certs.secret << EOF
+    RFC2136_NAMESERVER='127.0.0.1:53'
+    RFC2136_TSIG_ALGORITHM='hmac-sha256.'
+    RFC2136_TSIG_KEY='rfc2136key.example.com'
+    RFC2136_TSIG_SECRET='$secret'
+    EOF
+    chmod 400 /var/lib/secrets/certs.secret
+  '';
+};
+```
+
+Now you're all set to generate certs! You should monitor the first invocation
+by running `systemctl start acme-example.com.service &
+journalctl -fu acme-example.com.service` and watching its log output.
+
+## Using DNS validation with web server virtual hosts {#module-security-acme-config-dns-with-vhosts}
+
+It is possible to use DNS-01 validation with all certificates,
+including those automatically configured via the Nginx/Apache
+[`enableACME`](#opt-services.nginx.virtualHosts._name_.enableACME)
+option. This configuration pattern is fully
+supported and part of the module's test suite for Nginx + Apache.
+
+You must follow the guide above on configuring DNS-01 validation
+first, however instead of setting the options for one certificate
+(e.g. [](#opt-security.acme.certs._name_.dnsProvider))
+you will set them as defaults
+(e.g. [](#opt-security.acme.defaults.dnsProvider)).
+
+```
+# Configure ACME appropriately
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+security.acme.defaults = {
+  dnsProvider = "rfc2136";
+  credentialsFile = "/var/lib/secrets/certs.secret";
+  # We don't need to wait for propagation since this is a local DNS server
+  dnsPropagationCheck = false;
+};
+
+# For each virtual host you would like to use DNS-01 validation with,
+# set acmeRoot = null
+services.nginx = {
+  enable = true;
+  virtualHosts = {
+    "foo.example.com" = {
+      enableACME = true;
+      acmeRoot = null;
+    };
+  };
+}
+```
+
+And that's it! Next time your configuration is rebuilt, or when
+you add a new virtualHost, it will be DNS-01 validated.
+
+## Using ACME with services demanding root owned certificates {#module-security-acme-root-owned}
+
+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
+`acme`), however you can use systemd's
+`LoadCredential` feature to resolve this elegantly.
+Below is an example configuration for OpenSMTPD, but this pattern
+can be applied to any service.
+
+```
+# 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.
+systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"];
+systemd.services.opensmtpd.serviceConfig.LoadCredential = 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
+  '';
+};
+```
+
+## Regenerating certificates {#module-security-acme-regenerate}
+
+Should you need to regenerate a particular certificate in a hurry, such
+as when a vulnerability is found in Let's Encrypt, there is now a convenient
+mechanism for doing so. Running
+`systemctl clean --what=state acme-example.com.service`
+will remove all certificate files and the account data for the given domain,
+allowing you to then `systemctl start acme-example.com.service`
+to generate fresh ones.
+
+## Fixing JWS Verification error {#module-security-acme-fix-jws}
+
+It is possible that your account credentials file may become corrupt and need
+to be regenerated. In this scenario lego will produce the error `JWS verification error`.
+The solution is to simply delete the associated accounts file and
+re-run the affected service(s).
+
+```
+# Find the accounts folder for the certificate
+systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
+export accountdir="$(!!)"
+# Move this folder to some place else
+mv /var/lib/acme/.lego/$accountdir{,.bak}
+# Recreate the folder using systemd-tmpfiles
+systemd-tmpfiles --create
+# Get a new account and reissue certificates
+# Note: Do this for all certs that share the same account email address
+systemctl start acme-example.com.service
+```
diff --git a/nixpkgs/nixos/modules/security/acme/default.nix b/nixpkgs/nixos/modules/security/acme/default.nix
index 1df6d9eba953..8aee71c864d0 100644
--- a/nixpkgs/nixos/modules/security/acme/default.nix
+++ b/nixpkgs/nixos/modules/security/acme/default.nix
@@ -26,8 +26,8 @@ let
     Type = "oneshot";
     User = user;
     Group = mkDefault "acme";
-    UMask = 0022;
-    StateDirectoryMode = 750;
+    UMask = "0022";
+    StateDirectoryMode = "750";
     ProtectSystem = "strict";
     ReadWritePaths = [
       "/var/lib/acme"
@@ -62,9 +62,9 @@ let
     SystemCallArchitectures = "native";
     SystemCallFilter = [
       # 1. allow a reasonable set of syscalls
-      "@system-service"
+      "@system-service @resources"
       # 2. and deny unreasonable ones
-      "~@privileged @resources"
+      "~@privileged"
       # 3. then allow the required subset within denied groups
       "@chown"
     ];
@@ -85,7 +85,7 @@ let
     serviceConfig = commonServiceConfig // {
       StateDirectory = "acme/.minica";
       BindPaths = "/var/lib/acme/.minica:/tmp/ca";
-      UMask = 0077;
+      UMask = "0077";
     };
 
     # Working directory will be /tmp
@@ -190,7 +190,7 @@ let
     );
     renewOpts = escapeShellArgs (
       commonOpts
-      ++ [ "renew" ]
+      ++ [ "renew" "--no-random-sleep" ]
       ++ optionals data.ocspMustStaple [ "--must-staple" ]
       ++ data.extraLegoRenewFlags
     );
@@ -223,9 +223,9 @@ let
         # have many certificates, the renewals are distributed over
         # the course of the day to avoid rate limits.
         AccuracySec = "${toString (_24hSecs / numCerts)}s";
-
         # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/.
         RandomizedDelaySec = "24h";
+        FixedRandomDelay = true;
       };
     };
 
@@ -243,7 +243,7 @@ let
 
       serviceConfig = commonServiceConfig // {
         Group = data.group;
-        UMask = 0027;
+        UMask = "0027";
 
         StateDirectory = "acme/${cert}";
 
@@ -323,8 +323,9 @@ let
             }
           fi
         '');
-      } // optionalAttrs (data.listenHTTP != null && toInt (elemAt (splitString ":" data.listenHTTP) 1) < 1024) {
+      } // optionalAttrs (data.listenHTTP != null && toInt (last (splitString ":" data.listenHTTP)) < 1024) {
         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
       };
 
       # Working directory will be /tmp
@@ -376,7 +377,8 @@ let
 
         # 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
+        # We also need an account key. Avoids #190493
+        if cmp -s domainhash.txt certificates/domainhash.txt && [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a -n "$(find accounts -name '${data.email}.key')" ]; 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.
@@ -457,7 +459,7 @@ let
         '';
       };
 
-      enableDebugLogs = mkEnableOption "debug logging for this certificate" // {
+      enableDebugLogs = mkEnableOption (lib.mdDoc "debug logging for this certificate") // {
         inherit (defaultAndText "enableDebugLogs" true) default defaultText;
       };
 
@@ -485,7 +487,7 @@ let
       };
 
       email = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         inherit (defaultAndText "email" null) default defaultText;
         description = lib.mdDoc ''
           Email address for account creation and correspondence from the CA.
@@ -553,7 +555,7 @@ let
       };
 
       credentialsFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
         inherit (defaultAndText "credentialsFile" null) default defaultText;
         description = lib.mdDoc ''
           Path to an EnvironmentFile for the cert's service containing any required and
@@ -576,13 +578,12 @@ let
       ocspMustStaple = mkOption {
         type = types.bool;
         inherit (defaultAndText "ocspMustStaple" false) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Turns on the OCSP Must-Staple TLS extension.
           Make sure you know what you're doing! See:
-          <itemizedlist>
-            <listitem><para><link xlink:href="https://blog.apnic.net/2019/01/15/is-the-web-ready-for-ocsp-must-staple/"/></para></listitem>
-            <listitem><para><link xlink:href="https://blog.hboeck.de/archives/886-The-Problem-with-OCSP-Stapling-and-Must-Staple-and-why-Certificate-Revocation-is-still-broken.html"/></para></listitem>
-          </itemizedlist>
+
+          - <https://blog.apnic.net/2019/01/15/is-the-web-ready-for-ocsp-must-staple/>
+          - <https://blog.hboeck.de/archives/886-The-Problem-with-OCSP-Stapling-and-Must-Staple-and-why-Certificate-Revocation-is-still-broken.html>
         '';
       };
 
@@ -676,7 +677,7 @@ let
       inheritDefaults = mkOption {
         default = true;
         example = true;
-        description = "Whether to inherit values set in `security.acme.defaults` or not.";
+        description = lib.mdDoc "Whether to inherit values set in `security.acme.defaults` or not.";
         type = lib.types.bool;
       };
     };
@@ -713,7 +714,7 @@ in {
         default = false;
         description = lib.mdDoc ''
           Whether to use the root user when generating certs. This is not recommended
-          for security + compatiblity reasons. If a service requires root owned certificates
+          for security + compatibility 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.
@@ -726,7 +727,7 @@ in {
           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.
+          {option}`security.acme.certs.''${cert}.inheritDefaults` option.
         '';
       };
 
@@ -764,7 +765,7 @@ 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" "directory" ] "ACME Directory is now hardcoded to /var/lib/acme and its permissions 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" "defaults" "validMinDays" ] (config: config.security.acme.validMin / (24 * 3600)))
@@ -780,11 +781,11 @@ in {
 
       # FIXME Most of these custom warnings and filters for security.acme.certs.* are required
       # because using mkRemovedOptionModule/mkChangedOptionModule with attrsets isn't possible.
-      warnings = filter (w: w != "") (mapAttrsToList (cert: data: if data.extraDomains != "_mkMergedOptionModule" then ''
+      warnings = filter (w: w != "") (mapAttrsToList (cert: data: optionalString (data.extraDomains != "_mkMergedOptionModule") ''
         The option definition `security.acme.certs.${cert}.extraDomains` has changed
         to `security.acme.certs.${cert}.extraDomainNames` and is now a list of strings.
         Setting a custom webroot for extra domains is not possible, instead use separate certs.
-      '' else "") cfg.certs);
+      '') cfg.certs);
 
       assertions = let
         certs = attrValues cfg.certs;
@@ -915,6 +916,6 @@ in {
 
   meta = {
     maintainers = lib.teams.acme.members;
-    doc = ./doc.xml;
+    doc = ./default.md;
   };
 }
diff --git a/nixpkgs/nixos/modules/security/acme/doc.xml b/nixpkgs/nixos/modules/security/acme/doc.xml
deleted file mode 100644
index 4817f7a7fc6b..000000000000
--- a/nixpkgs/nixos/modules/security/acme/doc.xml
+++ /dev/null
@@ -1,413 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-security-acme">
- <title>SSL/TLS Certificates with ACME</title>
- <para>
-  NixOS supports automatic domain validation &amp; certificate retrieval and
-  renewal using the ACME protocol. Any provider can be used, but by default
-  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
-  hosts is included in NixOS, however if you would like to generate a wildcard
-  cert or you are not using a web server you will have to configure DNS
-  based validation.
- </para>
- <section xml:id="module-security-acme-prerequisites">
-  <title>Prerequisites</title>
-
-  <para>
-   To use the ACME module, you must accept the provider's terms of service
-   by setting <literal><xref linkend="opt-security.acme.acceptTerms" /></literal>
-   to <literal>true</literal>. The Let's Encrypt ToS can be found
-   <link xlink:href="https://letsencrypt.org/repository/">here</link>.
-  </para>
-
-  <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.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,
-   and cannot be used to administer the certificates in any way.
-  </para>
-
-  <para>
-   Alternatively, you can use a different ACME server by changing the
-   <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>
-
-  <para>
-   You will need an HTTP server or DNS server for verification. For HTTP,
-   the server must have a webroot defined that can serve
-   <filename>.well-known/acme-challenge</filename>. This directory must be
-   writeable by the user that will run the ACME client. For DNS, you must
-   set up credentials with your provider/server for use with lego.
-  </para>
- </section>
- <section xml:id="module-security-acme-nginx">
-  <title>Using ACME certificates in Nginx</title>
-
-  <para>
-   NixOS supports fetching ACME certificates for you by setting
-   <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link>
-   = true;</literal> in a virtualHost config. We first create self-signed
-   placeholder certificates in place of the real ACME certs. The placeholder
-   certs are overwritten when the ACME certs arrive. For
-   <literal>foo.example.com</literal> the config would look like this:
-  </para>
-
-<programlisting>
-<xref linkend="opt-security.acme.acceptTerms" /> = true;
-<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> = {
-    "foo.example.com" = {
-      <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
-      # All serverAliases will be added as <link linkend="opt-security.acme.certs._name_.extraDomainNames">extra domain names</link> on the certificate.
-      <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "bar.example.com" ];
-      locations."/" = {
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www";
-      };
-    };
-
-    # We can also add a different vhost and reuse the same certificate
-    # but we have to append extraDomainNames manually beforehand:
-    # <link linkend="opt-security.acme.certs._name_.extraDomainNames">security.acme.certs."foo.example.com".extraDomainNames</link> = [ "baz.example.com" ];
-    "baz.example.com" = {
-      <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-      <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">useACMEHost</link> = "foo.example.com";
-      locations."/" = {
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www";
-      };
-    };
-  };
-}
-</programlisting>
- </section>
- <section xml:id="module-security-acme-httpd">
-  <title>Using ACME certificates in Apache/httpd</title>
-
-  <para>
-   Using ACME certificates with Apache virtual hosts is identical
-   to using them with Nginx. The attribute names are all the same, just replace
-   "nginx" with "httpd" where appropriate.
-  </para>
- </section>
- <section xml:id="module-security-acme-configuring">
-  <title>Manual configuration of HTTP-01 validation</title>
-
-  <para>
-   First off you will need to set up a virtual host to serve the challenges.
-   This example uses a vhost called <literal>certs.example.com</literal>, with
-   the intent that you will generate certs for all your vhosts and redirect
-   everyone to HTTPS.
-  </para>
-
-<programlisting>
-<xref linkend="opt-security.acme.acceptTerms" /> = true;
-<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
-# this is to add the Nginx user to the ACME group.
-<link linkend="opt-users.users._name_.extraGroups">users.users.nginx.extraGroups</link> = [ "acme" ];
-
-services.nginx = {
-  <link linkend="opt-services.nginx.enable">enable</link> = true;
-  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
-    "acmechallenge.example.com" = {
-      # Catchall vhost, will redirect users to HTTPS for all vhosts
-      <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ];
-      locations."/.well-known/acme-challenge" = {
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/lib/acme/.challenges";
-      };
-      locations."/" = {
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.return">return</link> = "301 https://$host$request_uri";
-      };
-    };
-  };
-}
-# Alternative config for Apache
-<link linkend="opt-users.users._name_.extraGroups">users.users.wwwrun.extraGroups</link> = [ "acme" ];
-services.httpd = {
-  <link linkend="opt-services.httpd.enable">enable = true;</link>
-  <link linkend="opt-services.httpd.virtualHosts">virtualHosts</link> = {
-    "acmechallenge.example.com" = {
-      # Catchall vhost, will redirect users to HTTPS for all vhosts
-      <link linkend="opt-services.httpd.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ];
-      # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user.
-      # By default, this is the case.
-      <link linkend="opt-services.httpd.virtualHosts._name_.documentRoot">documentRoot</link> = "/var/lib/acme/.challenges";
-      <link linkend="opt-services.httpd.virtualHosts._name_.extraConfig">extraConfig</link> = ''
-        RewriteEngine On
-        RewriteCond %{HTTPS} off
-        RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC]
-        RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
-      '';
-    };
-  };
-}
-</programlisting>
-
-  <para>
-   Now you need to configure ACME to generate a certificate.
-  </para>
-
-<programlisting>
-<xref linkend="opt-security.acme.certs"/>."foo.example.com" = {
-  <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/lib/acme/.challenges";
-  <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com";
-  # Ensure that the web server you use can read the generated certs
-  # Take a look at the <link linkend="opt-services.nginx.group">group</link> option for the web server you choose.
-  <link linkend="opt-security.acme.certs._name_.group">group</link> = "nginx";
-  # Since we have a wildcard vhost to handle port 80,
-  # we can generate certs for anything!
-  # Just make sure your DNS resolves them.
-  <link linkend="opt-security.acme.certs._name_.extraDomainNames">extraDomainNames</link> = [ "mail.example.com" ];
-};
-</programlisting>
-
-  <para>
-   The private key <filename>key.pem</filename> and certificate
-   <filename>fullchain.pem</filename> will be put into
-   <filename>/var/lib/acme/foo.example.com</filename>.
-  </para>
-
-  <para>
-   Refer to <xref linkend="ch-options" /> for all available configuration
-   options for the <link linkend="opt-security.acme.certs">security.acme</link>
-   module.
-  </para>
- </section>
- <section xml:id="module-security-acme-config-dns">
-  <title>Configuring ACME for DNS validation</title>
-
-  <para>
-   This is useful if you want to generate a wildcard certificate, since
-   ACME servers will only hand out wildcard certs over DNS validation.
-   There are a number of supported DNS providers and servers you can utilise,
-   see the <link xlink:href="https://go-acme.github.io/lego/dns/">lego docs</link>
-   for provider/server specific configuration values. For the sake of these
-   docs, we will provide a fully self-hosted example using bind.
-  </para>
-
-<programlisting>
-services.bind = {
-  <link linkend="opt-services.bind.enable">enable</link> = true;
-  <link linkend="opt-services.bind.extraConfig">extraConfig</link> = ''
-    include "/var/lib/secrets/dnskeys.conf";
-  '';
-  <link linkend="opt-services.bind.zones">zones</link> = [
-    rec {
-      name = "example.com";
-      file = "/var/db/bind/${name}";
-      master = true;
-      extraConfig = "allow-update { key rfc2136key.example.com.; };";
-    }
-  ];
-}
-
-# Now we can configure ACME
-<xref linkend="opt-security.acme.acceptTerms" /> = true;
-<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";
-  <link linkend="opt-security.acme.certs._name_.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.certs._name_.dnsPropagationCheck">dnsPropagationCheck</link> = false;
-};
-</programlisting>
-
-  <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 a systemd service:
-  </para>
-
-<programlisting>
-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 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>
-
-  <para>
-   Should you need to regenerate a particular certificate in a hurry, such
-   as when a vulnerability is found in Let's Encrypt, there is now a convenient
-   mechanism for doing so. Running
-   <literal>systemctl clean --what=state acme-example.com.service</literal>
-   will remove all certificate files and the account data for the given domain,
-   allowing you to then <literal>systemctl start acme-example.com.service</literal>
-   to generate fresh ones.
-  </para>
- </section>
- <section xml:id="module-security-acme-fix-jws">
-  <title>Fixing JWS Verification error</title>
-
-  <para>
-   It is possible that your account credentials file may become corrupt and need
-   to be regenerated. In this scenario lego will produce the error <literal>JWS verification error</literal>.
-   The solution is to simply delete the associated accounts file and
-   re-run the affected service(s).
-  </para>
-
-<programlisting>
-# Find the accounts folder for the certificate
-systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
-export accountdir="$(!!)"
-# Move this folder to some place else
-mv /var/lib/acme/.lego/$accountdir{,.bak}
-# Recreate the folder using systemd-tmpfiles
-systemd-tmpfiles --create
-# Get a new account and reissue certificates
-# Note: Do this for all certs that share the same account email address
-systemctl start acme-example.com.service
-</programlisting>
-
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/security/apparmor.nix b/nixpkgs/nixos/modules/security/apparmor.nix
index c4eca4532545..24b48338ed77 100644
--- a/nixpkgs/nixos/modules/security/apparmor.nix
+++ b/nixpkgs/nixos/modules/security/apparmor.nix
@@ -7,7 +7,7 @@ let
   inherit (lib) types;
   inherit (config.environment) etc;
   cfg = config.security.apparmor;
-  mkDisableOption = name: mkEnableOption name // {
+  mkDisableOption = name: mkEnableOption (lib.mdDoc name) // {
     default = true;
     example = false;
   };
@@ -24,7 +24,7 @@ in
 
   options = {
     security.apparmor = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         the AppArmor Mandatory Access Control system.
 
         If you're enabling this module on a running system,
@@ -38,9 +38,9 @@ in
         introducing for the first time an AppArmor profile for the executable
         of a running process.
 
-        Enable <xref linkend="opt-security.apparmor.killUnconfinedConfinables"/>
+        Enable [](#opt-security.apparmor.killUnconfinedConfinables)
         if you want this service to do such killing
-        by sending a <literal>SIGTERM</literal> to those running processes'';
+        by sending a `SIGTERM` to those running processes'');
       policies = mkOption {
         description = lib.mdDoc ''
           AppArmor policies.
@@ -72,23 +72,23 @@ in
         default = [];
         description = lib.mdDoc "List of packages to be added to AppArmor's include path";
       };
-      enableCache = mkEnableOption ''
+      enableCache = mkEnableOption (lib.mdDoc ''
         caching of AppArmor policies
-        in <literal>/var/cache/apparmor/</literal>.
+        in `/var/cache/apparmor/`.
 
         Beware that AppArmor policies almost always contain Nix store paths,
         and thus produce at each change of these paths
-        a new cached version accumulating in the cache'';
-      killUnconfinedConfinables = mkEnableOption ''
+        a new cached version accumulating in the cache'');
+      killUnconfinedConfinables = mkEnableOption (lib.mdDoc ''
         killing of processes which have an AppArmor profile enabled
-        (in <xref linkend="opt-security.apparmor.policies"/>)
+        (in [](#opt-security.apparmor.policies))
         but are not confined (because AppArmor can only confine new processes).
 
-        This is only sending a gracious <literal>SIGTERM</literal> signal to the processes,
-        not a <literal>SIGKILL</literal>.
+        This is only sending a gracious `SIGTERM` signal to the processes,
+        not a `SIGKILL`.
 
         Beware that due to a current limitation of AppArmor,
-        only profiles with exact paths (and no name) can enable such kills'';
+        only profiles with exact paths (and no name) can enable such kills'');
     };
   };
 
@@ -202,7 +202,7 @@ in
           # (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory).
           # Note that this does not remove profiles dynamically generated by libvirt.
           [ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ] ++
-          # Optionaly kill the processes which are unconfined but now have a profile loaded
+          # Optionally kill the processes which are unconfined but now have a profile loaded
           # (because AppArmor can only start to confine new processes).
           optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
         ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown";
diff --git a/nixpkgs/nixos/modules/security/apparmor/includes.nix b/nixpkgs/nixos/modules/security/apparmor/includes.nix
index f290e95a296d..adfca04426ca 100644
--- a/nixpkgs/nixos/modules/security/apparmor/includes.nix
+++ b/nixpkgs/nixos/modules/security/apparmor/includes.nix
@@ -22,7 +22,7 @@ in
 # some may even be completely useless.
 config.security.apparmor.includes = {
   # This one is included by <tunables/global>
-  # which is usualy included before any profile.
+  # which is usually included before any profile.
   "abstractions/tunables/alias" = ''
     alias /bin -> /run/current-system/sw/bin,
     alias /lib/modules -> /run/current-system/kernel/lib/modules,
diff --git a/nixpkgs/nixos/modules/security/audit.nix b/nixpkgs/nixos/modules/security/audit.nix
index 06b4766c8f5a..afc7dd13039d 100644
--- a/nixpkgs/nixos/modules/security/audit.nix
+++ b/nixpkgs/nixos/modules/security/audit.nix
@@ -57,7 +57,7 @@ in {
         type        = types.enum [ false true "lock" ];
         default     = false;
         description = lib.mdDoc ''
-          Whether to enable the Linux audit system. The special `lock' value can be used to
+          Whether to enable the Linux audit system. The special `lock` value can be used to
           enable auditing and prevent disabling it until a restart. Be careful about locking
           this, as it will prevent you from changing your audit configuration until you
           restart. If possible, test your configuration using build-vm beforehand.
diff --git a/nixpkgs/nixos/modules/security/auditd.nix b/nixpkgs/nixos/modules/security/auditd.nix
index 9d26cfbcfb10..db4b2701ee2e 100644
--- a/nixpkgs/nixos/modules/security/auditd.nix
+++ b/nixpkgs/nixos/modules/security/auditd.nix
@@ -3,7 +3,7 @@
 with lib;
 
 {
-  options.security.auditd.enable = mkEnableOption "the Linux Audit daemon";
+  options.security.auditd.enable = mkEnableOption (lib.mdDoc "the Linux Audit daemon");
 
   config = mkIf config.security.auditd.enable {
     boot.kernelParams = [ "audit=1" ];
diff --git a/nixpkgs/nixos/modules/security/dhparams.nix b/nixpkgs/nixos/modules/security/dhparams.nix
index b3fce7e83aff..9fed7e012b1e 100644
--- a/nixpkgs/nixos/modules/security/dhparams.nix
+++ b/nixpkgs/nixos/modules/security/dhparams.nix
@@ -56,45 +56,53 @@ in {
         in attrsOf (coercedTo int coerce (submodule paramsSubmodule));
         default = {};
         example = lib.literalExpression "{ nginx.bits = 3072; }";
-        description = ''
+        description = lib.mdDoc ''
           Diffie-Hellman parameters to generate.
 
           The value is the size (in bits) of the DH params to generate. The
           generated DH params path can be found in
-          <literal>config.security.dhparams.params.«name».path</literal>.
+          `config.security.dhparams.params.«name».path`.
 
-          <note><para>The name of the DH params is taken as being the name of
+          ::: {.note}
+          The name of the DH params is taken as being the name of
           the service it serves and the params will be generated before the
-          said service is started.</para></note>
+          said service is started.
+          :::
 
-          <warning><para>If you are removing all dhparams from this list, you
-          have to leave <option>security.dhparams.enable</option> for at
+          ::: {.warning}
+          If you are removing all dhparams from this list, you
+          have to leave {option}`security.dhparams.enable` for at
           least one activation in order to have them be cleaned up. This also
           means if you rollback to a version without any dhparams the
           existing ones won't be cleaned up. Of course this only applies if
-          <option>security.dhparams.stateful</option> is
-          <literal>true</literal>.</para></warning>
+          {option}`security.dhparams.stateful` is
+          `true`.
+          :::
 
-          <note><title>For module implementers:</title><para>It's recommended
+          ::: {.note}
+          **For module implementers:** It's recommended
           to not set a specific bit size here, so that users can easily
           override this by setting
-          <option>security.dhparams.defaultBitSize</option>.</para></note>
+          {option}`security.dhparams.defaultBitSize`.
+          :::
         '';
       };
 
       stateful = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether generation of Diffie-Hellman parameters should be stateful or
           not. If this is enabled, PEM-encoded files for Diffie-Hellman
           parameters are placed in the directory specified by
-          <option>security.dhparams.path</option>. Otherwise the files are
+          {option}`security.dhparams.path`. Otherwise the files are
           created within the Nix store.
 
-          <note><para>If this is <literal>false</literal> the resulting store
+          ::: {.note}
+          If this is `false` the resulting store
           path will be non-deterministic and will be rebuilt every time the
-          <package>openssl</package> package changes.</para></note>
+          `openssl` package changes.
+          :::
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/security/doas.nix b/nixpkgs/nixos/modules/security/doas.nix
index 4d15ed9a8025..115ca33efb5c 100644
--- a/nixpkgs/nixos/modules/security/doas.nix
+++ b/nixpkgs/nixos/modules/security/doas.nix
@@ -19,7 +19,7 @@ let
   ];
 
   mkArgs = rule:
-    if (isNull rule.args) then ""
+    if (rule.args == null) then ""
     else if (length rule.args == 0) then "args"
     else "args ${concatStringsSep " " rule.args}";
 
@@ -27,9 +27,9 @@ let
     let
       opts = mkOpts rule;
 
-      as = optionalString (!isNull rule.runAs) "as ${rule.runAs}";
+      as = optionalString (rule.runAs != null) "as ${rule.runAs}";
 
-      cmd = optionalString (!isNull rule.cmd) "cmd ${rule.cmd}";
+      cmd = optionalString (rule.cmd != null) "cmd ${rule.cmd}";
 
       args = mkArgs rule;
     in
@@ -75,7 +75,9 @@ in
         {file}`/etc/doas.conf` file. More specific rules should
         come after more general ones in order to yield the expected behavior.
         You can use `mkBefore` and/or `mkAfter` to ensure
-        this is the case when configuration options are merged.
+        this is the case when configuration options are merged. Be aware that
+        this option cannot be used to override the behaviour allowing
+        passwordless operation for root.
       '';
       example = literalExpression ''
         [
@@ -224,7 +226,9 @@ in
       type = with types; lines;
       default = "";
       description = lib.mdDoc ''
-        Extra configuration text appended to {file}`doas.conf`.
+        Extra configuration text appended to {file}`doas.conf`. Be aware that
+        this option cannot be used to override the behaviour allowing
+        passwordless operation for root.
       '';
     };
   };
@@ -266,14 +270,14 @@ in
             # completely replace the contents of this file, use
             # `environment.etc."doas.conf"`.
 
-            # "root" is allowed to do anything.
-            permit nopass keepenv root
-
             # extraRules
             ${concatStringsSep "\n" (lists.flatten (map mkRule cfg.extraRules))}
 
             # extraConfig
             ${cfg.extraConfig}
+
+            # "root" is allowed to do anything.
+            permit nopass keepenv root
           '';
           preferLocalBuild = true;
         }
diff --git a/nixpkgs/nixos/modules/security/ipa.nix b/nixpkgs/nixos/modules/security/ipa.nix
new file mode 100644
index 000000000000..7075be95040e
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/ipa.nix
@@ -0,0 +1,258 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.security.ipa;
+  pyBool = x:
+    if x
+    then "True"
+    else "False";
+
+  ldapConf = pkgs.writeText "ldap.conf" ''
+    # Turning this off breaks GSSAPI used with krb5 when rdns = false
+    SASL_NOCANON    on
+
+    URI ldaps://${cfg.server}
+    BASE ${cfg.basedn}
+    TLS_CACERT /etc/ipa/ca.crt
+  '';
+  nssDb =
+    pkgs.runCommand "ipa-nssdb"
+    {
+      nativeBuildInputs = [pkgs.nss.tools];
+    } ''
+      mkdir -p $out
+      certutil -d $out -N --empty-password
+      certutil -d $out -A --empty-password -n "${cfg.realm} IPA CA" -t CT,C,C -i ${cfg.certificate}
+    '';
+in {
+  options = {
+    security.ipa = {
+      enable = mkEnableOption (lib.mdDoc "FreeIPA domain integration");
+
+      certificate = mkOption {
+        type = types.package;
+        description = lib.mdDoc ''
+          IPA server CA certificate.
+
+          Use `nix-prefetch-url http://$server/ipa/config/ca.crt` to
+          obtain the file and the hash.
+        '';
+        example = literalExpression ''
+          pkgs.fetchurl {
+            url = http://ipa.example.com/ipa/config/ca.crt;
+            sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+          };
+        '';
+      };
+
+      domain = mkOption {
+        type = types.str;
+        example = "example.com";
+        description = lib.mdDoc "Domain of the IPA server.";
+      };
+
+      realm = mkOption {
+        type = types.str;
+        example = "EXAMPLE.COM";
+        description = lib.mdDoc "Kerberos realm.";
+      };
+
+      server = mkOption {
+        type = types.str;
+        example = "ipa.example.com";
+        description = lib.mdDoc "IPA Server hostname.";
+      };
+
+      basedn = mkOption {
+        type = types.str;
+        example = "dc=example,dc=com";
+        description = lib.mdDoc "Base DN to use when performing LDAP operations.";
+      };
+
+      offlinePasswords = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Whether to store offline passwords when the server is down.";
+      };
+
+      cacheCredentials = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Whether to cache credentials.";
+      };
+
+      ifpAllowedUids = mkOption {
+        type = types.listOf types.string;
+        default = ["root"];
+        description = lib.mdDoc "A list of users allowed to access the ifp dbus interface.";
+      };
+
+      dyndns = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc "Whether to enable FreeIPA automatic hostname updates.";
+        };
+
+        interface = mkOption {
+          type = types.str;
+          example = "eth0";
+          default = "*";
+          description = lib.mdDoc "Network interface to perform hostname updates through.";
+        };
+      };
+
+      chromiumSupport = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Whether to whitelist the FreeIPA domain in Chromium.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !config.krb5.enable;
+        message = "krb5 must be disabled through `krb5.enable` for FreeIPA integration to work.";
+      }
+      {
+        assertion = !config.users.ldap.enable;
+        message = "ldap must be disabled through `users.ldap.enable` for FreeIPA integration to work.";
+      }
+    ];
+
+    environment.systemPackages = with pkgs; [krb5Full freeipa];
+
+    environment.etc = {
+      "ipa/default.conf".text = ''
+        [global]
+        basedn = ${cfg.basedn}
+        realm = ${cfg.realm}
+        domain = ${cfg.domain}
+        server = ${cfg.server}
+        host = ${config.networking.hostName}
+        xmlrpc_uri = https://${cfg.server}/ipa/xml
+        enable_ra = True
+      '';
+
+      "ipa/nssdb".source = nssDb;
+
+      "krb5.conf".text = ''
+        [libdefaults]
+         default_realm = ${cfg.realm}
+         dns_lookup_realm = false
+         dns_lookup_kdc = true
+         rdns = false
+         ticket_lifetime = 24h
+         forwardable = true
+         udp_preference_limit = 0
+
+        [realms]
+         ${cfg.realm} = {
+          kdc = ${cfg.server}:88
+          master_kdc = ${cfg.server}:88
+          admin_server = ${cfg.server}:749
+          default_domain = ${cfg.domain}
+          pkinit_anchors = FILE:/etc/ipa/ca.crt
+        }
+
+        [domain_realm]
+         .${cfg.domain} = ${cfg.realm}
+         ${cfg.domain} = ${cfg.realm}
+         ${cfg.server} = ${cfg.realm}
+
+        [dbmodules]
+          ${cfg.realm} = {
+            db_library = ${pkgs.freeipa}/lib/krb5/plugins/kdb/ipadb.so
+          }
+      '';
+
+      "openldap/ldap.conf".source = ldapConf;
+    };
+
+    environment.etc."chromium/policies/managed/freeipa.json" = mkIf cfg.chromiumSupport {
+      text = ''
+        { "AuthServerWhitelist": "*.${cfg.domain}" }
+      '';
+    };
+
+    system.activationScripts.ipa = stringAfter ["etc"] ''
+      # libcurl requires a hard copy of the certificate
+      if ! ${pkgs.diffutils}/bin/diff ${cfg.certificate} /etc/ipa/ca.crt > /dev/null 2>&1; then
+        rm -f /etc/ipa/ca.crt
+        cp ${cfg.certificate} /etc/ipa/ca.crt
+      fi
+
+      if [ ! -f /etc/krb5.keytab ]; then
+        cat <<EOF
+
+          In order to complete FreeIPA integration, please join the domain by completing the following steps:
+          1. Authenticate as an IPA user authorized to join new hosts, e.g. kinit admin@${cfg.realm}
+          2. Join the domain and obtain the keytab file: ipa-join
+          3. Install the keytab file: sudo install -m 600 krb5.keytab /etc/
+          4. Restart sssd systemd service: sudo systemctl restart sssd
+
+      EOF
+      fi
+    '';
+
+    services.sssd.config = ''
+      [domain/${cfg.domain}]
+      id_provider = ipa
+      auth_provider = ipa
+      access_provider = ipa
+      chpass_provider = ipa
+
+      ipa_domain = ${cfg.domain}
+      ipa_server = _srv_, ${cfg.server}
+      ipa_hostname = ${config.networking.hostName}.${cfg.domain}
+
+      cache_credentials = ${pyBool cfg.cacheCredentials}
+      krb5_store_password_if_offline = ${pyBool cfg.offlinePasswords}
+      ${optionalString ((toLower cfg.domain) != (toLower cfg.realm))
+        "krb5_realm = ${cfg.realm}"}
+
+      dyndns_update = ${pyBool cfg.dyndns.enable}
+      dyndns_iface = ${cfg.dyndns.interface}
+
+      ldap_tls_cacert = /etc/ipa/ca.crt
+      ldap_user_extra_attrs = mail:mail, sn:sn, givenname:givenname, telephoneNumber:telephoneNumber, lock:nsaccountlock
+
+      [sssd]
+      debug_level = 65510
+      services = nss, sudo, pam, ssh, ifp
+      domains = ${cfg.domain}
+
+      [nss]
+      homedir_substring = /home
+
+      [pam]
+      pam_pwd_expiration_warning = 3
+      pam_verbosity = 3
+
+      [sudo]
+      debug_level = 65510
+
+      [autofs]
+
+      [ssh]
+
+      [pac]
+
+      [ifp]
+      user_attributes = +mail, +telephoneNumber, +givenname, +sn, +lock
+      allowed_uids = ${concatStringsSep ", " cfg.ifpAllowedUids}
+    '';
+
+    services.ntp.servers = singleton cfg.server;
+    services.sssd.enable = true;
+    services.ntp.enable = true;
+
+    security.pki.certificateFiles = singleton cfg.certificate;
+  };
+}
diff --git a/nixpkgs/nixos/modules/security/misc.nix b/nixpkgs/nixos/modules/security/misc.nix
index 6833452a570e..cd48eade7784 100644
--- a/nixpkgs/nixos/modules/security/misc.nix
+++ b/nixpkgs/nixos/modules/security/misc.nix
@@ -83,34 +83,19 @@ with lib;
     security.virtualisation.flushL1DataCache = mkOption {
       type = types.nullOr (types.enum [ "never" "cond" "always" ]);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Whether the hypervisor should flush the L1 data cache before
         entering guests.
-        See also <xref linkend="opt-security.allowSimultaneousMultithreading"/>.
-
-        <variablelist>
-          <varlistentry>
-            <term><literal>null</literal></term>
-            <listitem><para>uses the kernel default</para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"never"</literal></term>
-            <listitem><para>disables L1 data cache flushing entirely.
-            May be appropriate if all guests are trusted.</para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"cond"</literal></term>
-            <listitem><para>flushes L1 data cache only for pre-determined
-            code paths.  May leak information about the host address space
-            layout.</para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"always"</literal></term>
-            <listitem><para>flushes L1 data cache every time the hypervisor
-            enters the guest.  May incur significant performance cost.
-            </para></listitem>
-          </varlistentry>
-        </variablelist>
+        See also [](#opt-security.allowSimultaneousMultithreading).
+
+        - `null`: uses the kernel default
+        - `"never"`: disables L1 data cache flushing entirely.
+          May be appropriate if all guests are trusted.
+        - `"cond"`: flushes L1 data cache only for pre-determined
+          code paths.  May leak information about the host address space
+          layout.
+        - `"always"`: flushes L1 data cache every time the hypervisor
+          enters the guest.  May incur significant performance cost.
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/security/pam.nix b/nixpkgs/nixos/modules/security/pam.nix
index 9f89508bd1fe..eac67cfdec5a 100644
--- a/nixpkgs/nixos/modules/security/pam.nix
+++ b/nixpkgs/nixos/modules/security/pam.nix
@@ -146,8 +146,8 @@ let
         default = config.users.mysql.enable;
         defaultText = literalExpression "config.users.mysql.enable";
         type = types.bool;
-        description = ''
-          If set, the <literal>pam_mysql</literal> module will be used to
+        description = lib.mdDoc ''
+          If set, the `pam_mysql` module will be used to
           authenticate users against a MySQL/MariaDB database.
         '';
       };
@@ -282,7 +282,7 @@ let
         defaultText = literalExpression "config.security.pam.mount.enable";
         type = types.bool;
         description = lib.mdDoc ''
-          Enable PAM mount (pam_mount) system to mount fileystems on user login.
+          Enable PAM mount (pam_mount) system to mount filesystems on user login.
         '';
       };
 
@@ -305,7 +305,7 @@ let
         default = false;
         type = types.bool;
         description = lib.mdDoc ''
-          Wheather the delay after typing a wrong password should be disabled.
+          Whether the delay after typing a wrong password should be disabled.
         '';
       };
 
@@ -320,11 +320,10 @@ let
       limits = mkOption {
         default = [];
         type = limitsType;
-        description = ''
+        description = lib.mdDoc ''
           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>.
+          value of {option}`security.pam.loginLimits`.
+          The meaning of the values is explained in {manpage}`limits.conf(5)`.
         '';
       };
 
@@ -393,6 +392,24 @@ let
         '';
       };
 
+      failDelay = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            If enabled, this will replace the `FAIL_DELAY` setting from `login.defs`.
+            Change the delay on failure per-application.
+            '';
+        };
+
+        delay = mkOption {
+          default = 3000000;
+          type = types.int;
+          example = 1000000;
+          description = lib.mdDoc "The delay time (in microseconds) on failure.";
+        };
+      };
+
       gnupg = {
         enable = mkOption {
           type = types.bool;
@@ -429,6 +446,15 @@ let
         };
       };
 
+      zfs = mkOption {
+        default = config.security.pam.zfs.enable;
+        defaultText = literalExpression "config.security.pam.zfs.enable";
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable unlocking and mounting of encrypted ZFS home dataset at login.
+        '';
+      };
+
       text = mkOption {
         type = types.nullOr types.lines;
         description = lib.mdDoc "Contents of the PAM service file.";
@@ -471,6 +497,9 @@ let
             account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
             account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so
           '' +
+          optionalString config.services.homed.enable ''
+            account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' +
           # The required pam_unix.so module has to come after all the sufficient modules
           # because otherwise, the account lookup will fail if the user does not exist
           # locally, for example with MySQL- or LDAP-auth.
@@ -522,24 +551,38 @@ let
           # 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
-          # prompts the user for password so we run it once with 'required' at an
+          # prompts the user for password so we run it once with 'optional' at an
           # 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 &&
+          # We use try_first_pass the second time to avoid prompting password twice.
+          #
+          # The same principle applies to systemd-homed
+          (optionalString ((cfg.unixAuth || config.services.homed.enable) &&
             (config.security.pam.enableEcryptfs
+              || config.security.pam.enableFscrypt
               || cfg.pamMount
               || cfg.enableKwallet
               || cfg.enableGnomeKeyring
               || cfg.googleAuthenticator.enable
               || cfg.gnupg.enable
-              || cfg.duoSecurity.enable))
+              || cfg.failDelay.enable
+              || cfg.duoSecurity.enable
+              || cfg.zfs))
             (
-              ''
-                auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
+              optionalString config.services.homed.enable ''
+                auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
+              '' +
+              optionalString cfg.unixAuth ''
+                auth optional 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 config.security.pam.enableFscrypt ''
+                auth optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
+              '' +
+              optionalString cfg.zfs ''
+                auth optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
+              '' +
               optionalString cfg.pamMount ''
                 auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
               '' +
@@ -552,6 +595,9 @@ let
               optionalString cfg.gnupg.enable ''
                 auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.storeOnly " store-only"}
               '' +
+              optionalString cfg.failDelay.enable ''
+                auth optional ${pkgs.pam}/lib/security/pam_faildelay.so delay=${toString cfg.failDelay.delay}
+              '' +
               optionalString cfg.googleAuthenticator.enable ''
                 auth required ${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so no_increment_hotp
               '' +
@@ -559,6 +605,9 @@ let
                 auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
               ''
             )) +
+          optionalString config.services.homed.enable ''
+            auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' +
           optionalString cfg.unixAuth ''
             auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
           '' +
@@ -580,11 +629,21 @@ let
             auth required pam_deny.so
 
             # Password management.
-            password sufficient pam_unix.so nullok sha512
+          '' +
+          optionalString config.services.homed.enable ''
+            password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' + ''
+            password sufficient pam_unix.so nullok yescrypt
           '' +
           optionalString config.security.pam.enableEcryptfs ''
             password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
           '' +
+          optionalString config.security.pam.enableFscrypt ''
+            password optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
+          '' +
+          optionalString cfg.zfs ''
+            password optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
+          '' +
           optionalString cfg.pamMount ''
             password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
           '' +
@@ -595,7 +654,7 @@ let
             password sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
           '' +
           optionalString config.services.sssd.enable ''
-            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok
+            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so
           '' +
           optionalString config.security.pam.krb5.enable ''
             password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
@@ -616,11 +675,14 @@ let
           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.ttyAudit.enable (concatStringsSep " \\\n  " ([
+            "session required ${pkgs.pam}/lib/security/pam_tty_audit.so"
+          ] ++ optional cfg.ttyAudit.openOnly "open_only"
+          ++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
+          ++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
+          )) +
+          optionalString config.services.homed.enable ''
+            session required ${config.systemd.package}/lib/security/pam_systemd_home.so
           '' +
           optionalString cfg.makeHomeDir ''
             session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
@@ -631,6 +693,18 @@ let
           optionalString config.security.pam.enableEcryptfs ''
             session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
           '' +
+          optionalString config.security.pam.enableFscrypt ''
+            # Work around https://github.com/systemd/systemd/issues/8598
+            # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
+            # anyways.
+            # See also https://github.com/google/fscrypt/issues/95
+            session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
+            session optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
+          '' +
+          optionalString cfg.zfs ''
+            session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
+            session optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes} ${optionalString config.security.pam.zfs.noUnmount "nounmount"}
+          '' +
           optionalString cfg.pamMount ''
             session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
           '' +
@@ -658,7 +732,7 @@ let
           optionalString (cfg.limits != []) ''
             session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}
           '' +
-          optionalString (cfg.showMotd && config.users.motd != null) ''
+          optionalString (cfg.showMotd && (config.users.motd != null || config.users.motdFile != null)) ''
             session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}
           '' +
           optionalString (cfg.enableAppArmor && config.security.apparmor.enable) ''
@@ -739,7 +813,9 @@ let
     };
   }));
 
-  motd = pkgs.writeText "motd" config.users.motd;
+  motd = if config.users.motdFile == null
+         then pkgs.writeText "motd" config.users.motd
+         else config.users.motdFile;
 
   makePAMService = name: service:
     { name = "pam.d/${name}";
@@ -774,18 +850,18 @@ in
           }
        ];
 
-     description =
-       '' Define resource limits that should apply to users or groups.
-          Each item in the list should be an attribute set with a
-          <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 <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>
-          instead.
-       '';
+     description = lib.mdDoc ''
+       Define resource limits that should apply to users or groups.
+       Each item in the list should be an attribute set with a
+       {var}`domain`, {var}`type`,
+       {var}`item`, and {var}`value`
+       attribute.  The syntax and semantics of these attributes
+       must be that described in {manpage}`limits.conf(5)`.
+
+       Note that these limits do not apply to systemd services,
+       whose limits can be changed via {option}`systemd.extraConfig`
+       instead.
+     '';
     };
 
     security.pam.services = mkOption {
@@ -823,7 +899,7 @@ in
         '';
     };
 
-    security.pam.enableOTPW = mkEnableOption "the OTPW (one-time password) PAM module";
+    security.pam.enableOTPW = mkEnableOption (lib.mdDoc "the OTPW (one-time password) PAM module");
 
     security.pam.krb5 = {
       enable = mkOption {
@@ -1146,7 +1222,43 @@ in
       };
     };
 
-    security.pam.enableEcryptfs = mkEnableOption "eCryptfs PAM module (mounting ecryptfs home directory on login)";
+    security.pam.zfs = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable unlocking and mounting of encrypted ZFS home dataset at login.
+        '';
+      };
+
+      homes = mkOption {
+        example = "rpool/home";
+        default = "rpool/home";
+        type = types.str;
+        description = lib.mdDoc ''
+          Prefix of home datasets. This value will be concatenated with
+          `"/" + <username>` in order to determine the home dataset to unlock.
+        '';
+      };
+
+      noUnmount = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Do not unmount home dataset on logout.
+        '';
+      };
+    };
+
+    security.pam.enableEcryptfs = mkEnableOption (lib.mdDoc "eCryptfs PAM module (mounting ecryptfs home directory on login)");
+    security.pam.enableFscrypt = mkEnableOption (lib.mdDoc ''
+      Enables fscrypt to automatically unlock directories with the user's login password.
+
+      This also enables a service at security.pam.services.fscrypt which is used by
+      fscrypt to verify the user's password when setting up a new protector. If you
+      use something other than pam_unix to verify user passwords, please remember to
+      adjust this PAM service.
+    '');
 
     users.motd = mkOption {
       default = null;
@@ -1155,12 +1267,32 @@ in
       description = lib.mdDoc "Message of the day shown to users when they log in.";
     };
 
+    users.motdFile = mkOption {
+      default = null;
+      example = "/etc/motd";
+      type = types.nullOr types.path;
+      description = lib.mdDoc "A file containing the message of the day shown to users when they log in.";
+    };
   };
 
 
   ###### implementation
 
   config = {
+    assertions = [
+      {
+        assertion = config.users.motd == null || config.users.motdFile == null;
+        message = ''
+          Only one of users.motd and users.motdFile can be set.
+        '';
+      }
+      {
+        assertion = config.security.pam.zfs.enable -> (config.boot.zfs.enabled || config.boot.zfs.enableUnstable);
+        message = ''
+          `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled` or `boot.zfs.enableUnstable`).
+        '';
+      }
+    ];
 
     environment.systemPackages =
       # Include the PAM modules in the system path mostly for the manpages.
@@ -1171,6 +1303,7 @@ in
       ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
       ++ optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
       ++ optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
+      ++ optionals config.security.pam.enableFscrypt [ pkgs.fscrypt-experimental ]
       ++ optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
 
     boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
@@ -1212,6 +1345,9 @@ in
            it complains "Cannot create session: Already running in a
            session". */
         runuser-l = { rootOK = true; unixAuth = false; };
+      } // optionalAttrs (config.security.pam.enableFscrypt) {
+        # Allow fscrypt to verify login passphrase
+        fscrypt = {};
       };
 
     security.apparmor.includes."abstractions/pam" = let
@@ -1276,11 +1412,14 @@ in
       optionalString config.security.pam.enableEcryptfs ''
         mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,
       '' +
+      optionalString config.security.pam.enableFscrypt ''
+        mr ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so,
+      '' +
       optionalString (isEnabled (cfg: cfg.pamMount)) ''
         mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
       '' +
       optionalString (isEnabled (cfg: cfg.enableGnomeKeyring)) ''
-        mr ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so,
+        mr ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so,
       '' +
       optionalString (isEnabled (cfg: cfg.startSession)) ''
         mr ${config.systemd.package}/lib/security/pam_systemd.so,
@@ -1293,7 +1432,13 @@ in
         mr ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so,
       '' +
       optionalString config.virtualisation.lxc.lxcfs.enable ''
-        mr ${pkgs.lxc}/lib/security/pam_cgfs.so
+        mr ${pkgs.lxc}/lib/security/pam_cgfs.so,
+      '' +
+      optionalString (isEnabled (cfg: cfg.zfs)) ''
+        mr ${config.boot.zfs.package}/lib/security/pam_zfs_key.so,
+      '' +
+      optionalString config.services.homed.enable ''
+        mr ${config.systemd.package}/lib/security/pam_systemd_home.so
       '';
   };
 
diff --git a/nixpkgs/nixos/modules/security/pam_mount.nix b/nixpkgs/nixos/modules/security/pam_mount.nix
index 11cc13a8cbeb..ad78f38b0866 100644
--- a/nixpkgs/nixos/modules/security/pam_mount.nix
+++ b/nixpkgs/nixos/modules/security/pam_mount.nix
@@ -24,7 +24,7 @@ in
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Enable PAM mount system to mount fileystems on user login.
+          Enable PAM mount system to mount filesystems on user login.
         '';
       };
 
@@ -47,6 +47,18 @@ in
         '';
       };
 
+      cryptMountOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = literalExpression ''
+          [ "allow_discard" ]
+        '';
+        description = lib.mdDoc ''
+          Global mount options that apply to every crypt volume.
+          You can define volume-specific options in the volume definitions.
+        '';
+      };
+
       fuseMountOptions = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -155,9 +167,11 @@ in
           <!-- create mount point if not present -->
           <mkmountpoint enable="${if cfg.createMountPoints then "1" else "0"}" remove="${if cfg.removeCreatedMountPoints then "true" else "false"}" />
           <!-- specify the binaries to be called -->
-          <fusemount>${pkgs.fuse}/bin/mount.fuse %(VOLUME) %(MNTPT) -o ${concatStringsSep "," (cfg.fuseMountOptions ++ [ "%(OPTIONS)" ])}</fusemount>
+          <!-- the comma in front of the options is necessary for empty options -->
+          <fusemount>${pkgs.fuse}/bin/mount.fuse %(VOLUME) %(MNTPT) -o ,${concatStringsSep "," (cfg.fuseMountOptions ++ [ "%(OPTIONS)" ])}'</fusemount>
           <fuseumount>${pkgs.fuse}/bin/fusermount -u %(MNTPT)</fuseumount>
-          <cryptmount>${pkgs.pam_mount}/bin/mount.crypt %(VOLUME) %(MNTPT)</cryptmount>
+          <!-- the comma in front of the options is necessary for empty options -->
+          <cryptmount>${pkgs.pam_mount}/bin/mount.crypt -o ,${concatStringsSep "," (cfg.cryptMountOptions ++ [ "%(OPTIONS)" ])} %(VOLUME) %(MNTPT)</cryptmount>
           <cryptumount>${pkgs.pam_mount}/bin/umount.crypt %(MNTPT)</cryptumount>
           <pmvarrun>${pkgs.pam_mount}/bin/pmvarrun -u %(USER) -o %(OPERATION)</pmvarrun>
           ${optionalString oflRequired "<ofl>${fake_ofl}/bin/fake_ofl %(SIGNAL) %(MNTPT)</ofl>"}
diff --git a/nixpkgs/nixos/modules/security/please.nix b/nixpkgs/nixos/modules/security/please.nix
new file mode 100644
index 000000000000..88bb9cba2bfc
--- /dev/null
+++ b/nixpkgs/nixos/modules/security/please.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.security.please;
+  ini = pkgs.formats.ini { };
+in
+{
+  options.security.please = {
+    enable = mkEnableOption (mdDoc ''
+      please, a Sudo clone which allows a users to execute a command or edit a
+      file as another user
+    '');
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.please;
+      defaultText = literalExpression "pkgs.please";
+      description = mdDoc ''
+        Which package to use for {command}`please`.
+      '';
+    };
+
+    wheelNeedsPassword = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Whether users of the `wheel` group must provide a password to run
+        commands or edit files with {command}`please` and
+        {command}`pleaseedit` respectively.
+      '';
+    };
+
+    settings = mkOption {
+      type = ini.type;
+      default = { };
+      example = {
+        jim_run_any_as_root = {
+          name = "jim";
+          type = "run";
+          target = "root";
+          rule = ".*";
+          require_pass = false;
+        };
+        jim_edit_etc_hosts_as_root = {
+          name = "jim";
+          type = "edit";
+          target = "root";
+          rule = "/etc/hosts";
+          editmode = 644;
+          require_pass = true;
+        };
+      };
+      description = mdDoc ''
+        Please configuration. Refer to
+        <https://github.com/edneville/please/blob/master/please.ini.md> for
+        details.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers =
+      let
+        owner = "root";
+        group = "root";
+        setuid = true;
+      in
+      {
+        please = {
+          source = "${cfg.package}/bin/please";
+          inherit owner group setuid;
+        };
+        pleaseedit = {
+          source = "${cfg.package}/bin/pleaseedit";
+          inherit owner group setuid;
+        };
+      };
+
+    security.please.settings = rec {
+      # The "wheel" group is allowed to do anything by default but this can be
+      # overridden.
+      wheel_run_as_any = {
+        type = "run";
+        group = true;
+        name = "wheel";
+        target = ".*";
+        rule = ".*";
+        require_pass = cfg.wheelNeedsPassword;
+      };
+      wheel_edit_as_any = wheel_run_as_any // { type = "edit"; };
+      wheel_list_as_any = wheel_run_as_any // { type = "list"; };
+    };
+
+    environment = {
+      systemPackages = [ cfg.package ];
+
+      etc."please.ini".source = ini.generate "please.ini"
+        (cfg.settings // (rec {
+          # The "root" user is allowed to do anything by default and this cannot
+          # be overridden.
+          root_run_as_any = {
+            type = "run";
+            name = "root";
+            target = ".*";
+            rule = ".*";
+            require_pass = false;
+          };
+          root_edit_as_any = root_run_as_any // { type = "edit"; };
+          root_list_as_any = root_run_as_any // { type = "list"; };
+        }));
+    };
+
+    security.pam.services.please = {
+      sshAgentAuth = true;
+      usshAuth = true;
+    };
+
+    meta.maintainers = with maintainers; [ azahi ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/security/polkit.nix b/nixpkgs/nixos/modules/security/polkit.nix
index 0a2d81445ba5..de427ccb295b 100644
--- a/nixpkgs/nixos/modules/security/polkit.nix
+++ b/nixpkgs/nixos/modules/security/polkit.nix
@@ -12,7 +12,9 @@ in
 
   options = {
 
-    security.polkit.enable = mkEnableOption "polkit";
+    security.polkit.enable = mkEnableOption (lib.mdDoc "polkit");
+
+    security.polkit.debug = mkEnableOption (lib.mdDoc "debug logs from polkit. This is required in order to see log messages from rule definitions");
 
     security.polkit.extraConfig = mkOption {
       type = types.lines;
@@ -21,6 +23,7 @@ in
         ''
           /* Log authorization checks. */
           polkit.addRule(function(action, subject) {
+            // Make sure to set { security.polkit.debug = true; } in configuration.nix
             polkit.log("user " +  subject.user + " is attempting action " + action.id + " from PID " + subject.pid);
           });
 
@@ -58,6 +61,11 @@ in
 
     systemd.packages = [ pkgs.polkit.out ];
 
+    systemd.services.polkit.serviceConfig.ExecStart = [
+      ""
+      "${pkgs.polkit.out}/lib/polkit-1/polkitd ${optionalString (!cfg.debug) "--no-debug"}"
+    ];
+
     systemd.services.polkit.restartTriggers = [ config.system.path ];
     systemd.services.polkit.stopIfChanged = false;
 
diff --git a/nixpkgs/nixos/modules/security/sudo.nix b/nixpkgs/nixos/modules/security/sudo.nix
index faa99a31a6d6..296b61fd703b 100644
--- a/nixpkgs/nixos/modules/security/sudo.nix
+++ b/nixpkgs/nixos/modules/security/sudo.nix
@@ -46,7 +46,7 @@ in
       type = types.package;
       default = pkgs.sudo;
       defaultText = literalExpression "pkgs.sudo";
-      description = ''
+      description = lib.mdDoc ''
         Which package to use for `sudo`.
       '';
     };
@@ -157,17 +157,17 @@ in
               options = {
                 command = mkOption {
                   type = with types; str;
-                  description = ''
+                  description = lib.mdDoc ''
                     A command being either just a path to a binary to allow any arguments,
-                    the full command with arguments pre-set or with <literal>""</literal> used as the argument,
+                    the full command with arguments pre-set or with `""` used as the argument,
                     not allowing arguments to the command at all.
                   '';
                 };
 
                 options = mkOption {
                   type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
-                  description = ''
-                    Options for running the command. Refer to the <a href="https://www.sudo.ws/man/1.7.10/sudoers.man.html">sudo manual</a>.
+                  description = lib.mdDoc ''
+                    Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
                   '';
                   default = [];
                 };
diff --git a/nixpkgs/nixos/modules/security/systemd-confinement.nix b/nixpkgs/nixos/modules/security/systemd-confinement.nix
index f5ed3d281a5f..cdf6c22ef1b6 100644
--- a/nixpkgs/nixos/modules/security/systemd-confinement.nix
+++ b/nixpkgs/nixos/modules/security/systemd-confinement.nix
@@ -38,8 +38,8 @@ in {
         type = types.listOf (types.either types.str types.package);
         default = [];
         description = let
-          mkScOption = optName: "<option>serviceConfig.${optName}</option>";
-        in ''
+          mkScOption = optName: "{option}`serviceConfig.${optName}`";
+        in lib.mdDoc ''
           Additional packages or strings with context to add to the closure of
           the chroot. By default, this includes all the packages from the
           ${lib.concatMapStringsSep ", " mkScOption [
@@ -47,12 +47,14 @@ in {
             "ExecStopPost"
           ]} and ${mkScOption "ExecStart"} options. If you want to have all the
           dependencies of this systemd unit, you can use
-          <option>confinement.fullUnit</option>.
+          {option}`confinement.fullUnit`.
 
-          <note><para>The store paths listed in <option>path</option> are
-          <emphasis role="strong">not</emphasis> included in the closure as
+          ::: {.note}
+          The store paths listed in {option}`path` are
+          **not** included in the closure as
           well as paths from other options except those listed
-          above.</para></note>
+          above.
+          :::
         '';
       };
 
@@ -74,25 +76,24 @@ in {
       options.confinement.mode = lib.mkOption {
         type = types.enum [ "full-apivfs" "chroot-only" ];
         default = "full-apivfs";
-        description = ''
-          The value <literal>full-apivfs</literal> (the default) sets up
-          private <filename class="directory">/dev</filename>, <filename
-          class="directory">/proc</filename>, <filename
-          class="directory">/sys</filename> and <filename
-          class="directory">/tmp</filename> file systems in a separate user
+        description = lib.mdDoc ''
+          The value `full-apivfs` (the default) sets up
+          private {file}`/dev`, {file}`/proc`,
+          {file}`/sys` and {file}`/tmp` file systems in a separate user
           name space.
 
-          If this is set to <literal>chroot-only</literal>, only the file
+          If this is set to `chroot-only`, only the file
           system name space is set up along with the call to
-          <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
+          {manpage}`chroot(2)`.
 
-          <note><para>This doesn't cover network namespaces and is solely for
-          file system level isolation.</para></note>
+          ::: {.note}
+          This doesn't cover network namespaces and is solely for
+          file system level isolation.
+          :::
         '';
       };
 
       config = let
-        rootName = "${mkPathSafeName name}-chroot";
         inherit (config.confinement) binSh fullUnit;
         wantsAPIVFS = lib.mkDefault (config.confinement.mode == "full-apivfs");
       in lib.mkIf config.confinement.enable {
diff --git a/nixpkgs/nixos/modules/security/tpm2.nix b/nixpkgs/nixos/modules/security/tpm2.nix
index 375f4af1a64f..708c3a69d174 100644
--- a/nixpkgs/nixos/modules/security/tpm2.nix
+++ b/nixpkgs/nixos/modules/security/tpm2.nix
@@ -3,7 +3,7 @@ let
   cfg = config.security.tpm2;
 
   # This snippet is taken from tpm2-tss/dist/tpm-udev.rules, but modified to allow custom user/groups
-  # The idea is that the tssUser is allowed to acess the TPM and kernel TPM resource manager, while
+  # The idea is that the tssUser is allowed to access the TPM and kernel TPM resource manager, while
   # the tssGroup is only allowed to access the kernel resource manager
   # Therefore, if either of the two are null, the respective part isn't generated
   udevRules = tssUser: tssGroup: ''
@@ -17,7 +17,7 @@ let
 
 in {
   options.security.tpm2 = {
-    enable = lib.mkEnableOption "Trusted Platform Module 2 support";
+    enable = lib.mkEnableOption (lib.mdDoc "Trusted Platform Module 2 support");
 
     tssUser = lib.mkOption {
       description = lib.mdDoc ''
@@ -48,9 +48,9 @@ in {
     };
 
     abrmd = {
-      enable = lib.mkEnableOption ''
+      enable = lib.mkEnableOption (lib.mdDoc ''
         Trusted Platform 2 userspace resource manager daemon
-      '';
+      '');
 
       package = lib.mkOption {
         description = lib.mdDoc "tpm2-abrmd package to use";
@@ -61,10 +61,10 @@ in {
     };
 
     pkcs11 = {
-      enable = lib.mkEnableOption ''
+      enable = lib.mkEnableOption (lib.mdDoc ''
         TPM2 PKCS#11 tool and shared library in system path
-        (<literal>/run/current-system/sw/lib/libtpm2_pkcs11.so</literal>)
-      '';
+        (`/run/current-system/sw/lib/libtpm2_pkcs11.so`)
+      '');
 
       package = lib.mkOption {
         description = lib.mdDoc "tpm2-pkcs11 package to use";
@@ -76,21 +76,11 @@ in {
 
     tctiEnvironment = {
       enable = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Set common TCTI environment variables to the specified value.
           The variables are
-          <itemizedlist>
-            <listitem>
-              <para>
-                <literal>TPM2TOOLS_TCTI</literal>
-              </para>
-            </listitem>
-            <listitem>
-              <para>
-                <literal>TPM2_PKCS11_TCTI</literal>
-              </para>
-            </listitem>
-          </itemizedlist>
+          - `TPM2TOOLS_TCTI`
+          - `TPM2_PKCS11_TCTI`
         '';
         type = lib.types.bool;
         default = false;
diff --git a/nixpkgs/nixos/modules/security/wrappers/default.nix b/nixpkgs/nixos/modules/security/wrappers/default.nix
index aa37ec7db496..12255d8392fe 100644
--- a/nixpkgs/nixos/modules/security/wrappers/default.nix
+++ b/nixpkgs/nixos/modules/security/wrappers/default.nix
@@ -51,20 +51,21 @@ let
     options.capabilities = lib.mkOption
       { type = lib.types.commas;
         default = "";
-        description = ''
-          A comma-separated list of capabilities to be given to the wrapper
-          program. For capabilities supported by the system check the
-          <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry>
-          manual page.
-
-          <note><para>
-            <literal>cap_setpcap</literal>, which is required for the wrapper
-            program to be able to raise caps into the Ambient set is NOT raised
-            to the Ambient set so that the real program cannot modify its own
-            capabilities!! This may be too restrictive for cases in which the
-            real program needs cap_setpcap but it at least leans on the side
-            security paranoid vs. too relaxed.
-          </para></note>
+        description = lib.mdDoc ''
+          A comma-separated list of capability clauses to be given to the
+          wrapper program. The format for capability clauses is described in the
+          “TEXTUAL REPRESENTATION” section of the {manpage}`cap_from_text(3)`
+          manual page. For a list of capabilities supported by the system, check
+          the {manpage}`capabilities(7)` manual page.
+
+          ::: {.note}
+          `cap_setpcap`, which is required for the wrapper
+          program to be able to raise caps into the Ambient set is NOT raised
+          to the Ambient set so that the real program cannot modify its own
+          capabilities!! This may be too restrictive for cases in which the
+          real program needs cap_setpcap but it at least leans on the side
+          security paranoid vs. too relaxed.
+          :::
         '';
       };
     options.setuid = lib.mkOption
@@ -189,7 +190,7 @@ in
       default = "50%";
       example = "10G";
       type = lib.types.str;
-      description = ''
+      description = lib.mdDoc ''
         Size limit for the /run/wrappers tmpfs. Look at mount(8), tmpfs size option,
         for the accepted syntax. WARNING: don't set to less than 64MB.
       '';
@@ -199,9 +200,9 @@ in
       type        = lib.types.path;
       default     = "/run/wrappers/bin";
       internal    = true;
-      description = ''
+      description = lib.mdDoc ''
         This option defines the path to the wrapper programs. It
-        should not be overriden.
+        should not be overridden.
       '';
     };
   };
@@ -282,7 +283,7 @@ in
         '';
 
     ###### wrappers consistency checks
-    system.extraDependencies = lib.singleton (pkgs.runCommandLocal
+    system.checks = lib.singleton (pkgs.runCommandLocal
       "ensure-all-wrappers-paths-exist" { }
       ''
         # make sure we produce output
diff --git a/nixpkgs/nixos/modules/services/admin/meshcentral.nix b/nixpkgs/nixos/modules/services/admin/meshcentral.nix
index e1df39716d47..22f31e952622 100644
--- a/nixpkgs/nixos/modules/services/admin/meshcentral.nix
+++ b/nixpkgs/nixos/modules/services/admin/meshcentral.nix
@@ -5,7 +5,7 @@ let
   configFile = configFormat.generate "meshcentral-config.json" cfg.settings;
 in with lib; {
   options.services.meshcentral = with types; {
-    enable = mkEnableOption "MeshCentral computer management server";
+    enable = mkEnableOption (lib.mdDoc "MeshCentral computer management server");
     package = mkOption {
       description = lib.mdDoc "MeshCentral package to use. Replacing this may be necessary to add dependencies for extra functionality.";
       type = types.package;
@@ -13,15 +13,13 @@ in with lib; {
       defaultText = literalExpression "pkgs.meshcentral";
     };
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Settings for MeshCentral. Refer to upstream documentation for details:
 
-        <itemizedlist>
-          <listitem><para><link xlink:href="https://github.com/Ylianst/MeshCentral/blob/master/meshcentral-config-schema.json">JSON Schema definition</link></para></listitem>
-          <listitem><para><link xlink:href="https://github.com/Ylianst/MeshCentral/blob/master/sample-config.json">simple sample configuration</link></para></listitem>
-          <listitem><para><link xlink:href="https://github.com/Ylianst/MeshCentral/blob/master/sample-config-advanced.json">complex sample configuration</link></para></listitem>
-          <listitem><para><link xlink:href="https://www.meshcommander.com/meshcentral2">Old homepage) with documentation link</link></para></listitem>
-        </itemizedlist>
+        - [JSON Schema definition](https://github.com/Ylianst/MeshCentral/blob/master/meshcentral-config-schema.json)
+        - [simple sample configuration](https://github.com/Ylianst/MeshCentral/blob/master/sample-config.json)
+        - [complex sample configuration](https://github.com/Ylianst/MeshCentral/blob/master/sample-config-advanced.json)
+        - [Old homepage with documentation link](https://www.meshcommander.com/meshcentral2)
       '';
       type = types.submodule {
         freeformType = configFormat.type;
diff --git a/nixpkgs/nixos/modules/services/admin/oxidized.nix b/nixpkgs/nixos/modules/services/admin/oxidized.nix
index f0d46f787b7c..56f33031498a 100644
--- a/nixpkgs/nixos/modules/services/admin/oxidized.nix
+++ b/nixpkgs/nixos/modules/services/admin/oxidized.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.services.oxidized = {
-    enable = mkEnableOption "the oxidized configuration backup service";
+    enable = mkEnableOption (lib.mdDoc "the oxidized configuration backup service");
 
     user = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/admin/pgadmin.nix b/nixpkgs/nixos/modules/services/admin/pgadmin.nix
index aff25bcb68be..390c80d1a2d4 100644
--- a/nixpkgs/nixos/modules/services/admin/pgadmin.nix
+++ b/nixpkgs/nixos/modules/services/admin/pgadmin.nix
@@ -28,7 +28,7 @@ let
 in
 {
   options.services.pgadmin = {
-    enable = mkEnableOption "PostgreSQL Admin 4";
+    enable = mkEnableOption (lib.mdDoc "PostgreSQL Admin 4");
 
     port = mkOption {
       description = lib.mdDoc "Port for pgadmin4 to run on";
@@ -37,27 +37,76 @@ in
     };
 
     initialEmail = mkOption {
-      description = lib.mdDoc "Initial email for the pgAdmin account.";
+      description = lib.mdDoc "Initial email for the pgAdmin account";
       type = types.str;
     };
 
     initialPasswordFile = mkOption {
       description = lib.mdDoc ''
         Initial password file for the pgAdmin account.
-        NOTE: Should be string not a store path, to prevent the password from being world readable.
+        NOTE: Should be string not a store path, to prevent the password from being world readable
       '';
       type = types.path;
     };
 
-    openFirewall = mkEnableOption "firewall passthrough for pgadmin4";
+    emailServer = {
+      enable = mkOption {
+        description = lib.mdDoc ''
+          Enable SMTP email server. This is necessary, if you want to use password recovery or change your own password
+        '';
+        type = types.bool;
+        default = false;
+      };
+      address = mkOption {
+        description = lib.mdDoc "SMTP server for email delivery";
+        type = types.str;
+        default = "localhost";
+      };
+      port = mkOption {
+        description = lib.mdDoc "SMTP server port for email delivery";
+        type = types.port;
+        default = 25;
+      };
+      useSSL = mkOption {
+        description = lib.mdDoc "SMTP server should use SSL";
+        type = types.bool;
+        default = false;
+      };
+      useTLS = mkOption {
+        description = lib.mdDoc "SMTP server should use TLS";
+        type = types.bool;
+        default = false;
+      };
+      username = mkOption {
+        description = lib.mdDoc "SMTP server username for email delivery";
+        type = types.nullOr types.str;
+        default = null;
+      };
+      sender = mkOption {
+        description = lib.mdDoc ''
+          SMTP server sender email for email delivery. Some servers require this to be a valid email address from that server
+        '';
+        type = types.str;
+        example = "noreply@example.com";
+      };
+      passwordFile = mkOption {
+        description = lib.mdDoc ''
+          Password for SMTP email account.
+          NOTE: Should be string not a store path, to prevent the password from being world readable
+        '';
+        type = types.path;
+      };
+    };
+
+    openFirewall = mkEnableOption (lib.mdDoc "firewall passthrough for pgadmin4");
 
     settings = mkOption {
       description = lib.mdDoc ''
         Settings for pgadmin4.
-        [Documentation](https://www.pgadmin.org/docs/pgadmin4/development/config_py.html).
+        [Documentation](https://www.pgadmin.org/docs/pgadmin4/development/config_py.html)
       '';
       type = pyType;
-      default= {};
+      default = { };
     };
   };
 
@@ -69,6 +118,13 @@ in
       SERVER_MODE = true;
     } // (optionalAttrs cfg.openFirewall {
       DEFAULT_SERVER = mkDefault "::";
+    }) // (optionalAttrs cfg.emailServer.enable {
+      MAIL_SERVER = cfg.emailServer.address;
+      MAIL_PORT = cfg.emailServer.port;
+      MAIL_USE_SSL = cfg.emailServer.useSSL;
+      MAIL_USE_TLS = cfg.emailServer.useTLS;
+      MAIL_USERNAME = cfg.emailServer.username;
+      SECURITY_EMAIL_SENDER = cfg.emailServer.sender;
     });
 
     systemd.services.pgadmin = {
@@ -115,10 +171,14 @@ in
       group = "pgadmin";
     };
 
-    users.groups.pgadmin = {};
+    users.groups.pgadmin = { };
 
     environment.etc."pgadmin/config_system.py" = {
-      text = formatPy cfg.settings;
+      text = lib.optionalString cfg.emailServer.enable ''
+        with open("${cfg.emailServer.passwordFile}") as f:
+          pw = f.read()
+        MAIL_PASSWORD = pw
+      '' + formatPy cfg.settings;
       mode = "0600";
       user = "pgadmin";
       group = "pgadmin";
diff --git a/nixpkgs/nixos/modules/services/admin/salt/master.nix b/nixpkgs/nixos/modules/services/admin/salt/master.nix
index 3c246a942399..4346022970e1 100644
--- a/nixpkgs/nixos/modules/services/admin/salt/master.nix
+++ b/nixpkgs/nixos/modules/services/admin/salt/master.nix
@@ -20,7 +20,7 @@ in
 {
   options = {
     services.salt.master = {
-      enable = mkEnableOption "Salt master service";
+      enable = mkEnableOption (lib.mdDoc "Salt master service");
       configuration = mkOption {
         type = types.attrs;
         default = {};
diff --git a/nixpkgs/nixos/modules/services/admin/salt/minion.nix b/nixpkgs/nixos/modules/services/admin/salt/minion.nix
index 165ec8ef96b3..3ae02a4cc5d5 100644
--- a/nixpkgs/nixos/modules/services/admin/salt/minion.nix
+++ b/nixpkgs/nixos/modules/services/admin/salt/minion.nix
@@ -21,7 +21,7 @@ in
 {
   options = {
     services.salt.minion = {
-      enable = mkEnableOption "Salt minion service";
+      enable = mkEnableOption (lib.mdDoc "Salt minion service");
       configuration = mkOption {
         type = types.attrs;
         default = {};
diff --git a/nixpkgs/nixos/modules/services/amqp/activemq/default.nix b/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
index bd37fe3b5574..b1f9b7a3bb1f 100644
--- a/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
+++ b/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
@@ -7,20 +7,19 @@ let
 
   cfg = config.services.activemq;
 
-  activemqBroker = stdenv.mkDerivation {
-    name = "activemq-broker";
-    phases = [ "installPhase" ];
-    buildInputs = [ jdk ];
-    installPhase = ''
-      mkdir -p $out/lib
-      source ${activemq}/lib/classpath.env
-      export CLASSPATH
-      ln -s "${./ActiveMQBroker.java}" ActiveMQBroker.java
-      javac -d $out/lib ActiveMQBroker.java
-    '';
-  };
+  activemqBroker = runCommand "activemq-broker"
+    {
+      nativeBuildInputs = [ jdk ];
+    } ''
+    mkdir -p $out/lib
+    source ${activemq}/lib/classpath.env
+    export CLASSPATH
+    ln -s "${./ActiveMQBroker.java}" ActiveMQBroker.java
+    javac -d $out/lib ActiveMQBroker.java
+  '';
 
-in {
+in
+{
 
   options = {
     services.activemq = {
diff --git a/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix b/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
index 9d3243722d62..11dabf0b51c8 100644
--- a/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
@@ -136,7 +136,7 @@ in
       };
 
       managementPlugin = {
-        enable = mkEnableOption "the management plugin";
+        enable = mkEnableOption (lib.mdDoc "the management plugin");
         port = mkOption {
           default = 15672;
           type = types.port;
diff --git a/nixpkgs/nixos/modules/services/audio/botamusique.nix b/nixpkgs/nixos/modules/services/audio/botamusique.nix
index edb59a49fd1f..5d3f7db12bc9 100644
--- a/nixpkgs/nixos/modules/services/audio/botamusique.nix
+++ b/nixpkgs/nixos/modules/services/audio/botamusique.nix
@@ -12,7 +12,7 @@ in
   meta.maintainers = with lib.maintainers; [ hexa ];
 
   options.services.botamusique = {
-    enable = mkEnableOption "botamusique, a bot to play audio streams on mumble";
+    enable = mkEnableOption (lib.mdDoc "botamusique, a bot to play audio streams on mumble");
 
     package = mkOption {
       type = types.package;
@@ -103,9 +103,8 @@ in
         StateDirectory = "botamusique";
         SystemCallArchitectures = "native";
         SystemCallFilter = [
-          "@system-service"
+          "@system-service @resources"
           "~@privileged"
-          "~@resources"
         ];
         UMask = "0077";
         WorkingDirectory = "/var/lib/botamusique";
diff --git a/nixpkgs/nixos/modules/services/audio/gmediarender.nix b/nixpkgs/nixos/modules/services/audio/gmediarender.nix
new file mode 100644
index 000000000000..2f23232d19cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/gmediarender.nix
@@ -0,0 +1,116 @@
+{ pkgs, lib, config, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gmediarender;
+in
+{
+  options.services.gmediarender = {
+    enable = mkEnableOption (mdDoc "the gmediarender DLNA renderer");
+
+    audioDevice = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = mdDoc ''
+        The audio device to use.
+      '';
+    };
+
+    audioSink = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = mdDoc ''
+        The audio sink to use.
+      '';
+    };
+
+    friendlyName = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = mdDoc ''
+        A "friendly name" for identifying the endpoint.
+      '';
+    };
+
+    initialVolume = mkOption {
+      type = types.nullOr types.int;
+      default = 0;
+      description = mdDoc ''
+        A default volume attenuation (in dB) for the endpoint.
+      '';
+    };
+
+    package = mkPackageOptionMD pkgs "gmediarender" {
+      default = "gmrender-resurrect";
+    };
+
+    port = mkOption {
+      type = types.nullOr types.port;
+      default = null;
+      description = mdDoc "Port that will be used to accept client connections.";
+    };
+
+    uuid = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = mdDoc ''
+        A UUID for uniquely identifying the endpoint.  If you have
+        multiple renderers on your network, you MUST set this.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd = {
+      services.gmediarender = {
+        after = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        description = "gmediarender server daemon";
+        environment = {
+          XDG_CACHE_HOME = "%t/gmediarender";
+        };
+        serviceConfig = {
+          DynamicUser = true;
+          User = "gmediarender";
+          Group = "gmediarender";
+          SupplementaryGroups = [ "audio" ];
+          ExecStart =
+            "${cfg.package}/bin/gmediarender " +
+            optionalString (cfg.audioDevice != null) ("--gstout-audiodevice=${utils.escapeSystemdExecArg cfg.audioDevice} ") +
+            optionalString (cfg.audioSink != null) ("--gstout-audiosink=${utils.escapeSystemdExecArg cfg.audioSink} ") +
+            optionalString (cfg.friendlyName != null) ("--friendly-name=${utils.escapeSystemdExecArg cfg.friendlyName} ") +
+            optionalString (cfg.initialVolume != 0) ("--initial-volume=${toString cfg.initialVolume} ") +
+            optionalString (cfg.port != null) ("--port=${toString cfg.port} ") +
+            optionalString (cfg.uuid != null) ("--uuid=${utils.escapeSystemdExecArg cfg.uuid} ");
+          Restart = "always";
+          RuntimeDirectory = "gmediarender";
+
+          # Security options:
+          CapabilityBoundingSet = "";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          # PrivateDevices = true;
+          PrivateTmp = true;
+          PrivateUsers = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
+          UMask = 066;
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/audio/gonic.nix b/nixpkgs/nixos/modules/services/audio/gonic.nix
new file mode 100644
index 000000000000..65cf10f2c4b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/gonic.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gonic;
+  settingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
+    listsAsDuplicateKeys = true;
+  };
+in
+{
+  options = {
+    services.gonic = {
+
+      enable = mkEnableOption (lib.mdDoc "Gonic music server");
+
+      settings = mkOption rec {
+        type = settingsFormat.type;
+        apply = recursiveUpdate default;
+        default = {
+          listen-addr = "127.0.0.1:4747";
+          cache-path = "/var/cache/gonic";
+          tls-cert = null;
+          tls-key = null;
+        };
+        example = {
+          music-path = [ "/mnt/music" ];
+          podcast-path = "/mnt/podcasts";
+        };
+        description = lib.mdDoc ''
+          Configuration for Gonic, see <https://github.com/sentriz/gonic#configuration-options> for supported values.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.gonic = {
+      description = "Gonic Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart =
+          let
+            # these values are null by default but should not appear in the final config
+            filteredSettings = filterAttrs (n: v: !((n == "tls-cert" || n == "tls-key") && v == null)) cfg.settings;
+          in
+          "${pkgs.gonic}/bin/gonic -config-path ${settingsFormat.generate "gonic" filteredSettings}";
+        DynamicUser = true;
+        StateDirectory = "gonic";
+        CacheDirectory = "gonic";
+        WorkingDirectory = "/var/lib/gonic";
+        RuntimeDirectory = "gonic";
+        RootDirectory = "/run/gonic";
+        ReadWritePaths = "";
+        BindReadOnlyPaths = [
+          # gonic can access scrobbling services
+          "-/etc/ssl/certs/ca-certificates.crt"
+          builtins.storeDir
+          cfg.settings.podcast-path
+        ] ++ cfg.settings.music-path
+        ++ lib.optional (cfg.settings.tls-cert != null) cfg.settings.tls-cert
+        ++ lib.optional (cfg.settings.tls-key != null) cfg.settings.tls-key;
+        CapabilityBoundingSet = "";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+        RestrictRealtime = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        UMask = "0066";
+        ProtectHostname = true;
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.autrimpo ];
+}
diff --git a/nixpkgs/nixos/modules/services/audio/hqplayerd.nix b/nixpkgs/nixos/modules/services/audio/hqplayerd.nix
index 4045a34b40df..d54400b18e30 100644
--- a/nixpkgs/nixos/modules/services/audio/hqplayerd.nix
+++ b/nixpkgs/nixos/modules/services/audio/hqplayerd.nix
@@ -12,7 +12,7 @@ in
 {
   options = {
     services.hqplayerd = {
-      enable = mkEnableOption "HQPlayer Embedded";
+      enable = mkEnableOption (lib.mdDoc "HQPlayer Embedded");
 
       auth = {
         username = mkOption {
@@ -82,7 +82,6 @@ in
       etc = {
         "hqplayer/hqplayerd.xml" = mkIf (cfg.config != null) { source = pkgs.writeText "hqplayerd.xml" cfg.config; };
         "hqplayer/hqplayerd4-key.xml" = mkIf (cfg.licenseFile != null) { source = cfg.licenseFile; };
-        "modules-load.d/taudio2.conf".source = "${pkg}/etc/modules-load.d/taudio2.conf";
       };
       systemPackages = [ pkg ];
     };
@@ -91,8 +90,6 @@ in
       allowedTCPPorts = [ 8088 4321 ];
     };
 
-    services.udev.packages = [ pkg ];
-
     systemd = {
       tmpfiles.rules = [
         "d ${configDir}      0755 hqplayer hqplayer - -"
diff --git a/nixpkgs/nixos/modules/services/audio/icecast.nix b/nixpkgs/nixos/modules/services/audio/icecast.nix
index 0a81d71b5690..63049bd93ab9 100644
--- a/nixpkgs/nixos/modules/services/audio/icecast.nix
+++ b/nixpkgs/nixos/modules/services/audio/icecast.nix
@@ -44,11 +44,11 @@ in {
 
     services.icecast = {
 
-      enable = mkEnableOption "Icecast server";
+      enable = mkEnableOption (lib.mdDoc "Icecast server");
 
       hostname = mkOption {
         type = types.nullOr types.str;
-        description = lib.mdDoc "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.";
+        description = lib.mdDoc "DNS name or IP address that will be used for the stream directory lookups or possibly the playlist generation if a Host header is not provided.";
         default = config.networking.domain;
         defaultText = literalExpression "config.networking.domain";
       };
@@ -74,7 +74,7 @@ in {
 
       listen = {
         port = mkOption {
-          type = types.int;
+          type = types.port;
           description = lib.mdDoc "TCP port that will be used to accept client connections.";
           default = 8000;
         };
diff --git a/nixpkgs/nixos/modules/services/audio/jack.nix b/nixpkgs/nixos/modules/services/audio/jack.nix
index ae566bba84e4..105e99cb2f5e 100644
--- a/nixpkgs/nixos/modules/services/audio/jack.nix
+++ b/nixpkgs/nixos/modules/services/audio/jack.nix
@@ -16,9 +16,9 @@ in {
   options = {
     services.jack = {
       jackd = {
-        enable = mkEnableOption ''
+        enable = mkEnableOption (lib.mdDoc ''
           JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group
-        '';
+        '');
 
         package = mkOption {
           # until jack1 promiscuous mode is fixed
@@ -27,7 +27,7 @@ in {
           default = pkgs.jack2;
           defaultText = literalExpression "pkgs.jack2";
           example = literalExpression "pkgs.jack1";
-          description = ''
+          description = lib.mdDoc ''
             The JACK package to use.
           '';
         };
diff --git a/nixpkgs/nixos/modules/services/audio/jmusicbot.nix b/nixpkgs/nixos/modules/services/audio/jmusicbot.nix
index 7e23ffe6bf2c..c6392c679c04 100644
--- a/nixpkgs/nixos/modules/services/audio/jmusicbot.nix
+++ b/nixpkgs/nixos/modules/services/audio/jmusicbot.nix
@@ -7,7 +7,7 @@ in
 {
   options = {
     services.jmusicbot = {
-      enable = mkEnableOption "jmusicbot, a Discord music bot that's easy to set up and run yourself";
+      enable = mkEnableOption (lib.mdDoc "jmusicbot, a Discord music bot that's easy to set up and run yourself");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/audio/liquidsoap.nix b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
index c313104c4609..5c10d13af5fd 100644
--- a/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
+++ b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
@@ -38,11 +38,13 @@ in
 
       default = {};
 
-      example = {
-        myStream1 = "/etc/liquidsoap/myStream1.liq";
-        myStream2 = literalExpression "./myStream2.liq";
-        myStream3 = "out(playlist(\"/srv/music/\"))";
-      };
+      example = literalExpression ''
+        {
+          myStream1 = "/etc/liquidsoap/myStream1.liq";
+          myStream2 = ./myStream2.liq;
+          myStream3 = "out(playlist(\"/srv/music/\"))";
+        }
+      '';
 
       type = types.attrsOf (types.either types.path types.str);
     };
diff --git a/nixpkgs/nixos/modules/services/audio/mopidy.nix b/nixpkgs/nixos/modules/services/audio/mopidy.nix
index 9c8e9b693c3f..40e8679f53d7 100644
--- a/nixpkgs/nixos/modules/services/audio/mopidy.nix
+++ b/nixpkgs/nixos/modules/services/audio/mopidy.nix
@@ -14,7 +14,7 @@ let
     name = "mopidy-with-extensions-${mopidy.version}";
     paths = closePropagation cfg.extensionPackages;
     pathsToLink = [ "/${mopidyPackages.python.sitePackages}" ];
-    buildInputs = [ makeWrapper ];
+    nativeBuildInputs = [ makeWrapper ];
     postBuild = ''
       makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \
         --prefix PYTHONPATH : $out/${mopidyPackages.python.sitePackages}
@@ -26,7 +26,7 @@ in {
 
     services.mopidy = {
 
-      enable = mkEnableOption "Mopidy, a music player daemon";
+      enable = mkEnableOption (lib.mdDoc "Mopidy, a music player daemon");
 
       dataDir = mkOption {
         default = "/var/lib/mopidy";
diff --git a/nixpkgs/nixos/modules/services/audio/mpd.nix b/nixpkgs/nixos/modules/services/audio/mpd.nix
index bbfccec98c4a..3c853973c872 100644
--- a/nixpkgs/nixos/modules/services/audio/mpd.nix
+++ b/nixpkgs/nixos/modules/services/audio/mpd.nix
@@ -102,7 +102,7 @@ in {
           Extra directives added to to the end of MPD's configuration file,
           mpd.conf. Basic configuration like file location and uid/gid
           is added automatically to the beginning of the file. For available
-          options see `man 5 mpd.conf`'.
+          options see {manpage}`mpd.conf(5)`.
         '';
       };
 
@@ -142,7 +142,7 @@ in {
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 6600;
           description = lib.mdDoc ''
             This setting is the TCP port that is desired for the daemon to get assigned
diff --git a/nixpkgs/nixos/modules/services/audio/mpdscribble.nix b/nixpkgs/nixos/modules/services/audio/mpdscribble.nix
index d829bc7ae0d0..132d9ad32588 100644
--- a/nixpkgs/nixos/modules/services/audio/mpdscribble.nix
+++ b/nixpkgs/nixos/modules/services/audio/mpdscribble.nix
@@ -77,7 +77,7 @@ in {
 
   options.services.mpdscribble = {
 
-    enable = mkEnableOption "mpdscribble";
+    enable = mkEnableOption (lib.mdDoc "mpdscribble");
 
     proxy = mkOption {
       default = null;
@@ -128,9 +128,9 @@ in {
           mpdCfg.credentials).passwordFile
       else
         null;
-      defaultText = literalDocBook ''
+      defaultText = literalMD ''
         The first password file with read access configured for MPD when using a local instance,
-        otherwise <literal>null</literal>.
+        otherwise `null`.
       '';
       type = types.nullOr types.str;
       description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/services/audio/navidrome.nix b/nixpkgs/nixos/modules/services/audio/navidrome.nix
index a7c8953f5107..e18e61eb6d44 100644
--- a/nixpkgs/nixos/modules/services/audio/navidrome.nix
+++ b/nixpkgs/nixos/modules/services/audio/navidrome.nix
@@ -9,7 +9,9 @@ in {
   options = {
     services.navidrome = {
 
-      enable = mkEnableOption "Navidrome music server";
+      enable = mkEnableOption (lib.mdDoc "Navidrome music server");
+
+      package = mkPackageOptionMD pkgs "navidrome" { };
 
       settings = mkOption rec {
         type = settingsFormat.type;
@@ -36,7 +38,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.navidrome}/bin/navidrome --configfile ${settingsFormat.generate "navidrome.json" cfg.settings}
+          ${cfg.package}/bin/navidrome --configfile ${settingsFormat.generate "navidrome.json" cfg.settings}
         '';
         DynamicUser = true;
         StateDirectory = "navidrome";
@@ -62,7 +64,7 @@ in {
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
         RestrictRealtime = true;
         LockPersonality = true;
         MemoryDenyWriteExecute = true;
diff --git a/nixpkgs/nixos/modules/services/audio/networkaudiod.nix b/nixpkgs/nixos/modules/services/audio/networkaudiod.nix
index 265a4e1d95d6..11486429e667 100644
--- a/nixpkgs/nixos/modules/services/audio/networkaudiod.nix
+++ b/nixpkgs/nixos/modules/services/audio/networkaudiod.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.networkaudiod = {
-      enable = mkEnableOption "Networkaudiod (NAA)";
+      enable = mkEnableOption (lib.mdDoc "Networkaudiod (NAA)");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/audio/roon-bridge.nix b/nixpkgs/nixos/modules/services/audio/roon-bridge.nix
index 9a9a6479efc8..70392b647cc6 100644
--- a/nixpkgs/nixos/modules/services/audio/roon-bridge.nix
+++ b/nixpkgs/nixos/modules/services/audio/roon-bridge.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.roon-bridge = {
-      enable = mkEnableOption "Roon Bridge";
+      enable = mkEnableOption (lib.mdDoc "Roon Bridge");
       openFirewall = mkOption {
         type = types.bool;
         default = false;
@@ -42,7 +42,7 @@ in {
       environment.ROON_DATAROOT = "/var/lib/${name}";
 
       serviceConfig = {
-        ExecStart = "${pkgs.roon-bridge}/start.sh";
+        ExecStart = "${pkgs.roon-bridge}/bin/RoonBridge";
         LimitNOFILE = 8192;
         User = cfg.user;
         Group = cfg.group;
@@ -53,13 +53,18 @@ in {
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPortRanges = [{ from = 9100; to = 9200; }];
       allowedUDPPorts = [ 9003 ];
-      extraCommands = ''
+      extraCommands = optionalString (!config.networking.nftables.enable) ''
         iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
         iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
         iptables -A INPUT -s 240.0.0.0/5 -j ACCEPT
         iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
         iptables -A INPUT -m pkttype --pkt-type broadcast -j ACCEPT
       '';
+      extraInputRules = optionalString config.networking.nftables.enable ''
+        ip saddr { 224.0.0.0/4, 240.0.0.0/5 } accept
+        ip daddr 224.0.0.0/4 accept
+        pkttype { multicast, broadcast } accept
+      '';
     };
 
 
diff --git a/nixpkgs/nixos/modules/services/audio/roon-server.nix b/nixpkgs/nixos/modules/services/audio/roon-server.nix
index 535950f75656..fbe74f63b9da 100644
--- a/nixpkgs/nixos/modules/services/audio/roon-server.nix
+++ b/nixpkgs/nixos/modules/services/audio/roon-server.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.roon-server = {
-      enable = mkEnableOption "Roon Server";
+      enable = mkEnableOption (lib.mdDoc "Roon Server");
       openFirewall = mkOption {
         type = types.bool;
         default = false;
@@ -40,6 +40,7 @@ in {
       wantedBy = [ "multi-user.target" ];
 
       environment.ROON_DATAROOT = "/var/lib/${name}";
+      environment.ROON_ID_DIR = "/var/lib/${name}";
 
       serviceConfig = {
         ExecStart = "${pkgs.roon-server}/bin/RoonServer";
@@ -57,7 +58,7 @@ in {
         { from = 30000; to = 30010; }
       ];
       allowedUDPPorts = [ 9003 ];
-      extraCommands = ''
+      extraCommands = optionalString (!config.networking.nftables.enable) ''
         ## IGMP / Broadcast ##
         iptables -A INPUT -s 224.0.0.0/4 -j ACCEPT
         iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT
@@ -65,6 +66,11 @@ in {
         iptables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
         iptables -A INPUT -m pkttype --pkt-type broadcast -j ACCEPT
       '';
+      extraInputRules = optionalString config.networking.nftables.enable ''
+        ip saddr { 224.0.0.0/4, 240.0.0.0/5 } accept
+        ip daddr 224.0.0.0/4 accept
+        pkttype { multicast, broadcast } accept
+      '';
     };
 
 
diff --git a/nixpkgs/nixos/modules/services/audio/snapserver.nix b/nixpkgs/nixos/modules/services/audio/snapserver.nix
index fdc1f605bb32..dbab741bf6fc 100644
--- a/nixpkgs/nixos/modules/services/audio/snapserver.nix
+++ b/nixpkgs/nixos/modules/services/audio/snapserver.nix
@@ -101,9 +101,7 @@ in {
 
       openFirewall = mkOption {
         type = types.bool;
-        # Make the behavior consistent with other services. Set the default to
-        # false and remove the accompanying warning after NixOS 22.05 is released.
-        default = true;
+        default = false;
         description = lib.mdDoc ''
           Whether to automatically open the specified ports in the firewall.
         '';
@@ -277,14 +275,9 @@ in {
 
     warnings =
       # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
-      filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
+      filter (w: w != "") (mapAttrsToList (k: v: optionalString (v.type == "spotify") ''
         services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
-      '' else "") cfg.streams)
-      # Remove this warning after NixOS 22.05 is released.
-      ++ optional (options.services.snapserver.openFirewall.highestPrio >= (mkOptionDefault null).priority) ''
-        services.snapserver.openFirewall will no longer default to true starting with NixOS 22.11.
-        Enable it explicitly if you need to control Snapserver remotely.
-      '';
+      '') cfg.streams);
 
     systemd.services.snapserver = {
       after = [ "network.target" ];
diff --git a/nixpkgs/nixos/modules/services/audio/spotifyd.nix b/nixpkgs/nixos/modules/services/audio/spotifyd.nix
index 87ee083e74b7..975be5a87cba 100644
--- a/nixpkgs/nixos/modules/services/audio/spotifyd.nix
+++ b/nixpkgs/nixos/modules/services/audio/spotifyd.nix
@@ -17,7 +17,7 @@ in
 {
   options = {
     services.spotifyd = {
-      enable = mkEnableOption "spotifyd, a Spotify playing daemon";
+      enable = mkEnableOption (lib.mdDoc "spotifyd, a Spotify playing daemon");
 
       config = mkOption {
         default = "";
diff --git a/nixpkgs/nixos/modules/services/audio/squeezelite.nix b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
index 767eeda177f3..30dc12552f00 100644
--- a/nixpkgs/nixos/modules/services/audio/squeezelite.nix
+++ b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
@@ -14,9 +14,9 @@ in
   ###### interface
 
   options.services.squeezelite = {
-    enable = mkEnableOption "Squeezelite, a software Squeezebox emulator";
+    enable = mkEnableOption (lib.mdDoc "Squeezelite, a software Squeezebox emulator");
 
-    pulseAudio = mkEnableOption "pulseaudio support";
+    pulseAudio = mkEnableOption (lib.mdDoc "pulseaudio support");
 
     extraArguments = mkOption {
       default = "";
diff --git a/nixpkgs/nixos/modules/services/audio/tts.nix b/nixpkgs/nixos/modules/services/audio/tts.nix
new file mode 100644
index 000000000000..1a355c8ee39f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/tts.nix
@@ -0,0 +1,151 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.tts;
+in
+
+{
+  options.services.tts = let
+    inherit (lib) literalExpression mkOption mdDoc mkEnableOption types;
+  in  {
+    servers = mkOption {
+      type = types.attrsOf (types.submodule (
+        { ... }: {
+          options = {
+            enable = mkEnableOption (mdDoc "Coqui TTS server");
+
+            port = mkOption {
+              type = types.port;
+              example = 5000;
+              description = mdDoc ''
+                Port to bind the TTS server to.
+              '';
+            };
+
+            model = mkOption {
+              type = types.nullOr types.str;
+              default = "tts_models/en/ljspeech/tacotron2-DDC";
+              example = null;
+              description = mdDoc ''
+                Name of the model to download and use for speech synthesis.
+
+                Check `tts-server --list_models` for possible values.
+
+                Set to `null` to use a custom model.
+              '';
+            };
+
+            useCuda = mkOption {
+              type = types.bool;
+              default = false;
+              example = true;
+              description = mdDoc ''
+                Whether to offload computation onto a CUDA compatible GPU.
+              '';
+            };
+
+            extraArgs = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = mdDoc ''
+                Extra arguments to pass to the server commandline.
+              '';
+            };
+          };
+        }
+      ));
+      default = {};
+      example = literalExpression ''
+        {
+          english = {
+            port = 5300;
+            model = "tts_models/en/ljspeech/tacotron2-DDC";
+          };
+          german = {
+            port = 5301;
+            model = "tts_models/de/thorsten/tacotron2-DDC";
+          };
+          dutch = {
+            port = 5302;
+            model = "tts_models/nl/mai/tacotron2-DDC";
+          };
+        }
+      '';
+      description = mdDoc ''
+        TTS server instances.
+      '';
+    };
+  };
+
+  config = let
+    inherit (lib) mkIf mapAttrs' nameValuePair optionalString concatMapStringsSep escapeShellArgs;
+  in mkIf (cfg.servers != {}) {
+    systemd.services = mapAttrs' (server: options:
+      nameValuePair "tts-${server}" {
+        description = "Coqui TTS server instance ${server}";
+        after = [
+          "network-online.target"
+        ];
+        wantedBy = [
+          "multi-user.target"
+        ];
+        path = with pkgs; [
+          espeak-ng
+        ];
+        environment.HOME = "/var/lib/tts";
+        serviceConfig = {
+          DynamicUser = true;
+          User = "tts";
+          StateDirectory = "tts";
+          ExecStart = "${pkgs.tts}/bin/tts-server --port ${toString options.port}"
+            + optionalString (options.model != null) " --model_name ${options.model}"
+            + optionalString (options.useCuda) " --use_cuda"
+            + (concatMapStringsSep " " escapeShellArgs options.extraArgs);
+          CapabilityBoundingSet = "";
+          DeviceAllow = if options.useCuda then [
+            # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            "/dev/nvidia1"
+            "/dev/nvidia2"
+            "/dev/nvidia3"
+            "/dev/nvidia4"
+            "/dev/nvidia-caps/nvidia-cap1"
+            "/dev/nvidia-caps/nvidia-cap2"
+            "/dev/nvidiactl"
+            "/dev/nvidia-modeset"
+            "/dev/nvidia-uvm"
+            "/dev/nvidia-uvm-tools"
+          ] else "";
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          # jit via numba->llvmpipe
+          MemoryDenyWriteExecute = false;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectControlGroups = true;
+          ProtectProc = "invisible";
+          ProcSubset = "pid";
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@system-service"
+            "~@privileged"
+          ];
+          UMask = "0077";
+        };
+      }) cfg.servers;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/audio/ympd.nix b/nixpkgs/nixos/modules/services/audio/ympd.nix
index 98522f254239..b74cc3f9c0b4 100644
--- a/nixpkgs/nixos/modules/services/audio/ympd.nix
+++ b/nixpkgs/nixos/modules/services/audio/ympd.nix
@@ -12,7 +12,7 @@ in {
 
     services.ympd = {
 
-      enable = mkEnableOption "ympd, the MPD Web GUI";
+      enable = mkEnableOption (lib.mdDoc "ympd, the MPD Web GUI");
 
       webPort = mkOption {
         type = types.either types.str types.port; # string for backwards compat
@@ -29,7 +29,7 @@ in {
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = config.services.mpd.network.port;
           defaultText = literalExpression "config.services.mpd.network.port";
           description = lib.mdDoc "The port where MPD is listening.";
@@ -48,8 +48,46 @@ in {
 
     systemd.services.ympd = {
       description = "Standalone MPD Web GUI written in C";
+
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${pkgs.ympd}/bin/ympd --host ${cfg.mpd.host} --port ${toString cfg.mpd.port} --webport ${toString cfg.webPort} --user nobody";
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.ympd}/bin/ympd \
+            --host ${cfg.mpd.host} \
+            --port ${toString cfg.mpd.port} \
+            --webport ${toString cfg.webPort}
+        '';
+
+        DynamicUser = true;
+        NoNewPrivileges = true;
+
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
+        SystemCallFilter = [
+          "@system-service"
+          "~@process"
+          "~@setuid"
+        ];
+      };
     };
 
   };
diff --git a/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix b/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
index 194b49da539a..27bbff813b10 100644
--- a/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
+++ b/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
@@ -3,7 +3,7 @@
 let
 
   inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExpression;
-  inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption optional types;
+  inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption mkRenamedOptionModule optional types;
 
   cfg = config.services.automysqlbackup;
   pkg = pkgs.automysqlbackup;
@@ -26,11 +26,15 @@ let
 
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "automysqlbackup" "config" ] [ "services" "automysqlbackup" "settings" ])
+  ];
+
   # interface
   options = {
     services.automysqlbackup = {
 
-      enable = mkEnableOption "AutoMySQLBackup";
+      enable = mkEnableOption (lib.mdDoc "AutoMySQLBackup");
 
       calendar = mkOption {
         type = types.str;
@@ -40,7 +44,7 @@ in
         '';
       };
 
-      config = mkOption {
+      settings = mkOption {
         type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
         default = {};
         description = lib.mdDoc ''
@@ -112,7 +116,18 @@ in
 
     services.mysql.ensureUsers = optional (config.services.mysql.enable && cfg.config.mysql_dump_host == "localhost") {
       name = user;
-      ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT"; };
+      ensurePermissions = {
+        "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT";
+
+        # https://forums.mysql.com/read.php?10,668311,668315#msg-668315
+        "function sys.extract_table_from_file_name" = "execute";
+        "function sys.format_path" = "execute";
+        "function sys.format_statement" = "execute";
+        "function sys.extract_schema_from_file_name" = "execute";
+        "function sys.ps_thread_account" = "execute";
+        "function sys.format_time" = "execute";
+        "function sys.format_bytes" = "execute";
+      };
     };
 
   };
diff --git a/nixpkgs/nixos/modules/services/backup/bacula.nix b/nixpkgs/nixos/modules/services/backup/bacula.nix
index cb8a6eb4390c..0acbf1b3eabb 100644
--- a/nixpkgs/nixos/modules/services/backup/bacula.nix
+++ b/nixpkgs/nixos/modules/services/backup/bacula.nix
@@ -195,7 +195,7 @@ let
       };
 
       devices = mkOption {
-        description = "";
+        description = lib.mdDoc "";
         type = types.listOf types.str;
       };
 
@@ -314,7 +314,7 @@ in {
 
       port = mkOption {
         default = 9102;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           This specifies the port number on which the Client listens for
           Director connections. It must agree with the FDPort specified in
@@ -374,7 +374,7 @@ in {
 
       port = mkOption {
         default = 9103;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           Specifies port number on which the Storage daemon listens for
           Director connections.
@@ -451,7 +451,7 @@ in {
 
       port = mkOption {
         default = 9101;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           Specify the port (a positive integer) on which the Director daemon
           will listen for Bacula Console connections. This same port number
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.md b/nixpkgs/nixos/modules/services/backup/borgbackup.md
new file mode 100644
index 000000000000..39141f6ec858
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/borgbackup.md
@@ -0,0 +1,163 @@
+# BorgBackup {#module-borgbase}
+
+*Source:* {file}`modules/services/backup/borgbackup.nix`
+
+*Upstream documentation:* <https://borgbackup.readthedocs.io/>
+
+[BorgBackup](https://www.borgbackup.org/) (short: Borg)
+is a deduplicating backup program. Optionally, it supports compression and
+authenticated encryption.
+
+The main goal of Borg is to provide an efficient and secure way to backup
+data. The data deduplication technique used makes Borg suitable for daily
+backups since only changes are stored. The authenticated encryption technique
+makes it suitable for backups to not fully trusted targets.
+
+## Configuring {#module-services-backup-borgbackup-configuring}
+
+A complete list of options for the Borgbase module may be found
+[here](#opt-services.borgbackup.jobs).
+
+## Basic usage for a local backup {#opt-services-backup-borgbackup-local-directory}
+
+A very basic configuration for backing up to a locally accessible directory is:
+```
+{
+    opt.services.borgbackup.jobs = {
+      { rootBackup = {
+          paths = "/";
+          exclude = [ "/nix" "/path/to/local/repo" ];
+          repo = "/path/to/local/repo";
+          doInit = true;
+          encryption = {
+            mode = "repokey";
+            passphrase = "secret";
+          };
+          compression = "auto,lzma";
+          startAt = "weekly";
+        };
+      }
+    };
+}
+```
+
+::: {.warning}
+If you do not want the passphrase to be stored in the world-readable
+Nix store, use passCommand. You find an example below.
+:::
+
+## Create a borg backup server {#opt-services-backup-create-server}
+
+You should use a different SSH key for each repository you write to,
+because the specified keys are restricted to running borg serve and can only
+access this single repository. You need the output of the generate pub file.
+
+```ShellSession
+# sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_my_borg_repo
+# cat /run/keys/id_ed25519_my_borg_repo
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos
+```
+
+Add the following snippet to your NixOS configuration:
+```
+{
+  services.borgbackup.repos = {
+    my_borg_repo = {
+      authorizedKeys = [
+        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos"
+      ] ;
+      path = "/var/lib/my_borg_repo" ;
+    };
+  };
+}
+```
+
+## Backup to the borg repository server {#opt-services-backup-borgbackup-remote-server}
+
+The following NixOS snippet creates an hourly backup to the service
+(on the host nixos) as created in the section above. We assume
+that you have stored a secret passphrasse in the file
+{file}`/run/keys/borgbackup_passphrase`, which should be only
+accessible by root
+
+```
+{
+  services.borgbackup.jobs = {
+    backupToLocalServer = {
+      paths = [ "/etc/nixos" ];
+      doInit = true;
+      repo =  "borg@nixos:." ;
+      encryption = {
+        mode = "repokey-blake2";
+        passCommand = "cat /run/keys/borgbackup_passphrase";
+      };
+      environment = { BORG_RSH = "ssh -i /run/keys/id_ed25519_my_borg_repo"; };
+      compression = "auto,lzma";
+      startAt = "hourly";
+    };
+  };
+};
+```
+
+The following few commands (run as root) let you test your backup.
+```
+> nixos-rebuild switch
+...restarting the following units: polkit.service
+> systemctl restart borgbackup-job-backupToLocalServer
+> sleep 10
+> systemctl restart borgbackup-job-backupToLocalServer
+> export BORG_PASSPHRASE=topSecrect
+> borg list --rsh='ssh -i /run/keys/id_ed25519_my_borg_repo' borg@nixos:.
+nixos-backupToLocalServer-2020-03-30T21:46:17 Mon, 2020-03-30 21:46:19 [84feb97710954931ca384182f5f3cb90665f35cef214760abd7350fb064786ac]
+nixos-backupToLocalServer-2020-03-30T21:46:30 Mon, 2020-03-30 21:46:32 [e77321694ecd160ca2228611747c6ad1be177d6e0d894538898de7a2621b6e68]
+```
+
+## Backup to a hosting service {#opt-services-backup-borgbackup-borgbase}
+
+Several companies offer [(paid) hosting services](https://www.borgbackup.org/support/commercial.html)
+for Borg repositories.
+
+To backup your home directory to borgbase you have to:
+
+  - Generate a SSH key without a password, to access the remote server. E.g.
+
+        sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_borgbase
+
+  - Create the repository on the server by following the instructions for your
+    hosting server.
+  - Initialize the repository on the server. Eg.
+
+        sudo borg init --encryption=repokey-blake2  \
+            --rsh "ssh -i /run/keys/id_ed25519_borgbase" \
+            zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo
+
+  - Add it to your NixOS configuration, e.g.
+
+        {
+            services.borgbackup.jobs = {
+            my_Remote_Backup = {
+                paths = [ "/" ];
+                exclude = [ "/nix" "'**/.cache'" ];
+                repo =  "zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo";
+                  encryption = {
+                  mode = "repokey-blake2";
+                  passCommand = "cat /run/keys/borgbackup_passphrase";
+                };
+                environment = { BORG_RSH = "ssh -i /run/keys/id_ed25519_borgbase"; };
+                compression = "auto,lzma";
+                startAt = "daily";
+            };
+          };
+        }}
+
+## Vorta backup client for the desktop {#opt-services-backup-borgbackup-vorta}
+
+Vorta is a backup client for macOS and Linux desktops. It integrates the
+mighty BorgBackup with your desktop environment to protect your data from
+disk failure, ransomware and theft.
+
+It can be installed in NixOS e.g. by adding `pkgs.vorta`
+to [](#opt-environment.systemPackages).
+
+Details about using Vorta can be found under
+[https://vorta.borgbase.com](https://vorta.borgbase.com/usage) .
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.nix b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
index f02e15f2b988..0da70112d48d 100644
--- a/nixpkgs/nixos/modules/services/backup/borgbackup.nix
+++ b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
@@ -11,7 +11,11 @@ let
 
   mkExcludeFile = cfg:
     # Write each exclude pattern to a new line
-    pkgs.writeText "excludefile" (concatStringsSep "\n" cfg.exclude);
+    pkgs.writeText "excludefile" (concatMapStrings (s: s + "\n") cfg.exclude);
+
+  mkPatternsFile = cfg:
+    # Write each pattern to a new line
+    pkgs.writeText "patternsfile" (concatMapStrings (s: s + "\n") cfg.patterns);
 
   mkKeepArgs = cfg:
     # If cfg.prune.keep e.g. has a yearly attribute,
@@ -19,7 +23,8 @@ let
     concatStringsSep " "
       (mapAttrsToList (x: y: "--keep-${x}=${toString y}") cfg.prune.keep);
 
-  mkBackupScript = cfg: ''
+  mkBackupScript = name: cfg: pkgs.writeShellScript "${name}-script" (''
+    set -e
     on_exit()
     {
       exitStatus=$?
@@ -46,6 +51,7 @@ let
       borg create $extraArgs \
         --compression ${cfg.compression} \
         --exclude-from ${mkExcludeFile cfg} \
+        --patterns-from ${mkPatternsFile cfg} \
         $extraCreateArgs \
         "::$archiveName$archiveSuffix" \
         ${if cfg.paths == null then "-" else escapeShellArgs cfg.paths}
@@ -58,10 +64,11 @@ let
   '' + optionalString (cfg.prune.keep != { }) ''
     borg prune $extraArgs \
       ${mkKeepArgs cfg} \
-      ${optionalString (cfg.prune.prefix != null) "--prefix ${escapeShellArg cfg.prune.prefix} \\"}
+      ${optionalString (cfg.prune.prefix != null) "--glob-archives ${escapeShellArg "${cfg.prune.prefix}*"}"} \
       $extraPruneArgs
+    borg compact $extraArgs $extraCompactArgs
     ${cfg.postPrune}
-  '';
+  '');
 
   mkPassEnv = cfg: with cfg.encryption;
     if passCommand != null then
@@ -73,12 +80,19 @@ let
   mkBackupService = name: cfg:
     let
       userHome = config.users.users.${cfg.user}.home;
-    in nameValuePair "borgbackup-job-${name}" {
+      backupJobName = "borgbackup-job-${name}";
+      backupScript = mkBackupScript backupJobName cfg;
+    in nameValuePair backupJobName {
       description = "BorgBackup job ${name}";
       path = with pkgs; [
         borgbackup openssh
       ];
-      script = mkBackupScript cfg;
+      script = "exec " + optionalString cfg.inhibitsSleep ''\
+        ${pkgs.systemd}/bin/systemd-inhibit \
+            --who="borgbackup" \
+            --what="sleep" \
+            --why="Scheduled backup" \
+        '' + backupScript;
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
@@ -95,7 +109,7 @@ let
       };
       environment = {
         BORG_REPO = cfg.repo;
-        inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs;
+        inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs extraCompactArgs;
       } // (mkPassEnv cfg) // cfg.environment;
     };
 
@@ -116,7 +130,7 @@ let
       original, name, set ? {}
     }:
     pkgs.runCommand "${name}-wrapper" {
-      buildInputs = [ pkgs.makeWrapper ];
+      nativeBuildInputs = [ pkgs.makeWrapper ];
     } (with lib; ''
       makeWrapper "${original}" "$out/bin/${name}" \
         ${concatStringsSep " \\\n " (mapAttrsToList (name: value: ''--set ${name} "${value}"'') set)}
@@ -137,8 +151,9 @@ let
         # Ensure that the home directory already exists
         # We can't assert createHome == true because that's not the case for root
         cd "${config.users.users.${cfg.user}.home}"
-        ${install} -d .config/borg
-        ${install} -d .cache/borg
+        # Create each directory separately to prevent root owned parent dirs
+        ${install} -d .config .config/borg
+        ${install} -d .cache .cache/borg
       '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) ''
         ${install} -d ${escapeShellArg cfg.repo}
       ''));
@@ -212,7 +227,7 @@ let
 
 in {
   meta.maintainers = with maintainers; [ dotlambda ];
-  meta.doc = ./borgbackup.xml;
+  meta.doc = ./borgbackup.md;
 
   ###### interface
 
@@ -341,6 +356,15 @@ in {
             '';
           };
 
+          inhibitsSleep = mkOption {
+            default = false;
+            type = types.bool;
+            example = true;
+            description = lib.mdDoc ''
+              Prevents the system from sleeping while backing up.
+            '';
+          };
+
           user = mkOption {
             type = types.str;
             description = lib.mdDoc ''
@@ -424,6 +448,21 @@ in {
             ];
           };
 
+          patterns = mkOption {
+            type = with types; listOf str;
+            description = lib.mdDoc ''
+              Include/exclude paths matching the given patterns. The first
+              matching patterns is used, so if an include pattern (prefix `+`)
+              matches before an exclude pattern (prefix `-`), the file is
+              backed up. See [{command}`borg help patterns`](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-patterns) for pattern syntax.
+            '';
+            default = [ ];
+            example = [
+              "+ /home/susan"
+              "- /home/*"
+            ];
+          };
+
           readWritePaths = mkOption {
             type = with types; listOf path;
             description = lib.mdDoc ''
@@ -600,6 +639,15 @@ in {
             example = "--save-space";
           };
 
+          extraCompactArgs = mkOption {
+            type = types.str;
+            description = lib.mdDoc ''
+              Additional arguments for {command}`borg compact`.
+              Can also be set at runtime using `$extraCompactArgs`.
+            '';
+            default = "";
+            example = "--cleanup-commits";
+          };
         };
       }
     ));
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.xml b/nixpkgs/nixos/modules/services/backup/borgbackup.xml
deleted file mode 100644
index 8f623c936568..000000000000
--- a/nixpkgs/nixos/modules/services/backup/borgbackup.xml
+++ /dev/null
@@ -1,209 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-borgbase">
- <title>BorgBackup</title>
-  <para>
-  <emphasis>Source:</emphasis>
-  <filename>modules/services/backup/borgbackup.nix</filename>
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis>
-  <link xlink:href="https://borgbackup.readthedocs.io/"/>
- </para>
- <para>
-  <link xlink:href="https://www.borgbackup.org/">BorgBackup</link> (short: Borg)
-  is a deduplicating backup program. Optionally, it supports compression and
-  authenticated encryption.
-  </para>
-  <para>
-  The main goal of Borg is to provide an efficient and secure way to backup
-  data. The data deduplication technique used makes Borg suitable for daily
-  backups since only changes are stored. The authenticated encryption technique
-  makes it suitable for backups to not fully trusted targets.
- </para>
-  <section xml:id="module-services-backup-borgbackup-configuring">
-  <title>Configuring</title>
-  <para>
-   A complete list of options for the Borgbase module may be found
-   <link linkend="opt-services.borgbackup.jobs">here</link>.
-  </para>
-</section>
- <section xml:id="opt-services-backup-borgbackup-local-directory">
-  <title>Basic usage for a local backup</title>
-
-  <para>
-   A very basic configuration for backing up to a locally accessible directory
-   is:
-<programlisting>
-{
-    opt.services.borgbackup.jobs = {
-      { rootBackup = {
-          paths = "/";
-          exclude = [ "/nix" "/path/to/local/repo" ];
-          repo = "/path/to/local/repo";
-          doInit = true;
-          encryption = {
-            mode = "repokey";
-            passphrase = "secret";
-          };
-          compression = "auto,lzma";
-          startAt = "weekly";
-        };
-      }
-    };
-}</programlisting>
-  </para>
-  <warning>
-    <para>
-        If you do not want the passphrase to be stored in the world-readable
-        Nix store, use passCommand. You find an example below.
-    </para>
-  </warning>
- </section>
-<section xml:id="opt-services-backup-create-server">
-  <title>Create a borg backup server</title>
-  <para>You should use a different SSH key for each repository you write to,
-    because the specified keys are restricted to running borg serve and can only
-    access this single repository. You need the output of the generate pub file.
-  </para>
-    <para>
-<screen>
-<prompt># </prompt>sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_my_borg_repo
-<prompt># </prompt>cat /run/keys/id_ed25519_my_borg_repo
-ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos</screen>
-    </para>
-    <para>
-      Add the following snippet to your NixOS configuration:
-      <programlisting>
-{
-  services.borgbackup.repos = {
-    my_borg_repo = {
-      authorizedKeys = [
-        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos"
-      ] ;
-      path = "/var/lib/my_borg_repo" ;
-    };
-  };
-}</programlisting>
-    </para>
-</section>
-
- <section xml:id="opt-services-backup-borgbackup-remote-server">
-  <title>Backup to the borg repository server</title>
-  <para>The following NixOS snippet creates an hourly backup to the service
-    (on the host nixos) as created in the section above. We assume
-    that you have stored a secret passphrasse in the file
-    <code>/run/keys/borgbackup_passphrase</code>, which should be only
-    accessible by root
-  </para>
-  <para>
-      <programlisting>
-{
-  services.borgbackup.jobs = {
-    backupToLocalServer = {
-      paths = [ "/etc/nixos" ];
-      doInit = true;
-      repo =  "borg@nixos:." ;
-      encryption = {
-        mode = "repokey-blake2";
-        passCommand = "cat /run/keys/borgbackup_passphrase";
-      };
-      environment = { BORG_RSH = "ssh -i /run/keys/id_ed25519_my_borg_repo"; };
-      compression = "auto,lzma";
-      startAt = "hourly";
-    };
-  };
-};</programlisting>
-  </para>
-  <para>The following few commands (run as root) let you test your backup.
-      <programlisting>
-> nixos-rebuild switch
-...restarting the following units: polkit.service
-> systemctl restart borgbackup-job-backupToLocalServer
-> sleep 10
-> systemctl restart borgbackup-job-backupToLocalServer
-> export BORG_PASSPHRASE=topSecrect
-> borg list --rsh='ssh -i /run/keys/id_ed25519_my_borg_repo' borg@nixos:.
-nixos-backupToLocalServer-2020-03-30T21:46:17 Mon, 2020-03-30 21:46:19 [84feb97710954931ca384182f5f3cb90665f35cef214760abd7350fb064786ac]
-nixos-backupToLocalServer-2020-03-30T21:46:30 Mon, 2020-03-30 21:46:32 [e77321694ecd160ca2228611747c6ad1be177d6e0d894538898de7a2621b6e68]</programlisting>
-    </para>
-</section>
-
- <section xml:id="opt-services-backup-borgbackup-borgbase">
-  <title>Backup to a hosting service</title>
-
-  <para>
-    Several companies offer <link
-      xlink:href="https://www.borgbackup.org/support/commercial.html">(paid)
-      hosting services</link> for Borg repositories.
-  </para>
-  <para>
-    To backup your home directory to borgbase you have to:
-  </para>
-  <itemizedlist>
-  <listitem>
-    <para>
-      Generate a SSH key without a password, to access the remote server. E.g.
-    </para>
-    <para>
-        <programlisting>sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_borgbase</programlisting>
-    </para>
-  </listitem>
-  <listitem>
-    <para>
-      Create the repository on the server by following the instructions for your
-      hosting server.
-    </para>
-  </listitem>
-  <listitem>
-    <para>
-      Initialize the repository on the server. Eg.
-      <programlisting>
-sudo borg init --encryption=repokey-blake2  \
-    -rsh "ssh -i /run/keys/id_ed25519_borgbase" \
-    zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo</programlisting>
-  </para>
-  </listitem>
-  <listitem>
-<para>Add it to your NixOS configuration, e.g.
-<programlisting>
-{
-    services.borgbackup.jobs = {
-    my_Remote_Backup = {
-        paths = [ "/" ];
-        exclude = [ "/nix" "'**/.cache'" ];
-        repo =  "zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo";
-          encryption = {
-          mode = "repokey-blake2";
-          passCommand = "cat /run/keys/borgbackup_passphrase";
-        };
-        BORG_RSH = "ssh -i /run/keys/id_ed25519_borgbase";
-        compression = "auto,lzma";
-        startAt = "daily";
-    };
-  };
-}}</programlisting>
-  </para>
-  </listitem>
-</itemizedlist>
- </section>
-  <section xml:id="opt-services-backup-borgbackup-vorta">
-  <title>Vorta backup client for the desktop</title>
-  <para>
-    Vorta is a backup client for macOS and Linux desktops. It integrates the
-    mighty BorgBackup with your desktop environment to protect your data from
-    disk failure, ransomware and theft.
-  </para>
-  <para>
-   It can be installed in NixOS e.g. by adding <package>pkgs.vorta</package>
-   to <xref linkend="opt-environment.systemPackages" />.
-  </para>
-  <para>
-    Details about using Vorta can be found under <link
-      xlink:href="https://vorta.borgbase.com/usage">https://vorta.borgbase.com
-      </link>.
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/backup/borgmatic.nix b/nixpkgs/nixos/modules/services/backup/borgmatic.nix
index 7236a1f19411..5ee036e68c7b 100644
--- a/nixpkgs/nixos/modules/services/backup/borgmatic.nix
+++ b/nixpkgs/nixos/modules/services/backup/borgmatic.nix
@@ -5,44 +5,58 @@ with lib;
 let
   cfg = config.services.borgmatic;
   settingsFormat = pkgs.formats.yaml { };
+
+  cfgType = with types; submodule {
+    freeformType = settingsFormat.type;
+    options.location = {
+      source_directories = mkOption {
+        type = listOf str;
+        description = mdDoc ''
+          List of source directories to backup (required). Globs and
+          tildes are expanded.
+        '';
+        example = [ "/home" "/etc" "/var/log/syslog*" ];
+      };
+      repositories = mkOption {
+        type = listOf str;
+        description = mdDoc ''
+          Paths to local or remote repositories (required). Tildes are
+          expanded. Multiple repositories are backed up to in
+          sequence. Borg placeholders can be used. See the output of
+          "borg help placeholders" for details. See ssh_command for
+          SSH options like identity file or port. If systemd service
+          is used, then add local repository paths in the systemd
+          service file to the ReadWritePaths list.
+        '';
+        example = [
+          "ssh://user@backupserver/./sourcehostname.borg"
+          "ssh://user@backupserver/./{fqdn}"
+          "/var/local/backups/local.borg"
+        ];
+      };
+    };
+  };
+
   cfgfile = settingsFormat.generate "config.yaml" cfg.settings;
-in {
+in
+{
   options.services.borgmatic = {
-    enable = mkEnableOption "borgmatic";
+    enable = mkEnableOption (mdDoc "borgmatic");
 
     settings = mkOption {
-      description = lib.mdDoc ''
+      description = mdDoc ''
         See https://torsion.org/borgmatic/docs/reference/configuration/
       '';
-      type = types.submodule {
-        freeformType = settingsFormat.type;
-        options.location = {
-          source_directories = mkOption {
-            type = types.listOf types.str;
-            description = lib.mdDoc ''
-              List of source directories to backup (required). Globs and
-              tildes are expanded.
-            '';
-            example = [ "/home" "/etc" "/var/log/syslog*" ];
-          };
-          repositories = mkOption {
-            type = types.listOf types.str;
-            description = lib.mdDoc ''
-              Paths to local or remote repositories (required). Tildes are
-              expanded. Multiple repositories are backed up to in
-              sequence. Borg placeholders can be used. See the output of
-              "borg help placeholders" for details. See ssh_command for
-              SSH options like identity file or port. If systemd service
-              is used, then add local repository paths in the systemd
-              service file to the ReadWritePaths list.
-            '';
-            example = [
-              "user@backupserver:sourcehostname.borg"
-              "user@backupserver:{fqdn}"
-            ];
-          };
-        };
-      };
+      default = null;
+      type = types.nullOr cfgType;
+    };
+
+    configurations = mkOption {
+      description = mdDoc ''
+        Set of borgmatic configurations, see https://torsion.org/borgmatic/docs/reference/configuration/
+      '';
+      default = { };
+      type = types.attrsOf cfgType;
     };
   };
 
@@ -50,9 +64,16 @@ in {
 
     environment.systemPackages = [ pkgs.borgmatic ];
 
-    environment.etc."borgmatic/config.yaml".source = cfgfile;
+    environment.etc = (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
+      mapAttrs'
+        (name: value: nameValuePair
+          "borgmatic.d/${name}.yaml"
+          { source = settingsFormat.generate "${name}.yaml" value; })
+        cfg.configurations;
 
     systemd.packages = [ pkgs.borgmatic ];
 
+    # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
+    systemd.timers.borgmatic.wantedBy = [ "timers.target" ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/backup/btrbk.nix b/nixpkgs/nixos/modules/services/backup/btrbk.nix
index f1d58f597c25..b838c174553d 100644
--- a/nixpkgs/nixos/modules/services/backup/btrbk.nix
+++ b/nixpkgs/nixos/modules/services/backup/btrbk.nix
@@ -1,72 +1,79 @@
 { config, pkgs, lib, ... }:
 let
   inherit (lib)
+    concatLists
+    concatMap
     concatMapStringsSep
     concatStringsSep
     filterAttrs
-    flatten
     isAttrs
-    isString
     literalExpression
     mapAttrs'
     mapAttrsToList
     mkIf
     mkOption
     optionalString
-    partition
-    typeOf
+    sort
     types
     ;
 
-  cfg = config.services.btrbk;
-  sshEnabled = cfg.sshAccess != [ ];
-  serviceEnabled = cfg.instances != { };
-  attr2Lines = attr:
-    let
-      pairs = mapAttrsToList (name: value: { inherit name value; }) attr;
-      isSubsection = value:
-        if isAttrs value then true
-        else if isString value then false
-        else throw "invalid type in btrbk config ${typeOf value}";
-      sortedPairs = partition (x: isSubsection x.value) pairs;
-    in
-    flatten (
-      # non subsections go first
-      (
-        map (pair: [ "${pair.name} ${pair.value}" ]) sortedPairs.wrong
-      )
-      ++ # subsections go last
-      (
-        map
-          (
-            pair:
-            mapAttrsToList
-              (
-                childname: value:
-                  [ "${pair.name} ${childname}" ] ++ (map (x: " " + x) (attr2Lines value))
-              )
-              pair.value
-          )
-          sortedPairs.right
-      )
-    )
-  ;
-  addDefaults = settings: { backend = "btrfs-progs-sudo"; } // settings;
-  mkConfigFile = settings: concatStringsSep "\n" (attr2Lines (addDefaults settings));
-  mkTestedConfigFile = name: settings:
+  # The priority of an option or section.
+  # The configurations format are order-sensitive. Pairs are added as children of
+  # the last sections if possible, otherwise, they start a new section.
+  # We sort them in topological order:
+  # 1. Leaf pairs.
+  # 2. Sections that may contain (1).
+  # 3. Sections that may contain (1) or (2).
+  # 4. Etc.
+  prioOf = { name, value }:
+    if !isAttrs value then 0 # Leaf options.
+    else {
+      target = 1; # Contains: options.
+      subvolume = 2; # Contains: options, target.
+      volume = 3; # Contains: options, target, subvolume.
+    }.${name} or (throw "Unknow section '${name}'");
+
+  genConfig' = set: concatStringsSep "\n" (genConfig set);
+  genConfig = set:
     let
-      configFile = pkgs.writeText "btrbk-${name}.conf" (mkConfigFile settings);
+      pairs = mapAttrsToList (name: value: { inherit name value; }) set;
+      sortedPairs = sort (a: b: prioOf a < prioOf b) pairs;
     in
-    pkgs.runCommand "btrbk-${name}-tested.conf" { } ''
-      mkdir foo
-      cp ${configFile} $out
-      if (set +o pipefail; ${pkgs.btrbk}/bin/btrbk -c $out ls foo 2>&1 | grep $out);
-      then
-      echo btrbk configuration is invalid
-      cat $out
-      exit 1
-      fi;
+      concatMap genPair sortedPairs;
+  genSection = sec: secName: value:
+    [ "${sec} ${secName}" ] ++ map (x: " " + x) (genConfig value);
+  genPair = { name, value }:
+    if !isAttrs value
+    then [ "${name} ${value}" ]
+    else concatLists (mapAttrsToList (genSection name) value);
+
+  sudo_doas =
+    if config.security.sudo.enable then "sudo"
+    else if config.security.doas.enable then "doas"
+    else throw "The btrbk nixos module needs either sudo or doas enabled in the configuration";
+
+  addDefaults = settings: { backend = "btrfs-progs-${sudo_doas}"; } // settings;
+
+  mkConfigFile = name: settings: pkgs.writeTextFile {
+    name = "btrbk-${name}.conf";
+    text = genConfig' (addDefaults settings);
+    checkPhase = ''
+      set +e
+      ${pkgs.btrbk}/bin/btrbk -c $out dryrun
+      # According to btrbk(1), exit status 2 means parse error
+      # for CLI options or the config file.
+      if [[ $? == 2 ]]; then
+        echo "Btrbk configuration is invalid:"
+        cat $out
+        exit 1
+      fi
+      set -e
     '';
+  };
+
+  cfg = config.services.btrbk;
+  sshEnabled = cfg.sshAccess != [ ];
+  serviceEnabled = cfg.instances != { };
 in
 {
   meta.maintainers = with lib.maintainers; [ oxalica ];
@@ -150,20 +157,41 @@ in
   };
   config = mkIf (sshEnabled || serviceEnabled) {
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
-    security.sudo.extraRules = [
-      {
-        users = [ "btrbk" ];
-        commands = [
-          { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
-          { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
-          { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
-          # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-          { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
-          { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
-          { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+    security.sudo = mkIf (sudo_doas == "sudo") {
+      extraRules = [
+        {
+            users = [ "btrbk" ];
+            commands = [
+            { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
+            { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
+            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+            { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+            ];
+        }
+      ];
+    };
+    security.doas = mkIf (sudo_doas == "doas") {
+      extraRules = let
+        doasCmdNoPass = cmd: { users = [ "btrbk" ]; cmd = cmd; noPass = true; };
+      in
+        [
+            (doasCmdNoPass "${pkgs.btrfs-progs}/bin/btrfs")
+            (doasCmdNoPass "${pkgs.coreutils}/bin/mkdir")
+            (doasCmdNoPass "${pkgs.coreutils}/bin/readlink")
+            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+            (doasCmdNoPass "/run/current-system/bin/btrfs")
+            (doasCmdNoPass "/run/current-system/sw/bin/mkdir")
+            (doasCmdNoPass "/run/current-system/sw/bin/readlink")
+
+            # doas matches command, not binary
+            (doasCmdNoPass "btrfs")
+            (doasCmdNoPass "mkdir")
+            (doasCmdNoPass "readlink")
         ];
-      }
-    ];
+    };
     users.users.btrbk = {
       isSystemUser = true;
       # ssh needs a home directory
@@ -181,8 +209,9 @@ in
               "best-effort" = 2;
               "realtime" = 1;
             }.${cfg.ioSchedulingClass};
+            sudo_doas_flag = "--${sudo_doas}";
           in
-          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh --sudo ${options}" ${v.key}''
+          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh ${sudo_doas_flag} ${options}" ${v.key}''
         )
         cfg.sshAccess;
     };
@@ -196,7 +225,7 @@ in
       (
         name: instance: {
           name = "btrbk/${name}.conf";
-          value.source = mkTestedConfigFile name instance.settings;
+          value.source = mkConfigFile name instance.settings;
         }
       )
       cfg.instances;
diff --git a/nixpkgs/nixos/modules/services/backup/duplicati.nix b/nixpkgs/nixos/modules/services/backup/duplicati.nix
index 8da29a04c824..007396ebfc9b 100644
--- a/nixpkgs/nixos/modules/services/backup/duplicati.nix
+++ b/nixpkgs/nixos/modules/services/backup/duplicati.nix
@@ -8,11 +8,11 @@ in
 {
   options = {
     services.duplicati = {
-      enable = mkEnableOption "Duplicati";
+      enable = mkEnableOption (lib.mdDoc "Duplicati");
 
       port = mkOption {
         default = 8200;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           Port serving the web interface
         '';
@@ -21,14 +21,14 @@ in
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/duplicati";
-        description = ''
+        description = lib.mdDoc ''
           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>
+          ::: {.note}
+          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.
+          :::
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/backup/duplicity.nix b/nixpkgs/nixos/modules/services/backup/duplicity.nix
index afa4f31b129c..05ec997ab66b 100644
--- a/nixpkgs/nixos/modules/services/backup/duplicity.nix
+++ b/nixpkgs/nixos/modules/services/backup/duplicity.nix
@@ -13,7 +13,7 @@ let
 in
 {
   options.services.duplicity = {
-    enable = mkEnableOption "backups with duplicity";
+    enable = mkEnableOption (lib.mdDoc "backups with duplicity");
 
     root = mkOption {
       type = types.path;
@@ -54,15 +54,15 @@ in
     secretFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path of a file containing secrets (gpg passphrase, access key...) in
         the format of EnvironmentFile as described by
-        <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>. For example:
-        <programlisting>
+        {manpage}`systemd.exec(5)`. For example:
+        ```
         PASSPHRASE=«...»
         AWS_ACCESS_KEY_ID=«...»
         AWS_SECRET_ACCESS_KEY=«...»
-        </programlisting>
+        ```
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/backup/mysql-backup.nix b/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
index 41adb63e7fa8..9fbc599cd41a 100644
--- a/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
+++ b/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
@@ -20,7 +20,7 @@ let
   '';
   backupDatabaseScript = db: ''
     dest="${cfg.location}/${db}.gz"
-    if ${mariadb}/bin/mysqldump ${if cfg.singleTransaction then "--single-transaction" else ""} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
+    if ${mariadb}/bin/mysqldump ${optionalString cfg.singleTransaction "--single-transaction"} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
       mv $dest.tmp $dest
       echo "Backed up to $dest"
     else
@@ -37,7 +37,7 @@ in
 
     services.mysqlBackup = {
 
-      enable = mkEnableOption "MySQL backups";
+      enable = mkEnableOption (lib.mdDoc "MySQL backups");
 
       calendar = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix b/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
index 744ccb98e2c2..d3c6f3104fc5 100644
--- a/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
+++ b/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
@@ -71,7 +71,7 @@ in {
 
   options = {
     services.postgresqlBackup = {
-      enable = mkEnableOption "PostgreSQL dumps";
+      enable = mkEnableOption (lib.mdDoc "PostgreSQL dumps");
 
       startAt = mkOption {
         default = "*-*-* 01:15:00";
diff --git a/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
index 1d3892c158e0..37a6150c99d3 100644
--- a/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
@@ -9,7 +9,7 @@ in
   meta.maintainers = [ maintainers.bachp ];
 
   options.services.restic.server = {
-    enable = mkEnableOption "Restic REST Server";
+    enable = mkEnableOption (lib.mdDoc "Restic REST Server");
 
     listenAddress = mkOption {
       default = ":8000";
diff --git a/nixpkgs/nixos/modules/services/backup/restic.nix b/nixpkgs/nixos/modules/services/backup/restic.nix
index 2b0dcb16344c..3a951f7cbc83 100644
--- a/nixpkgs/nixos/modules/services/backup/restic.nix
+++ b/nixpkgs/nixos/modules/services/backup/restic.nix
@@ -126,17 +126,34 @@ in
           ];
         };
 
+        exclude = mkOption {
+          type = types.listOf types.str;
+          default = [ ];
+          description = lib.mdDoc ''
+            Patterns to exclude when backing up. See
+            https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for
+            details on syntax.
+          '';
+          example = [
+            "/var/cache"
+            "/home/*/.cache"
+            ".git"
+          ];
+        };
+
         timerConfig = mkOption {
           type = types.attrsOf unitOption;
           default = {
             OnCalendar = "daily";
+            Persistent = true;
           };
           description = lib.mdDoc ''
-            When to run the backup. See man systemd.timer for details.
+            When to run the backup. See {manpage}`systemd.timer(5)` for details.
           '';
           example = {
             OnCalendar = "00:05";
             RandomizedDelaySec = "5h";
+            Persistent = true;
           };
         };
 
@@ -182,11 +199,11 @@ in
         pruneOpts = mkOption {
           type = types.listOf types.str;
           default = [ ];
-          description = ''
-            A list of options (--keep-* et al.) for 'restic forget
+          description = lib.mdDoc ''
+            A list of options (--keep-\* et al.) for 'restic forget
             --prune', to automatically prune old snapshots.  The
             'forget' command is run *after* the 'backup' command, so
-            keep that in mind when constructing the --keep-* options.
+            keep that in mind when constructing the --keep-\* options.
           '';
           example = [
             "--keep-daily 7"
@@ -196,6 +213,18 @@ in
           ];
         };
 
+        checkOpts = mkOption {
+          type = types.listOf types.str;
+          default = [ ];
+          description = lib.mdDoc ''
+            A list of options for 'restic check', which is run after
+            pruning.
+          '';
+          example = [
+            "--with-cache"
+          ];
+        };
+
         dynamicFilesFrom = mkOption {
           type = with types; nullOr str;
           default = null;
@@ -237,6 +266,7 @@ in
     example = {
       localbackup = {
         paths = [ "/home" ];
+        exclude = [ "/home/*/.cache" ];
         repository = "/mnt/backup-hdd";
         passwordFile = "/etc/nixos/secrets/restic-password";
         initialize = true;
@@ -258,20 +288,25 @@ 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);
+    assertions = mapAttrsToList (n: v: {
+      assertion = (v.repository == null) != (v.repositoryFile == null);
+      message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
+    }) config.services.restic.backups;
     systemd.services =
       mapAttrs'
         (name: backup:
           let
             extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
             resticCmd = "${backup.package}/bin/restic${extraOptions}";
+            excludeFlags = if (backup.exclude != []) then ["--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}"] else [];
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
             backupPaths =
               if (backup.dynamicFilesFrom == null)
-              then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
+              then optionalString (backup.paths != null) (concatStringsSep " " backup.paths)
               else "--files-from ${filesFromTmpFile}";
             pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
               (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
-              (resticCmd + " check")
+              (resticCmd + " check " + (concatStringsSep " " backup.checkOpts))
             ];
             # Helper functions for rclone remotes
             rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
@@ -281,6 +316,7 @@ in
           in
           nameValuePair "restic-backups-${name}" ({
             environment = {
+              RESTIC_CACHE_DIR = "%C/restic-backups-${name}";
               RESTIC_PASSWORD_FILE = backup.passwordFile;
               RESTIC_REPOSITORY = backup.repository;
               RESTIC_REPOSITORY_FILE = backup.repositoryFile;
@@ -299,12 +335,13 @@ in
             restartIfChanged = false;
             serviceConfig = {
               Type = "oneshot";
-              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ])
+              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
                 ++ pruneCmd;
               User = backup.user;
               RuntimeDirectory = "restic-backups-${name}";
               CacheDirectory = "restic-backups-${name}";
               CacheDirectoryMode = "0700";
+              PrivateTmp = true;
             } // optionalAttrs (backup.environmentFile != null) {
               EnvironmentFile = backup.environmentFile;
             };
diff --git a/nixpkgs/nixos/modules/services/backup/rsnapshot.nix b/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
index b18c02d7d11c..0b9bb60af0ea 100644
--- a/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
+++ b/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
@@ -22,7 +22,7 @@ in
 {
   options = {
     services.rsnapshot = {
-      enable = mkEnableOption "rsnapshot backups";
+      enable = mkEnableOption (lib.mdDoc "rsnapshot backups");
       enableManualRsnapshot = mkOption {
         description = lib.mdDoc "Whether to enable manual usage of the rsnapshot command with this module.";
         default = true;
diff --git a/nixpkgs/nixos/modules/services/backup/sanoid.nix b/nixpkgs/nixos/modules/services/backup/sanoid.nix
index 0c01aa57f7e9..aae77cee07d0 100644
--- a/nixpkgs/nixos/modules/services/backup/sanoid.nix
+++ b/nixpkgs/nixos/modules/services/backup/sanoid.nix
@@ -112,7 +112,9 @@ in
   # Interface
 
   options.services.sanoid = {
-    enable = mkEnableOption "Sanoid ZFS snapshotting service";
+    enable = mkEnableOption (lib.mdDoc "Sanoid ZFS snapshotting service");
+
+    package = lib.mkPackageOptionMD pkgs "sanoid" {};
 
     interval = mkOption {
       type = types.str;
@@ -130,8 +132,8 @@ in
       type = types.attrsOf (types.submodule ({ config, options, ... }: {
         freeformType = datasetSettingsType;
         options = commonOptions // datasetOptions;
-        config.use_template = mkAliasDefinitions (mkDefault options.useTemplate or { });
-        config.process_children_only = mkAliasDefinitions (mkDefault options.processChildrenOnly or { });
+        config.use_template = modules.mkAliasAndWrapDefsWithPriority id (options.useTemplate or { });
+        config.process_children_only = modules.mkAliasAndWrapDefsWithPriority id (options.processChildrenOnly or { });
       }));
       default = { };
       description = lib.mdDoc "Datasets to snapshot.";
@@ -181,7 +183,7 @@ in
         ExecStartPre = (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]) datasets);
         ExecStopPost = (map (buildAllowCommand "unallow" [ "snapshot" "mount" "destroy" ]) datasets);
         ExecStart = lib.escapeShellArgs ([
-          "${pkgs.sanoid}/bin/sanoid"
+          "${cfg.package}/bin/sanoid"
           "--cron"
           "--configdir"
           (pkgs.writeTextDir "sanoid.conf" configFile)
diff --git a/nixpkgs/nixos/modules/services/backup/syncoid.nix b/nixpkgs/nixos/modules/services/backup/syncoid.nix
index 1e445f4bebed..0f375455e7ed 100644
--- a/nixpkgs/nixos/modules/services/backup/syncoid.nix
+++ b/nixpkgs/nixos/modules/services/backup/syncoid.nix
@@ -85,7 +85,9 @@ in
   # Interface
 
   options.services.syncoid = {
-    enable = mkEnableOption "Syncoid ZFS synchronization service";
+    enable = mkEnableOption (lib.mdDoc "Syncoid ZFS synchronization service");
+
+    package = lib.mkPackageOptionMD pkgs "sanoid" {};
 
     interval = mkOption {
       type = types.str;
@@ -200,7 +202,7 @@ in
             '';
           };
 
-          recursive = mkEnableOption ''the transfer of child datasets'';
+          recursive = mkEnableOption (lib.mdDoc ''the transfer of child datasets'');
 
           sshKey = mkOption {
             type = types.nullOr types.path;
@@ -331,7 +333,7 @@ in
               ExecStopPost =
                 (map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source)) ++
                 (map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target));
-              ExecStart = lib.escapeShellArgs ([ "${pkgs.sanoid}/bin/syncoid" ]
+              ExecStart = lib.escapeShellArgs ([ "${cfg.package}/bin/syncoid" ]
                 ++ optionals c.useCommonArgs cfg.commonArgs
                 ++ optional c.recursive "-r"
                 ++ optionals (c.sshKey != null) [ "--sshkey" c.sshKey ]
diff --git a/nixpkgs/nixos/modules/services/backup/tarsnap.nix b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
index c2d4f87362e7..b34aa3ff50dd 100644
--- a/nixpkgs/nixos/modules/services/backup/tarsnap.nix
+++ b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
@@ -30,7 +30,7 @@ in
 
   options = {
     services.tarsnap = {
-      enable = mkEnableOption "periodic tarsnap backups";
+      enable = mkEnableOption (lib.mdDoc "periodic tarsnap backups");
 
       keyfile = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/backup/tsm.nix b/nixpkgs/nixos/modules/services/backup/tsm.nix
index bd6f3d71fe6c..c4de0b16d47d 100644
--- a/nixpkgs/nixos/modules/services/backup/tsm.nix
+++ b/nixpkgs/nixos/modules/services/backup/tsm.nix
@@ -8,12 +8,12 @@ let
   inherit (lib.types) nonEmptyStr nullOr;
 
   options.services.tsmBackup = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       automatic backups with the
       IBM Spectrum Protect (Tivoli Storage Manager, TSM) client.
       This also enables
-      <option>programs.tsmClient.enable</option>
-    '';
+      {option}`programs.tsmClient.enable`
+    '');
     command = mkOption {
       type = nonEmptyStr;
       default = "backup";
diff --git a/nixpkgs/nixos/modules/services/backup/zfs-replication.nix b/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
index 1a089bb34866..8e7059e5b59d 100644
--- a/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
+++ b/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
@@ -9,10 +9,10 @@ let
 in {
   options = {
     services.zfs.autoReplication = {
-      enable = mkEnableOption "ZFS snapshot replication.";
+      enable = mkEnableOption (lib.mdDoc "ZFS snapshot replication");
 
       followDelete = mkOption {
-        description = lib.mdDoc "Remove remote snapshots that don't have a local correspondant.";
+        description = lib.mdDoc "Remove remote snapshots that don't have a local correspondent.";
         default = true;
         type = types.bool;
       };
@@ -30,7 +30,7 @@ in {
       };
 
       localFilesystem = mkOption {
-        description = lib.mdDoc "Local ZFS fileystem from which snapshots should be sent.  Defaults to the attribute name.";
+        description = lib.mdDoc "Local ZFS filesystem from which snapshots should be sent.  Defaults to the attribute name.";
         example = "pool/file/path";
         type = types.str;
       };
diff --git a/nixpkgs/nixos/modules/services/backup/znapzend.nix b/nixpkgs/nixos/modules/services/backup/znapzend.nix
index ecd90ba5b30a..76f147c18aff 100644
--- a/nixpkgs/nixos/modules/services/backup/znapzend.nix
+++ b/nixpkgs/nixos/modules/services/backup/znapzend.nix
@@ -9,22 +9,22 @@ let
       The znapzend backup plan to use for the source.
 
       The plan specifies how often to backup and for how long to keep the
-      backups. It consists of a series of retention periodes to interval
+      backups. It consists of a series of retention periods to interval
       associations:
 
-      <literal>
+      ```
         retA=>intA,retB=>intB,...
-      </literal>
+      ```
 
       Both intervals and retention periods are expressed in standard units
       of time or multiples of them. You can use both the full name or a
       shortcut according to the following listing:
 
-      <literal>
+      ```
         second|sec|s, minute|min, hour|h, day|d, week|w, month|mon|m, year|y
-      </literal>
+      ```
 
-      See <citerefentry><refentrytitle>znapzendzetup</refentrytitle><manvolnum>1</manvolnum></citerefentry> for more info.
+      See {manpage}`znapzendzetup(1)` for more info.
   '';
   planExample = "1h=>10min,1d=>1h,1w=>1d,1m=>1w,1y=>1m";
 
@@ -57,7 +57,7 @@ let
 
       plan = mkOption {
         type = str;
-        description = planDescription;
+        description = lib.mdDoc planDescription;
         example = planExample;
       };
 
@@ -209,7 +209,7 @@ let
 
       plan = mkOption {
         type = str;
-        description = planDescription;
+        description = lib.mdDoc planDescription;
         example = planExample;
       };
 
@@ -268,7 +268,7 @@ let
 
   mkSrcAttrs = srcCfg: with srcCfg; {
     enabled = onOff enable;
-    # mbuffer is not referenced by its full path to accomodate non-NixOS systems or differing mbuffer versions between source and target
+    # mbuffer is not referenced by its full path to accommodate non-NixOS systems or differing mbuffer versions between source and target
     mbuffer = with mbuffer; if enable then "mbuffer"
         + optionalString (port != null) ":${toString port}" else "off";
     mbuffer_size = mbuffer.size;
@@ -294,7 +294,7 @@ in
 {
   options = {
     services.znapzend = {
-      enable = mkEnableOption "ZnapZend ZFS backup daemon";
+      enable = mkEnableOption (lib.mdDoc "ZnapZend ZFS backup daemon");
 
       logLevel = mkOption {
         default = "debug";
@@ -310,8 +310,8 @@ in
         type = str;
         default = "syslog::daemon";
         example = "/var/log/znapzend.log";
-        description = ''
-          Where to log to (syslog::&lt;facility&gt; or &lt;filepath&gt;).
+        description = lib.mdDoc ''
+          Where to log to (syslog::\<facility\> or \<filepath\>).
         '';
       };
 
@@ -358,62 +358,62 @@ in
         default = false;
       };
 
-      features.oracleMode = mkEnableOption ''
+      features.oracleMode = mkEnableOption (lib.mdDoc ''
         Destroy snapshots one by one instead of using one long argument list.
         If source and destination are out of sync for a long time, you may have
         so many snapshots to destroy that the argument gets is too long and the
         command fails.
-      '';
-      features.recvu = mkEnableOption ''
-        recvu feature which uses <literal>-u</literal> on the receiving end to keep the destination
+      '');
+      features.recvu = mkEnableOption (lib.mdDoc ''
+        recvu feature which uses `-u` on the receiving end to keep the destination
         filesystem unmounted.
-      '';
-      features.compressed = mkEnableOption ''
-        compressed feature which adds the options <literal>-Lce</literal> to
-        the <command>zfs send</command> command. When this is enabled, make
+      '');
+      features.compressed = mkEnableOption (lib.mdDoc ''
+        compressed feature which adds the options `-Lce` to
+        the {command}`zfs send` command. When this is enabled, make
         sure that both the sending and receiving pool have the same relevant
-        features enabled. Using <literal>-c</literal> will skip unneccessary
-        decompress-compress stages, <literal>-L</literal> is for large block
+        features enabled. Using `-c` will skip unnecessary
+        decompress-compress stages, `-L` is for large block
         support and -e is for embedded data support. see
-        <citerefentry><refentrytitle>znapzend</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-        and <citerefentry><refentrytitle>zfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        {manpage}`znapzend(1)`
+        and {manpage}`zfs(8)`
         for more info.
-      '';
-      features.sendRaw = mkEnableOption ''
-        sendRaw feature which adds the options <literal>-w</literal> to the
-        <command>zfs send</command> command. For encrypted source datasets this
+      '');
+      features.sendRaw = mkEnableOption (lib.mdDoc ''
+        sendRaw feature which adds the options `-w` to the
+        {command}`zfs send` command. For encrypted source datasets this
         instructs zfs not to decrypt before sending which results in a remote
         backup that can't be read without the encryption key/passphrase, useful
         when the remote isn't fully trusted or not physically secure. This
         option must be used consistently, raw incrementals cannot be based on
         non-raw snapshots and vice versa.
-      '';
-      features.skipIntermediates = mkEnableOption ''
+      '');
+      features.skipIntermediates = mkEnableOption (lib.mdDoc ''
         Enable the skipIntermediates feature to send a single increment
         between latest common snapshot and the newly made one. It may skip
         several source snaps if the destination was offline for some time, and
         it should skip snapshots not managed by znapzend. Normally for online
         destinations, the new snapshot is sent as soon as it is created on the
         source, so there are no automatic increments to skip.
-      '';
-      features.lowmemRecurse = mkEnableOption ''
+      '');
+      features.lowmemRecurse = mkEnableOption (lib.mdDoc ''
         use lowmemRecurse on systems where you have too many datasets, so a
         recursive listing of attributes to find backup plans exhausts the
-        memory available to <command>znapzend</command>: instead, go the slower
+        memory available to {command}`znapzend`: instead, go the slower
         way to first list all impacted dataset names, and then query their
         configs one by one.
-      '';
-      features.zfsGetType = mkEnableOption ''
-        use zfsGetType if your <command>zfs get</command> supports a
-        <literal>-t</literal> argument for filtering by dataset type at all AND
+      '');
+      features.zfsGetType = mkEnableOption (lib.mdDoc ''
+        use zfsGetType if your {command}`zfs get` supports a
+        `-t` argument for filtering by dataset type at all AND
         lists properties for snapshots by default when recursing, so that there
         is too much data to process while searching for backup plans.
         If these two conditions apply to your system, the time needed for a
-        <literal>--recursive</literal> search for backup plans can literally
+        `--recursive` search for backup plans can literally
         differ by hundreds of times (depending on the amount of snapshots in
         that dataset tree... and a decent backup plan will ensure you have a lot
         of those), so you would benefit from requesting this feature.
-      '';
+      '');
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/backup/zrepl.nix b/nixpkgs/nixos/modules/services/backup/zrepl.nix
index ea858a8b77db..1d3afa3eda05 100644
--- a/nixpkgs/nixos/modules/services/backup/zrepl.nix
+++ b/nixpkgs/nixos/modules/services/backup/zrepl.nix
@@ -11,7 +11,7 @@ in
 
   options = {
     services.zrepl = {
-      enable = mkEnableOption "zrepl";
+      enable = mkEnableOption (lib.mdDoc "zrepl");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix b/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix
new file mode 100644
index 000000000000..8ebe0fcaff54
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.erigon;
+
+  settingsFormat = pkgs.formats.toml { };
+  configFile = settingsFormat.generate "config.toml" cfg.settings;
+in {
+
+  options = {
+    services.erigon = {
+      enable = mkEnableOption (lib.mdDoc "Ethereum implementation on the efficiency frontier");
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        description = lib.mdDoc "Additional arguments passed to Erigon";
+        default = [ ];
+      };
+
+      secretJwtPath = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          Path to the secret jwt used for the http api authentication.
+        '';
+        default = "";
+        example = "config.age.secrets.ERIGON_JWT.path";
+      };
+
+      settings = mkOption {
+        description = lib.mdDoc ''
+          Configuration for Erigon
+          Refer to <https://github.com/ledgerwatch/erigon#usage> for details on supported values.
+        '';
+
+        type = settingsFormat.type;
+
+        example = {
+          datadir = "/var/lib/erigon";
+          chain = "mainnet";
+          http = true;
+          "http.port" = 8545;
+          "http.api" = ["eth" "debug" "net" "trace" "web3" "erigon"];
+          ws = true;
+          port = 30303;
+          "authrpc.port" = 8551;
+          "torrent.port" = 42069;
+          "private.api.addr" = "localhost:9090";
+          "log.console.verbosity" = 3; # info
+        };
+
+        defaultText = literalExpression ''
+          {
+            datadir = "/var/lib/erigon";
+            chain = "mainnet";
+            http = true;
+            "http.port" = 8545;
+            "http.api" = ["eth" "debug" "net" "trace" "web3" "erigon"];
+            ws = true;
+            port = 30303;
+            "authrpc.port" = 8551;
+            "torrent.port" = 42069;
+            "private.api.addr" = "localhost:9090";
+            "log.console.verbosity" = 3; # info
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # Default values are the same as in the binary, they are just written here for convenience.
+    services.erigon.settings = {
+      datadir = mkDefault "/var/lib/erigon";
+      chain = mkDefault "mainnet";
+      http = mkDefault true;
+      "http.port" = mkDefault 8545;
+      "http.api" = mkDefault ["eth" "debug" "net" "trace" "web3" "erigon"];
+      ws = mkDefault true;
+      port = mkDefault 30303;
+      "authrpc.port" = mkDefault 8551;
+      "torrent.port" = mkDefault 42069;
+      "private.api.addr" = mkDefault "localhost:9090";
+      "log.console.verbosity" = mkDefault 3; # info
+    };
+
+    systemd.services.erigon = {
+      description = "Erigon ethereum implemenntation";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        LoadCredential = "ERIGON_JWT:${cfg.secretJwtPath}";
+        ExecStart = "${pkgs.erigon}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}";
+        DynamicUser = true;
+        Restart = "on-failure";
+        StateDirectory = "erigon";
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix b/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix
index 4f045acd9568..d12516ca2f24 100644
--- a/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix
+++ b/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix
@@ -9,7 +9,7 @@ let
 
     options = {
 
-      enable = lib.mkEnableOption "Go Ethereum Node";
+      enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum Node");
 
       port = mkOption {
         type = types.port;
@@ -18,7 +18,7 @@ let
       };
 
       http = {
-        enable = lib.mkEnableOption "Go Ethereum HTTP API";
+        enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum HTTP API");
         address = mkOption {
           type = types.str;
           default = "127.0.0.1";
@@ -40,7 +40,7 @@ let
       };
 
       websocket = {
-        enable = lib.mkEnableOption "Go Ethereum WebSocket API";
+        enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum WebSocket API");
         address = mkOption {
           type = types.str;
           default = "127.0.0.1";
@@ -61,8 +61,37 @@ let
         };
       };
 
+      authrpc = {
+        enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum Auth RPC API");
+        address = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = lib.mdDoc "Listen address of Go Ethereum Auth RPC API.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8551;
+          description = lib.mdDoc "Port number of Go Ethereum Auth RPC API.";
+        };
+
+        vhosts = mkOption {
+          type = types.nullOr (types.listOf types.str);
+          default = ["localhost"];
+          description = lib.mdDoc "List of virtual hostnames from which to accept requests.";
+          example = ["localhost" "geth.example.org"];
+        };
+
+        jwtsecret = mkOption {
+          type = types.str;
+          default = "";
+          description = lib.mdDoc "Path to a JWT secret for authenticated RPC endpoint.";
+          example = "/var/run/geth/jwtsecret";
+        };
+      };
+
       metrics = {
-        enable = lib.mkEnableOption "Go Ethereum prometheus metrics";
+        enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum prometheus metrics");
         address = mkOption {
           type = types.str;
           default = "127.0.0.1";
@@ -136,7 +165,10 @@ in
       cfg.package
     ]) eachGeth);
 
-    systemd.services = mapAttrs' (gethName: cfg: (
+    systemd.services = mapAttrs' (gethName: cfg: let
+      stateDir = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}";
+      dataDir = "/var/lib/${stateDir}";
+    in (
       nameValuePair "geth-${gethName}" (mkIf cfg.enable {
       description = "Go Ethereum node (${gethName})";
       wantedBy = [ "multi-user.target" ];
@@ -145,7 +177,7 @@ in
       serviceConfig = {
         DynamicUser = true;
         Restart = "always";
-        StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}";
+        StateDirectory = stateDir;
 
         # Hardening measures
         PrivateTmp = "true";
@@ -164,13 +196,15 @@ in
           --gcmode ${cfg.gcmode} \
           --port ${toString cfg.port} \
           --maxpeers ${toString cfg.maxpeers} \
-          ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \
+          ${optionalString cfg.http.enable ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}''} \
           ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \
-          ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \
+          ${optionalString cfg.websocket.enable ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}''} \
           ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \
           ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \
+          --authrpc.addr ${cfg.authrpc.address} --authrpc.port ${toString cfg.authrpc.port} --authrpc.vhosts ${lib.concatStringsSep "," cfg.authrpc.vhosts} \
+          ${if (cfg.authrpc.jwtsecret != "") then ''--authrpc.jwtsecret ${cfg.authrpc.jwtsecret}'' else ''--authrpc.jwtsecret ${dataDir}/geth/jwtsecret''} \
           ${lib.escapeShellArgs cfg.extraArgs} \
-          --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}
+          --datadir ${dataDir}
       '';
     }))) eachGeth;
 
diff --git a/nixpkgs/nixos/modules/services/blockchain/ethereum/lighthouse.nix b/nixpkgs/nixos/modules/services/blockchain/ethereum/lighthouse.nix
new file mode 100644
index 000000000000..863e737d908a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/blockchain/ethereum/lighthouse.nix
@@ -0,0 +1,315 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.lighthouse;
+in {
+
+  options = {
+    services.lighthouse = {
+      beacon = mkOption {
+        description = lib.mdDoc "Beacon node";
+        default = {};
+        type = types.submodule {
+          options = {
+            enable = lib.mkEnableOption (lib.mdDoc "Lightouse Beacon node");
+
+            dataDir = mkOption {
+              type = types.str;
+              default = "/var/lib/lighthouse-beacon";
+              description = lib.mdDoc ''
+                Directory where data will be stored. Each chain will be stored under it's own specific subdirectory.
+              '';
+            };
+
+            address = mkOption {
+              type = types.str;
+              default = "0.0.0.0";
+              description = lib.mdDoc ''
+                Listen address of Beacon node.
+              '';
+            };
+
+            port = mkOption {
+              type = types.port;
+              default = 9000;
+              description = lib.mdDoc ''
+                Port number the Beacon node will be listening on.
+              '';
+            };
+
+            openFirewall = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                Open the port in the firewall
+              '';
+            };
+
+            disableDepositContractSync = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                Explicitly disables syncing of deposit logs from the execution node.
+                This overrides any previous option that depends on it.
+                Useful if you intend to run a non-validating beacon node.
+              '';
+            };
+
+            execution = {
+              address = mkOption {
+                type = types.str;
+                default = "127.0.0.1";
+                description = lib.mdDoc ''
+                  Listen address for the execution layer.
+                '';
+              };
+
+              port = mkOption {
+                type = types.port;
+                default = 8551;
+                description = lib.mdDoc ''
+                  Port number the Beacon node will be listening on for the execution layer.
+                '';
+              };
+
+              jwtPath = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  Path for the jwt secret required to connect to the execution layer.
+                '';
+              };
+            };
+
+            http = {
+              enable = lib.mkEnableOption (lib.mdDoc "Beacon node http api");
+              port = mkOption {
+                type = types.port;
+                default = 5052;
+                description = lib.mdDoc ''
+                  Port number of Beacon node RPC service.
+                '';
+              };
+
+              address = mkOption {
+                type = types.str;
+                default = "127.0.0.1";
+                description = lib.mdDoc ''
+                  Listen address of Beacon node RPC service.
+                '';
+              };
+            };
+
+            metrics = {
+              enable = lib.mkEnableOption (lib.mdDoc "Beacon node prometheus metrics");
+              address = mkOption {
+                type = types.str;
+                default = "127.0.0.1";
+                description = lib.mdDoc ''
+                  Listen address of Beacon node metrics service.
+                '';
+              };
+
+              port = mkOption {
+                type = types.port;
+                default = 5054;
+                description = lib.mdDoc ''
+                  Port number of Beacon node metrics service.
+                '';
+              };
+            };
+
+            extraArgs = mkOption {
+              type = types.str;
+              description = lib.mdDoc ''
+                Additional arguments passed to the lighthouse beacon command.
+              '';
+              default = "";
+              example = "";
+            };
+          };
+        };
+      };
+
+      validator = mkOption {
+        description = lib.mdDoc "Validator node";
+        default = {};
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc "Enable Lightouse Validator node.";
+            };
+
+            dataDir = mkOption {
+              type = types.str;
+              default = "/var/lib/lighthouse-validator";
+              description = lib.mdDoc ''
+                Directory where data will be stored. Each chain will be stored under it's own specific subdirectory.
+              '';
+            };
+
+            beaconNodes = mkOption {
+              type = types.listOf types.str;
+              default = ["http://localhost:5052"];
+              description = lib.mdDoc ''
+                Beacon nodes to connect to.
+              '';
+            };
+
+            metrics = {
+              enable = lib.mkEnableOption (lib.mdDoc "Validator node prometheus metrics");
+              address = mkOption {
+                type = types.str;
+                default = "127.0.0.1";
+                description = lib.mdDoc ''
+                  Listen address of Validator node metrics service.
+                '';
+              };
+
+              port = mkOption {
+                type = types.port;
+                default = 5056;
+                description = lib.mdDoc ''
+                  Port number of Validator node metrics service.
+                '';
+              };
+            };
+
+            extraArgs = mkOption {
+              type = types.str;
+              description = lib.mdDoc ''
+                Additional arguments passed to the lighthouse validator command.
+              '';
+              default = "";
+              example = "";
+            };
+          };
+        };
+      };
+
+      network = mkOption {
+        type = types.enum [ "mainnet" "prater" "goerli" "gnosis" "kiln" "ropsten" "sepolia" ];
+        default = "mainnet";
+        description = lib.mdDoc ''
+          The network to connect to. Mainnet is the default ethereum network.
+        '';
+      };
+
+      extraArgs = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Additional arguments passed to every lighthouse command.
+        '';
+        default = "";
+        example = "";
+      };
+    };
+  };
+
+  config = mkIf (cfg.beacon.enable || cfg.validator.enable) {
+
+    environment.systemPackages = [ pkgs.lighthouse ] ;
+
+    networking.firewall = mkIf cfg.beacon.enable {
+      allowedTCPPorts = mkIf cfg.beacon.openFirewall [ cfg.beacon.port ];
+      allowedUDPPorts = mkIf cfg.beacon.openFirewall [ cfg.beacon.port ];
+    };
+
+
+    systemd.services.lighthouse-beacon = mkIf cfg.beacon.enable {
+      description = "Lighthouse beacon node (connect to P2P nodes and verify blocks)";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      script = ''
+        # make sure the chain data directory is created on first run
+        mkdir -p ${cfg.beacon.dataDir}/${cfg.network}
+
+        ${pkgs.lighthouse}/bin/lighthouse beacon_node \
+          --disable-upnp \
+          ${lib.optionalString cfg.beacon.disableDepositContractSync "--disable-deposit-contract-sync"} \
+          --port ${toString cfg.beacon.port} \
+          --listen-address ${cfg.beacon.address} \
+          --network ${cfg.network} \
+          --datadir ${cfg.beacon.dataDir}/${cfg.network} \
+          --execution-endpoint http://${cfg.beacon.execution.address}:${toString cfg.beacon.execution.port} \
+          --execution-jwt ''${CREDENTIALS_DIRECTORY}/LIGHTHOUSE_JWT \
+          ${lib.optionalString cfg.beacon.http.enable '' --http --http-address ${cfg.beacon.http.address} --http-port ${toString cfg.beacon.http.port}''} \
+          ${lib.optionalString cfg.beacon.metrics.enable '' --metrics --metrics-address ${cfg.beacon.metrics.address} --metrics-port ${toString cfg.beacon.metrics.port}''} \
+          ${cfg.extraArgs} ${cfg.beacon.extraArgs}
+      '';
+      serviceConfig = {
+        LoadCredential = "LIGHTHOUSE_JWT:${cfg.beacon.execution.jwtPath}";
+        DynamicUser = true;
+        Restart = "on-failure";
+        StateDirectory = "lighthouse-beacon";
+        ReadWritePaths = [ cfg.beacon.dataDir ];
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+
+    systemd.services.lighthouse-validator = mkIf cfg.validator.enable {
+      description = "Lighthouse validtor node (manages validators, using data obtained from the beacon node via a HTTP API)";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      script = ''
+        # make sure the chain data directory is created on first run
+        mkdir -p ${cfg.validator.dataDir}/${cfg.network}
+
+        ${pkgs.lighthouse}/bin/lighthouse validator_client \
+          --network ${cfg.network} \
+          --beacon-nodes ${lib.concatStringsSep "," cfg.validator.beaconNodes} \
+          --datadir ${cfg.validator.dataDir}/${cfg.network} \
+          ${optionalString cfg.validator.metrics.enable ''--metrics --metrics-address ${cfg.validator.metrics.address} --metrics-port ${toString cfg.validator.metrics.port}''} \
+          ${cfg.extraArgs} ${cfg.validator.extraArgs}
+      '';
+
+      serviceConfig = {
+        Restart = "on-failure";
+        StateDirectory = "lighthouse-validator";
+        ReadWritePaths = [ cfg.validator.dataDir ];
+        CapabilityBoundingSet = "";
+        DynamicUser = true;
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/corosync/default.nix b/nixpkgs/nixos/modules/services/cluster/corosync/default.nix
index 973089445416..7ef17c46b81e 100644
--- a/nixpkgs/nixos/modules/services/cluster/corosync/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/corosync/default.nix
@@ -7,7 +7,7 @@ in
 {
   # interface
   options.services.corosync = {
-    enable = mkEnableOption "corosync";
+    enable = mkEnableOption (lib.mdDoc "corosync");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
index 76e84c1111a0..72bf25c21146 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
@@ -25,7 +25,7 @@ with lib;
       default = {};
       type = types.attrsOf types.anything;
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         Internal option to add configs to core-site.xml based on module options
       '';
     };
@@ -59,7 +59,7 @@ with lib;
       default = {};
       type = types.attrsOf types.anything;
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         Internal option to add configs to hdfs-site.xml based on module options
       '';
     };
@@ -134,7 +134,7 @@ with lib;
       default = {};
       type = types.attrsOf types.anything;
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         Internal option to add configs to yarn-site.xml based on module options
       '';
     };
@@ -197,13 +197,13 @@ with lib;
       description = lib.mdDoc "Directories containing additional config files to be added to HADOOP_CONF_DIR";
     };
 
-    gatewayRole.enable = mkEnableOption "gateway role for deploying hadoop configs";
+    gatewayRole.enable = mkEnableOption (lib.mdDoc "gateway role for deploying hadoop configs");
 
     package = mkOption {
       type = types.package;
       default = pkgs.hadoop;
       defaultText = literalExpression "pkgs.hadoop";
-      description = "";
+      description = lib.mdDoc "";
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/hbase.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/hbase.nix
index 246775dc8987..a39da2a84eca 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/hbase.nix
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/hbase.nix
@@ -5,11 +5,95 @@ let
   cfg = config.services.hadoop;
   hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
   mkIfNotNull = x: mkIf (x != null) x;
+  # generic hbase role options
+  hbaseRoleOption = name: extraOpts: {
+    enable = mkEnableOption (mdDoc "HBase ${name}");
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Open firewall ports for HBase ${name}.";
+    };
+
+    restartIfChanged = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Restart ${name} con config change.";
+    };
+
+    extraFlags = mkOption {
+      type = with types; listOf str;
+      default = [];
+      example = literalExpression ''[ "--backup" ]'';
+      description = mdDoc "Extra flags for the ${name} service.";
+    };
+
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = {};
+      example = literalExpression ''
+        {
+          HBASE_MASTER_OPTS = "-Dcom.sun.management.jmxremote.ssl=true";
+        }
+      '';
+      description = mdDoc "Environment variables passed to ${name}.";
+    };
+  } // extraOpts;
+  # generic hbase role configs
+  hbaseRoleConfig = name: ports: (mkIf cfg.hbase."${name}".enable {
+    services.hadoop.gatewayRole = {
+      enable = true;
+      enableHbaseCli = mkDefault true;
+    };
+
+    systemd.services."hbase-${toLower name}" = {
+      description = "HBase ${name}";
+      wantedBy = [ "multi-user.target" ];
+      path = with cfg; [ hbase.package ] ++ optional
+        (with cfg.hbase.master; enable && initHDFS) package;
+      preStart = mkIf (with cfg.hbase.master; enable && initHDFS)
+        (concatStringsSep "\n" (
+          map (x: "HADOOP_USER_NAME=hdfs hdfs --config /etc/hadoop-conf ${x}")[
+            "dfsadmin -safemode wait"
+            "dfs -mkdir -p ${cfg.hbase.rootdir}"
+            "dfs -chown hbase ${cfg.hbase.rootdir}"
+          ]
+        ));
+
+      inherit (cfg.hbase."${name}") environment;
+      script = concatStringsSep " " (
+        [
+          "hbase --config /etc/hadoop-conf/"
+          "${toLower name} start"
+        ]
+        ++ cfg.hbase."${name}".extraFlags
+        ++ map (x: "--${toLower x} ${toString cfg.hbase.${name}.${x}}")
+          (filter (x: hasAttr x cfg.hbase.${name}) ["port" "infoPort"])
+      );
+
+      serviceConfig = {
+        User = "hbase";
+        SyslogIdentifier = "hbase-${toLower name}";
+        Restart = "always";
+      };
+    };
+
+    services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
+
+    networking = {
+      firewall.allowedTCPPorts = mkIf cfg.hbase."${name}".openFirewall ports;
+      hosts = mkIf (with cfg.hbase.regionServer; enable && overrideHosts) {
+        "127.0.0.2" = mkForce [ ];
+        "::1" = mkForce [ ];
+      };
+    };
+
+  });
 in
 {
   options.services.hadoop = {
 
-    gatewayRole.enableHbaseCli = mkEnableOption "HBase CLI tools";
+    gatewayRole.enableHbaseCli = mkEnableOption (mdDoc "HBase CLI tools");
 
     hbaseSiteDefault = mkOption {
       default = {
@@ -21,7 +105,7 @@ in
         "hbase.cluster.distributed" = "true";
       };
       type = types.attrsOf types.anything;
-      description = ''
+      description = mdDoc ''
         Default options for hbase-site.xml
       '';
     };
@@ -29,17 +113,21 @@ in
       default = {};
       type = with types; attrsOf anything;
       example = literalExpression ''
+        {
+          "hbase.hregion.max.filesize" = 20*1024*1024*1024;
+          "hbase.table.normalization.enabled" = "true";
+        }
       '';
-      description = ''
+      description = mdDoc ''
         Additional options and overrides for hbase-site.xml
-        <link xlink:href="https://github.com/apache/hbase/blob/rel/2.4.11/hbase-common/src/main/resources/hbase-default.xml"/>
+        <https://github.com/apache/hbase/blob/rel/2.4.11/hbase-common/src/main/resources/hbase-default.xml>
       '';
     };
     hbaseSiteInternal = mkOption {
       default = {};
       type = with types; attrsOf anything;
       internal = true;
-      description = ''
+      description = mdDoc ''
         Internal option to add configs to hbase-site.xml based on module options
       '';
     };
@@ -50,11 +138,11 @@ in
         type = types.package;
         default = pkgs.hbase;
         defaultText = literalExpression "pkgs.hbase";
-        description = "HBase package";
+        description = mdDoc "HBase package";
       };
 
       rootdir = mkOption {
-        description = ''
+        description = mdDoc ''
           This option will set "hbase.rootdir" in hbase-site.xml and determine
           the directory shared by region servers and into which HBase persists.
           The URL should be 'fully-qualified' to include the filesystem scheme.
@@ -68,7 +156,7 @@ in
         default = "/hbase";
       };
       zookeeperQuorum = mkOption {
-        description = ''
+        description = mdDoc ''
           This option will set "hbase.zookeeper.quorum" in hbase-site.xml.
           Comma separated list of servers in the ZooKeeper ensemble.
         '';
@@ -76,107 +164,36 @@ in
         example = "zk1.internal,zk2.internal,zk3.internal";
         default = null;
       };
-      master = {
-        enable = mkEnableOption "HBase Master";
-        initHDFS = mkEnableOption "initialization of the hbase directory on HDFS";
-
-        openFirewall = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Open firewall ports for HBase master.
-          '';
+    } // (let
+      ports = port: infoPort: {
+        port = mkOption {
+          type = types.int;
+          default = port;
+          description = mdDoc "RPC port";
         };
-      };
-      regionServer = {
-        enable = mkEnableOption "HBase RegionServer";
-
-        overrideHosts = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix
-            Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records
-            or /etc/hosts entries.
-
-          '';
-        };
-
-        openFirewall = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Open firewall ports for HBase master.
-          '';
+        infoPort = mkOption {
+          type = types.int;
+          default = infoPort;
+          description = mdDoc "web UI port";
         };
       };
-    };
-  };
-
-  config = mkMerge [
-    (mkIf cfg.hbase.master.enable {
-      services.hadoop.gatewayRole = {
-        enable = true;
-        enableHbaseCli = mkDefault true;
-      };
-
-      systemd.services.hbase-master = {
-        description = "HBase master";
-        wantedBy = [ "multi-user.target" ];
-
-        preStart = mkIf cfg.hbase.master.initHDFS ''
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfsadmin -safemode wait
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -mkdir -p ${cfg.hbase.rootdir}
-          HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -chown hbase ${cfg.hbase.rootdir}
+    in mapAttrs hbaseRoleOption {
+      master.initHDFS = mkEnableOption (mdDoc "initialization of the hbase directory on HDFS");
+      regionServer.overrideHosts = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc ''
+          Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix
+          Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records
+          or /etc/hosts entries.
         '';
-
-        serviceConfig = {
-          User = "hbase";
-          SyslogIdentifier = "hbase-master";
-          ExecStart = "${cfg.hbase.package}/bin/hbase --config ${hadoopConf} " +
-                      "master start";
-          Restart = "always";
-        };
-      };
-
-      services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
-
-      networking.firewall.allowedTCPPorts = (mkIf cfg.hbase.master.openFirewall [
-        16000 16010
-      ]);
-
-    })
-
-    (mkIf cfg.hbase.regionServer.enable {
-      services.hadoop.gatewayRole = {
-        enable = true;
-        enableHbaseCli = mkDefault true;
-      };
-
-      systemd.services.hbase-regionserver = {
-        description = "HBase RegionServer";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = "hbase";
-          SyslogIdentifier = "hbase-regionserver";
-          ExecStart = "${cfg.hbase.package}/bin/hbase --config /etc/hadoop-conf/ " +
-                      "regionserver start";
-          Restart = "always";
-        };
       };
+      thrift = ports 9090 9095;
+      rest = ports 8080 8085;
+    });
+  };
 
-      services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir;
-
-      networking = {
-        firewall.allowedTCPPorts = (mkIf cfg.hbase.regionServer.openFirewall [
-          16020 16030
-        ]);
-        hosts = mkIf cfg.hbase.regionServer.overrideHosts {
-          "127.0.0.2" = mkForce [ ];
-          "::1" = mkForce [ ];
-        };
-      };
-    })
+  config = mkMerge ([
 
     (mkIf cfg.gatewayRole.enable {
 
@@ -192,5 +209,10 @@ in
         isSystemUser = true;
       };
     })
-  ];
+  ] ++ (mapAttrsToList hbaseRoleConfig {
+    master = [ 16000 16010 ];
+    regionServer = [ 16020 16030 ];
+    thrift = with cfg.hbase.thrift; [ port infoPort ];
+    rest = with cfg.hbase.rest; [ port infoPort ];
+  }));
 }
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
index 75a97e535479..4a49bd0ddd43 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
@@ -8,7 +8,7 @@ let
 
   # Generator for HDFS service options
   hadoopServiceOption = { serviceName, firewallOption ? true, extraOpts ? null }: {
-    enable = mkEnableOption serviceName;
+    enable = mkEnableOption (lib.mdDoc serviceName);
     restartIfChanged = mkOption {
       type = types.bool;
       description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
index be0b9c13cd35..26077f35fdd0 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -31,7 +31,7 @@ in
 {
   options.services.hadoop.yarn = {
     resourcemanager = {
-      enable = mkEnableOption "Hadoop YARN ResourceManager";
+      enable = mkEnableOption (lib.mdDoc "Hadoop YARN ResourceManager");
       inherit restartIfChanged extraFlags extraEnv;
 
       openFirewall = mkOption {
@@ -43,7 +43,7 @@ in
       };
     };
     nodemanager = {
-      enable = mkEnableOption "Hadoop YARN NodeManager";
+      enable = mkEnableOption (lib.mdDoc "Hadoop YARN NodeManager");
       inherit restartIfChanged extraFlags extraEnv;
 
       resource = {
diff --git a/nixpkgs/nixos/modules/services/cluster/k3s/default.nix b/nixpkgs/nixos/modules/services/cluster/k3s/default.nix
index a1f6d4ecdfad..72b2f992a339 100644
--- a/nixpkgs/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/k3s/default.nix
@@ -13,7 +13,7 @@ in
 
   # interface
   options.services.k3s = {
-    enable = mkEnableOption "k3s";
+    enable = mkEnableOption (lib.mdDoc "k3s");
 
     package = mkOption {
       type = types.package;
@@ -25,7 +25,17 @@ in
     role = mkOption {
       description = lib.mdDoc ''
         Whether k3s should run as a server or agent.
-        Note that the server, by default, also runs as an agent.
+
+        If it's a server:
+
+        - By default it also runs workloads as an agent.
+        - Starts by default as a standalone server using an embedded sqlite datastore.
+        - Configure `clusterInit = true` to switch over to embedded etcd datastore and enable HA mode.
+        - Configure `serverAddr` to join an already-initialized HA cluster.
+
+        If it's an agent:
+
+        - `serverAddr` is required.
       '';
       default = "server";
       type = types.enum [ "server" "agent" ];
@@ -33,15 +43,44 @@ in
 
     serverAddr = mkOption {
       type = types.str;
-      description = lib.mdDoc "The k3s server to connect to. This option only makes sense for an agent.";
+      description = lib.mdDoc ''
+        The k3s server to connect to.
+
+        Servers and agents need to communicate each other. Read
+        [the networking docs](https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/#networking)
+        to know how to configure the firewall.
+      '';
       example = "https://10.0.0.10:6443";
       default = "";
     };
 
+    clusterInit = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Initialize HA cluster using an embedded etcd datastore.
+
+        If this option is `false` and `role` is `server`
+
+        On a server that was using the default embedded sqlite backend,
+        enabling this option will migrate to an embedded etcd DB.
+
+        If an HA cluster using the embedded etcd datastore was already initialized,
+        this option has no effect.
+
+        This option only makes sense in a server that is not connecting to another server.
+
+        If you are configuring an HA cluster with an embedded etcd,
+        the 1st server must have `clusterInit = true`
+        and other servers must connect to it using `serverAddr`.
+      '';
+    };
+
     token = mkOption {
       type = types.str;
       description = lib.mdDoc ''
-        The k3s token to use when connecting to the server. This option only makes sense for an agent.
+        The k3s token to use when connecting to a server.
+
         WARNING: This option will expose store your token unencrypted world-readable in the nix store.
         If this is undesired use the tokenFile option instead.
       '';
@@ -50,7 +89,7 @@ in
 
     tokenFile = mkOption {
       type = types.nullOr types.path;
-      description = lib.mdDoc "File path containing k3s token to use when connecting to the server. This option only makes sense for an agent.";
+      description = lib.mdDoc "File path containing k3s token to use when connecting to the server.";
       default = null;
     };
 
@@ -67,6 +106,14 @@ in
       description = lib.mdDoc "Only run the server. This option only makes sense for a server.";
     };
 
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        File path containing environment variables for configuring the k3s service in the format of an EnvironmentFile. See systemd.exec(5).
+      '';
+      default = null;
+    };
+
     configPath = mkOption {
       type = types.nullOr types.path;
       default = null;
@@ -86,14 +133,22 @@ in
         assertion = cfg.role == "agent" -> cfg.configPath != null || cfg.tokenFile != null || cfg.token != "";
         message = "token or tokenFile or configPath (with 'token' or 'token-file' keys) should be set if role is 'agent'";
       }
+      {
+        assertion = cfg.role == "agent" -> !cfg.disableAgent;
+        message = "disableAgent must be false if role is 'agent'";
+      }
+      {
+        assertion = cfg.role == "agent" -> !cfg.clusterInit;
+        message = "clusterInit must be false if role is 'agent'";
+      }
     ];
 
     environment.systemPackages = [ config.services.k3s.package ];
 
     systemd.services.k3s = {
       description = "k3s service";
-      after = [ "network.service" "firewall.service" ];
-      wants = [ "network.service" "firewall.service" ];
+      after = [ "firewall.service" "network-online.target" ];
+      wants = [ "firewall.service" "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       path = optional config.boot.zfs.enabled config.boot.zfs.package;
       serviceConfig = {
@@ -107,10 +162,12 @@ in
         LimitNPROC = "infinity";
         LimitCORE = "infinity";
         TasksMax = "infinity";
+        EnvironmentFile = cfg.environmentFile;
         ExecStart = concatStringsSep " \\\n " (
           [
             "${cfg.package}/bin/k3s ${cfg.role}"
           ]
+          ++ (optional cfg.clusterInit "--cluster-init")
           ++ (optional cfg.disableAgent "--disable-agent")
           ++ (optional (cfg.serverAddr != "") "--server ${cfg.serverAddr}")
           ++ (optional (cfg.token != "") "--token ${cfg.token}")
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index 99fd1e6f0492..dc851688fbec 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -22,7 +22,7 @@ in
 
     bootstrapAddons = mkOption {
       description = lib.mdDoc ''
-        Bootstrap addons are like regular addons, but they are applied with cluster-admin rigths.
+        Bootstrap addons are like regular addons, but they are applied with cluster-admin rights.
         They are applied at addon-manager startup only.
       '';
       default = { };
@@ -62,7 +62,7 @@ in
       '';
     };
 
-    enable = mkEnableOption "Kubernetes addon manager.";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes addon manager");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 5b1e9a687682..1c00329e6ccf 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  version = "1.7.1";
+  version = "1.10.1";
   cfg = config.services.kubernetes.addons.dns;
   ports = {
     dns = 10053;
@@ -12,7 +12,7 @@ let
   };
 in {
   options.services.kubernetes.addons.dns = {
-    enable = mkEnableOption "kubernetes dns addon";
+    enable = mkEnableOption (lib.mdDoc "kubernetes dns addon");
 
     clusterIp = mkOption {
       description = lib.mdDoc "Dns addon clusterIP";
@@ -23,9 +23,9 @@ 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>.
+      defaultText = literalMD ''
+        The `x.y.z.254` IP of
+        `config.${options.services.kubernetes.apiserver.serviceClusterIpRange}`.
       '';
       type = types.str;
     };
@@ -59,9 +59,9 @@ in {
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
-        imageDigest = "sha256:4a6e0769130686518325b21b0c1d0688b54e7c79244d48e1b15634e98e40c6ef";
+        imageDigest = "sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e";
         finalImageTag = version;
-        sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw";
+        sha256 = "0wg696920smmal7552a2zdhfncndn5kfammfa8bk8l7dz9bhk0y1";
       };
     };
 
@@ -136,6 +136,11 @@ in {
             resources = [ "nodes" ];
             verbs = [ "get" ];
           }
+          {
+            apiGroups = [ "discovery.k8s.io" ];
+            resources = [ "endpointslices" ];
+            verbs = [ "list" "watch" ];
+          }
         ];
       };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
index c9ae2c14bbf9..d5ec1e5e6d26 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -18,7 +18,8 @@ in
   imports = [
     (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ])
     (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"])
-    (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "port" ] ["services" "kubernetes" "apiserver" "insecurePort"])
+    (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecureBindAddress" ] "")
+    (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "insecurePort" ] "")
     (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
     (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ])
     (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ])
@@ -98,7 +99,7 @@ in
       type = listOf str;
     };
 
-    enable = mkEnableOption "Kubernetes apiserver";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes apiserver");
 
     enableAdmissionPlugins = mkOption {
       description = lib.mdDoc ''
@@ -164,18 +165,6 @@ in
       type = listOf str;
     };
 
-    insecureBindAddress = mkOption {
-      description = lib.mdDoc "The IP address on which to serve the --insecure-port.";
-      default = "127.0.0.1";
-      type = str;
-    };
-
-    insecurePort = mkOption {
-      description = lib.mdDoc "Kubernetes apiserver insecure listening port. (0 = disabled)";
-      default = 0;
-      type = int;
-    };
-
     kubeletClientCaFile = mkOption {
       description = lib.mdDoc "Path to a cert file for connecting to kubelet.";
       default = top.caFile;
@@ -376,8 +365,6 @@ in
                 "--proxy-client-cert-file=${cfg.proxyClientCertFile}"} \
               ${optionalString (cfg.proxyClientKeyFile != null)
                 "--proxy-client-key-file=${cfg.proxyClientKeyFile}"} \
-              --insecure-bind-address=${cfg.insecureBindAddress} \
-              --insecure-port=${toString cfg.insecurePort} \
               ${optionalString (cfg.runtimeConfig != "")
                 "--runtime-config=${cfg.runtimeConfig}"} \
               --secure-port=${toString cfg.securePort} \
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 6080e6f99151..18c82fc23593 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -10,7 +10,7 @@ in
 {
   imports = [
     (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "address" ] ["services" "kubernetes" "controllerManager" "bindAddress"])
-    (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "port" ] ["services" "kubernetes" "controllerManager" "insecurePort"])
+    (mkRemovedOptionModule [ "services" "kubernetes" "controllerManager" "insecurePort" ] "")
   ];
 
   ###### interface
@@ -35,7 +35,7 @@ in
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes controller manager";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes controller manager");
 
     extraOpts = mkOption {
       description = lib.mdDoc "Kubernetes controller manager extra command line options.";
@@ -50,12 +50,6 @@ in
       type = listOf str;
     };
 
-    insecurePort = mkOption {
-      description = lib.mdDoc "Kubernetes controller manager insecure listening port.";
-      default = 0;
-      type = int;
-    };
-
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes controller manager";
 
     leaderElect = mkOption {
@@ -133,7 +127,6 @@ in
           --leader-elect=${boolToString cfg.leaderElect} \
           ${optionalString (cfg.rootCaFile!=null)
             "--root-ca-file=${cfg.rootCaFile}"} \
-          --port=${toString cfg.insecurePort} \
           --secure-port=${toString cfg.securePort} \
           ${optionalString (cfg.serviceAccountKeyFile!=null)
             "--service-account-private-key-file=${cfg.serviceAccountKeyFile}"} \
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
index cb81eaaf0160..11c5adc6a885 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -12,7 +12,7 @@ in
 {
   ###### interface
   options.services.kubernetes.flannel = {
-    enable = mkEnableOption "enable flannel networking";
+    enable = mkEnableOption (lib.mdDoc "flannel networking");
   };
 
   ###### implementation
@@ -26,7 +26,6 @@ in
     };
 
     services.kubernetes.kubelet = {
-      networkPlugin = mkDefault "cni";
       cni.config = mkDefault [{
         name = "mynet";
         type = "flannel";
@@ -54,7 +53,7 @@ in
       };
     };
 
-    # give flannel som kubernetes rbac permissions if applicable
+    # give flannel some kubernetes rbac permissions if applicable
     services.kubernetes.addonManager.bootstrapAddons = mkIf ((storageBackend == "kubernetes") && (elem "RBAC" top.apiserver.authorizationMode)) {
 
       flannel-cr = {
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
index cbb1cffc1694..fd2dce7ee6a2 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -40,7 +40,7 @@ let
       key = mkOption {
         description = lib.mdDoc "Key of taint.";
         default = name;
-        defaultText = literalDocBook "Name of this submodule.";
+        defaultText = literalMD "Name of this submodule.";
         type = str;
       };
       value = mkOption {
@@ -62,6 +62,8 @@ in
     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "")
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "networkPlugin" ] "")
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "containerRuntime" ] "")
   ];
 
   ###### interface
@@ -133,19 +135,13 @@ in
       };
     };
 
-    containerRuntime = mkOption {
-      description = lib.mdDoc "Which container runtime type to use";
-      type = enum ["docker" "remote"];
-      default = "remote";
-    };
-
     containerRuntimeEndpoint = mkOption {
       description = lib.mdDoc "Endpoint at which to find the container runtime api interface/socket";
       type = str;
       default = "unix:///run/containerd/containerd.sock";
     };
 
-    enable = mkEnableOption "Kubernetes kubelet.";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes kubelet");
 
     extraOpts = mkOption {
       description = lib.mdDoc "Kubernetes kubelet extra command line options.";
@@ -170,14 +166,13 @@ in
       port = mkOption {
         description = lib.mdDoc "Kubernetes kubelet healthz port.";
         default = 10248;
-        type = int;
+        type = port;
       };
     };
 
     hostname = mkOption {
       description = lib.mdDoc "Kubernetes kubelet hostname override.";
-      default = config.networking.hostName;
-      defaultText = literalExpression "config.networking.hostName";
+      defaultText = literalExpression "config.networking.fqdnOrHostName";
       type = str;
     };
 
@@ -189,12 +184,6 @@ in
       default = {};
     };
 
-    networkPlugin = mkOption {
-      description = lib.mdDoc "Network plugin to use by Kubernetes.";
-      type = nullOr (enum ["cni" "kubenet"]);
-      default = "kubenet";
-    };
-
     nodeIp = mkOption {
       description = lib.mdDoc "IP address of the node. If set, kubelet will use this IP address for the node.";
       default = null;
@@ -210,7 +199,7 @@ in
     port = mkOption {
       description = lib.mdDoc "Kubernetes kubelet info server listening port.";
       default = 10250;
-      type = int;
+      type = port;
     };
 
     seedDockerImages = mkOption {
@@ -315,7 +304,6 @@ in
               "--cluster-dns=${cfg.clusterDns}"} \
             ${optionalString (cfg.clusterDomain != "")
               "--cluster-domain=${cfg.clusterDomain}"} \
-            --cni-conf-dir=${cniConfig} \
             ${optionalString (cfg.featureGates != [])
               "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
             --hairpin-mode=hairpin-veth \
@@ -323,8 +311,6 @@ in
             --healthz-port=${toString cfg.healthz.port} \
             --hostname-override=${cfg.hostname} \
             --kubeconfig=${kubeconfig} \
-            ${optionalString (cfg.networkPlugin != null)
-              "--network-plugin=${cfg.networkPlugin}"} \
             ${optionalString (cfg.nodeIp != null)
               "--node-ip=${cfg.nodeIp}"} \
             --pod-infra-container-image=pause \
@@ -340,7 +326,6 @@ in
             ${optionalString (cfg.tlsKeyFile != null)
               "--tls-private-key-file=${cfg.tlsKeyFile}"} \
             ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
-            --container-runtime=${cfg.containerRuntime} \
             --container-runtime-endpoint=${cfg.containerRuntimeEndpoint} \
             --cgroup-driver=systemd \
             ${cfg.extraOpts}
@@ -352,13 +337,13 @@ in
         };
       };
 
-      # Allways include cni plugins
+      # Always include cni plugins
       services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins pkgs.cni-plugin-flannel];
 
       boot.kernelModules = ["br_netfilter" "overlay"];
 
-      services.kubernetes.kubelet.hostname = with config.networking;
-        mkDefault (hostName + optionalString (domain != null) ".${domain}");
+      services.kubernetes.kubelet.hostname =
+        mkDefault config.networking.fqdnOrHostName;
 
       services.kubernetes.pki.certs = with top.lib; {
         kubelet = mkCert {
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
index 7c46ac855846..38682701ea15 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -41,7 +41,7 @@ in
   ###### interface
   options.services.kubernetes.pki = with lib.types; {
 
-    enable = mkEnableOption "easyCert issuer service";
+    enable = mkEnableOption (lib.mdDoc "easyCert issuer service");
 
     certs = mkOption {
       description = lib.mdDoc "List of certificate specs to feed to cert generator.";
@@ -114,9 +114,9 @@ in
     };
 
     etcClusterAdminKubeconfig = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Symlink a kubeconfig with cluster-admin privileges to environment path
-        (/etc/&lt;path&gt;).
+        (/etc/\<path\>).
       '';
       default = null;
       type = nullOr str;
@@ -266,11 +266,11 @@ in
           in
           ''
             export KUBECONFIG=${clusterAdminKubeconfig}
-            ${kubernetes}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
+            ${top.package}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
           '';
         })]);
 
-      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
+      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (cfg.etcClusterAdminKubeconfig != null)
         clusterAdminKubeconfig;
 
       environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
@@ -323,7 +323,7 @@ in
           systemctl restart flannel
         ''}
 
-        echo "Node joined succesfully"
+        echo "Node joined successfully"
       '')];
 
       # isolate etcd on loopback at the master node
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
index 51114c324994..015784f7e311 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -21,7 +21,7 @@ in
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes proxy";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes proxy");
 
     extraOpts = mkOption {
       description = lib.mdDoc "Kubernetes proxy extra command line options.";
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
index ddc67889a370..f31a92f36840 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -17,7 +17,7 @@ in
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes scheduler";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes scheduler");
 
     extraOpts = mkOption {
       description = lib.mdDoc "Kubernetes scheduler extra command line options.";
@@ -43,7 +43,7 @@ in
     port = mkOption {
       description = lib.mdDoc "Kubernetes scheduler listening port.";
       default = 10251;
-      type = int;
+      type = port;
     };
 
     verbosity = mkOption {
diff --git a/nixpkgs/nixos/modules/services/cluster/pacemaker/default.nix b/nixpkgs/nixos/modules/services/cluster/pacemaker/default.nix
index 41d98a460f5c..0f37f4b754fe 100644
--- a/nixpkgs/nixos/modules/services/cluster/pacemaker/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/pacemaker/default.nix
@@ -7,7 +7,7 @@ in
 {
   # interface
   options.services.pacemaker = {
-    enable = mkEnableOption "pacemaker";
+    enable = mkEnableOption (lib.mdDoc "pacemaker");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/cluster/patroni/default.nix b/nixpkgs/nixos/modules/services/cluster/patroni/default.nix
index 1685351e48d3..83b372f59497 100644
--- a/nixpkgs/nixos/modules/services/cluster/patroni/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/patroni/default.nix
@@ -15,7 +15,7 @@ in
 {
   options.services.patroni = {
 
-    enable = mkEnableOption "Patroni";
+    enable = mkEnableOption (lib.mdDoc "Patroni");
 
     postgresqlPackage = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/cluster/spark/default.nix b/nixpkgs/nixos/modules/services/cluster/spark/default.nix
index 30d8fa0fc412..bf39c5537332 100644
--- a/nixpkgs/nixos/modules/services/cluster/spark/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/spark/default.nix
@@ -7,7 +7,7 @@ with lib;
   options = {
     services.spark = {
       master = {
-        enable = mkEnableOption "Spark master service";
+        enable = mkEnableOption (lib.mdDoc "Spark master service");
         bind = mkOption {
           type = types.str;
           description = lib.mdDoc "Address the spark master binds to.";
@@ -35,7 +35,7 @@ with lib;
         };
       };
       worker = {
-        enable = mkEnableOption "Spark worker service";
+        enable = mkEnableOption (lib.mdDoc "Spark worker service");
         workDir = mkOption {
           type = types.path;
           description = lib.mdDoc "Spark worker work dir.";
diff --git a/nixpkgs/nixos/modules/services/computing/boinc/client.nix b/nixpkgs/nixos/modules/services/computing/boinc/client.nix
index ec88be95ecbf..1879fef9666f 100644
--- a/nixpkgs/nixos/modules/services/computing/boinc/client.nix
+++ b/nixpkgs/nixos/modules/services/computing/boinc/client.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.boinc;
   allowRemoteGuiRpcFlag = optionalString cfg.allowRemoteGuiRpc "--allow_remote_gui_rpc";
 
-  fhsEnv = pkgs.buildFHSUserEnv {
+  fhsEnv = pkgs.buildFHSEnv {
     name = "boinc-fhs-env";
     targetPkgs = pkgs': [ cfg.package ] ++ cfg.extraEnvPackages;
     runScript = "/bin/boinc_client";
@@ -47,13 +47,13 @@ in
       allowRemoteGuiRpc = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If set to true, any remote host can connect to and control this BOINC
           client (subject to password authentication). If instead set to false,
-          only the hosts listed in <varname>dataDir</varname>/remote_hosts.cfg will be allowed to
+          only the hosts listed in {var}`dataDir`/remote_hosts.cfg will be allowed to
           connect.
 
-          See also: <link xlink:href="http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access"/>
+          See also: <http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
         '';
       };
 
@@ -61,36 +61,23 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.virtualbox ]";
-        description = ''
+        description = lib.mdDoc ''
           Additional packages to make available in the environment in which
           BOINC will run. Common choices are:
-          <variablelist>
-            <varlistentry>
-              <term><varname>pkgs.virtualbox</varname></term>
-              <listitem><para>
-                The VirtualBox virtual machine framework. Required by some BOINC
-                projects, such as ATLAS@home.
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><varname>pkgs.ocl-icd</varname></term>
-              <listitem><para>
-                OpenCL infrastructure library. Required by BOINC projects that
-                use OpenCL, in addition to a device-specific OpenCL driver.
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><varname>pkgs.linuxPackages.nvidia_x11</varname></term>
-              <listitem><para>
-                Provides CUDA libraries. Required by BOINC projects that use
-                CUDA. Note that this requires an NVIDIA graphics device to be
-                present on the system.
-              </para><para>
-                Also provides OpenCL drivers for NVIDIA GPUs;
-                <varname>pkgs.ocl-icd</varname> is also needed in this case.
-              </para></listitem>
-            </varlistentry>
-          </variablelist>
+
+          - {var}`pkgs.virtualbox`:
+            The VirtualBox virtual machine framework. Required by some BOINC
+            projects, such as ATLAS@home.
+          - {var}`pkgs.ocl-icd`:
+            OpenCL infrastructure library. Required by BOINC projects that
+            use OpenCL, in addition to a device-specific OpenCL driver.
+          - {var}`pkgs.linuxPackages.nvidia_x11`:
+            Provides CUDA libraries. Required by BOINC projects that use
+            CUDA. Note that this requires an NVIDIA graphics device to be
+            present on the system.
+
+            Also provides OpenCL drivers for NVIDIA GPUs;
+            {var}`pkgs.ocl-icd` is also needed in this case.
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix b/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix
index ad88fffe43ce..1229e5ac987e 100644
--- a/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix
+++ b/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix
@@ -18,7 +18,7 @@ in
     '')
   ];
   options.services.foldingathome = {
-    enable = mkEnableOption "Enable the Folding@home client";
+    enable = mkEnableOption (lib.mdDoc "Folding@home client");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
index 0860b9a220c3..344c43a429b3 100644
--- a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
@@ -76,7 +76,7 @@ in
       };
 
       dbdserver = {
-        enable = mkEnableOption "SlurmDBD service";
+        enable = mkEnableOption (lib.mdDoc "SlurmDBD service");
 
         dbdHost = mkOption {
           type = types.str;
@@ -117,7 +117,7 @@ in
       };
 
       client = {
-        enable = mkEnableOption "slurm client daemon";
+        enable = mkEnableOption (lib.mdDoc "slurm client daemon");
       };
 
       enableStools = mkOption {
@@ -281,11 +281,11 @@ in
         type = types.path;
         internal = true;
         default = etcSlurm;
-        defaultText = literalDocBook ''
+        defaultText = literalMD ''
           Directory created from generated config files and
-          <literal>config.${opt.extraConfigPaths}</literal>.
+          `config.${opt.extraConfigPaths}`.
         '';
-        description = ''
+        description = lib.mdDoc ''
           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.
         '';
@@ -383,7 +383,7 @@ in
       "d /var/spool/slurmd 755 root root -"
     ];
 
-    services.openssh.forwardX11 = mkIf cfg.client.enable (mkDefault true);
+    services.openssh.settings.X11Forwarding = mkIf cfg.client.enable (mkDefault true);
 
     systemd.services.slurmctld = mkIf (cfg.server.enable) {
       path = with pkgs; [ wrappedSlurm munge coreutils ]
diff --git a/nixpkgs/nixos/modules/services/computing/torque/mom.nix b/nixpkgs/nixos/modules/services/computing/torque/mom.nix
index bf3679847b94..5dd41429bf81 100644
--- a/nixpkgs/nixos/modules/services/computing/torque/mom.nix
+++ b/nixpkgs/nixos/modules/services/computing/torque/mom.nix
@@ -17,7 +17,7 @@ in
   options = {
 
     services.torque.mom = {
-      enable = mkEnableOption "torque computing node";
+      enable = mkEnableOption (lib.mdDoc "torque computing node");
 
       serverNode = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/computing/torque/server.nix b/nixpkgs/nixos/modules/services/computing/torque/server.nix
index 8d923fc04d46..02f20fb37c10 100644
--- a/nixpkgs/nixos/modules/services/computing/torque/server.nix
+++ b/nixpkgs/nixos/modules/services/computing/torque/server.nix
@@ -11,7 +11,7 @@ in
 
     services.torque.server = {
 
-      enable = mkEnableOption "torque server";
+      enable = mkEnableOption (lib.mdDoc "torque server");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
index ab1a8076c935..595374ea1e5b 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -1,4 +1,4 @@
-# NixOS module for Buildbot continous integration server.
+# NixOS module for Buildbot continuous integration server.
 
 { config, lib, options, pkgs, ... }:
 
@@ -8,9 +8,10 @@ let
   cfg = config.services.buildbot-master;
   opt = options.services.buildbot-master;
 
-  python = cfg.package.pythonModule;
+  package = pkgs.python3.pkgs.toPythonModule cfg.package;
+  python = package.pythonModule;
 
-  escapeStr = s: escape ["'"] s;
+  escapeStr = escape [ "'" ];
 
   defaultMasterCfg = pkgs.writeText "master.cfg" ''
     from buildbot.plugins import *
@@ -94,7 +95,7 @@ in {
         type = types.path;
         description = lib.mdDoc "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
         default = defaultMasterCfg;
-        defaultText = literalDocBook ''generated configuration file'';
+        defaultText = literalMD ''generated configuration file'';
         example = "/etc/nixos/buildbot/master.cfg";
       };
 
@@ -206,16 +207,16 @@ in {
 
       port = mkOption {
         default = 8010;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc "Specifies port number on which the buildbot HTTP interface listens.";
       };
 
       package = mkOption {
         type = types.package;
-        default = pkgs.python3Packages.buildbot-full;
-        defaultText = literalExpression "pkgs.python3Packages.buildbot-full";
+        default = pkgs.buildbot-full;
+        defaultText = literalExpression "pkgs.buildbot-full";
         description = lib.mdDoc "Package to use for buildbot.";
-        example = literalExpression "pkgs.python3Packages.buildbot";
+        example = literalExpression "pkgs.buildbot";
       };
 
       packages = mkOption {
@@ -245,9 +246,7 @@ in {
         description = "Buildbot User.";
         isNormalUser = true;
         createHome = true;
-        home = cfg.home;
-        group = cfg.group;
-        extraGroups = cfg.extraGroups;
+        inherit (cfg) home group extraGroups;
         useDefaultShell = true;
       };
     };
@@ -257,7 +256,7 @@ in {
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       path = cfg.packages ++ cfg.pythonPackages python.pkgs;
-      environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ cfg.package ])}/${python.sitePackages}";
+      environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ package ])}/${python.sitePackages}";
 
       preStart = ''
         mkdir -vp "${cfg.buildbotDir}"
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 245f685764dc..7e78b8935f81 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -8,7 +8,8 @@ let
   cfg = config.services.buildbot-worker;
   opt = options.services.buildbot-worker;
 
-  python = cfg.package.pythonModule;
+  package = pkgs.python3.pkgs.toPythonModule cfg.package;
+  python = package.pythonModule;
 
   tacFile = pkgs.writeText "aur-buildbot-worker.tac" ''
     import os
@@ -121,15 +122,15 @@ in {
       keepalive = mkOption {
         default = 600;
         type = types.int;
-        description = "
+        description = lib.mdDoc ''
           This is a number that indicates how frequently keepalive messages should be sent
           from the worker to the buildmaster, expressed in seconds.
-        ";
+        '';
       };
 
       package = mkOption {
         type = types.package;
-        default = pkgs.python3Packages.buildbot-worker;
+        default = pkgs.buildbot-worker;
         defaultText = literalExpression "pkgs.python3Packages.buildbot-worker";
         description = lib.mdDoc "Package to use for buildbot worker.";
         example = literalExpression "pkgs.python2Packages.buildbot-worker";
@@ -168,7 +169,7 @@ in {
       after = [ "network.target" "buildbot-master.service" ];
       wantedBy = [ "multi-user.target" ];
       path = cfg.packages;
-      environment.PYTHONPATH = "${python.withPackages (p: [ cfg.package ])}/${python.sitePackages}";
+      environment.PYTHONPATH = "${python.withPackages (p: [ package ])}/${python.sitePackages}";
 
       preStart = ''
         mkdir -vp "${cfg.buildbotDir}/info"
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix
index cafa40dc6e5f..7c8f77580ff6 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix
@@ -9,7 +9,7 @@ let
     inherit name;
     value = mkOption {
       default = null;
-      inherit description;
+      description = lib.mdDoc description;
       type = types.nullOr types.lines;
     } // (if example == null then {} else { inherit example; });
   };
@@ -168,7 +168,7 @@ let
       hooksPath = mkOption {
         type = types.path;
         default = hooksDir config;
-        defaultText = literalDocBook "generated from <option>services.buildkite-agents.&lt;name&gt;.hooks</option>";
+        defaultText = literalMD "generated from {option}`services.buildkite-agents.<name>.hooks`";
         description = lib.mdDoc ''
           Path to the directory storing the hooks.
           Consider using {option}`services.buildkite-agents.<name>.hooks.<name>`
@@ -193,7 +193,7 @@ in
   options.services.buildkite-agents = mkOption {
     type = types.attrsOf (types.submodule buildkiteOptions);
     default = {};
-    description = ''
+    description = lib.mdDoc ''
       Attribute set of buildkite agents.
       The attribute key is combined with the hostname and a unique integer to
       create the final agent name. This can be overridden by setting the `name`
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gitea-actions-runner.nix b/nixpkgs/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
new file mode 100644
index 000000000000..fb70c4899126
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
@@ -0,0 +1,237 @@
+{ config
+, lib
+, pkgs
+, utils
+, ...
+}:
+
+let
+  inherit (lib)
+    any
+    attrValues
+    concatStringsSep
+    escapeShellArg
+    hasInfix
+    hasSuffix
+    optionalAttrs
+    optionals
+    literalExpression
+    mapAttrs'
+    mkEnableOption
+    mkOption
+    mkPackageOptionMD
+    mkIf
+    nameValuePair
+    types
+  ;
+
+  inherit (utils)
+    escapeSystemdPath
+  ;
+
+  cfg = config.services.gitea-actions-runner;
+
+  # Check whether any runner instance label requires a container runtime
+  # Empty label strings result in the upstream defined defaultLabels, which require docker
+  # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
+  hasDockerScheme = instance:
+    instance.labels == [] || any (label: hasInfix ":docker:" label) instance.labels;
+  wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
+
+  hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
+
+  # provide shorthands for whether container runtimes are enabled
+  hasDocker = config.virtualisation.docker.enable;
+  hasPodman = config.virtualisation.podman.enable;
+
+  tokenXorTokenFile = instance:
+    (instance.token == null && instance.tokenFile != null) ||
+    (instance.token != null && instance.tokenFile == null);
+in
+{
+  meta.maintainers = with lib.maintainers; [
+    hexa
+  ];
+
+  options.services.gitea-actions-runner = with types; {
+    package = mkPackageOptionMD pkgs "gitea-actions-runner" { };
+
+    instances = mkOption {
+      default = {};
+      description = lib.mdDoc ''
+        Gitea Actions Runner instances.
+      '';
+      type = attrsOf (submodule {
+        options = {
+          enable = mkEnableOption (lib.mdDoc "Gitea Actions Runner instance");
+
+          name = mkOption {
+            type = str;
+            example = literalExpression "config.networking.hostName";
+            description = lib.mdDoc ''
+              The name identifying the runner instance towards the Gitea/Forgejo instance.
+            '';
+          };
+
+          url = mkOption {
+            type = str;
+            example = "https://forge.example.com";
+            description = lib.mdDoc ''
+              Base URL of your Gitea/Forgejo instance.
+            '';
+          };
+
+          token = mkOption {
+            type = nullOr str;
+            default = null;
+            description = lib.mdDoc ''
+              Plain token to register at the configured Gitea/Forgejo instance.
+            '';
+          };
+
+          tokenFile = mkOption {
+            type = nullOr (either str path);
+            default = null;
+            description = lib.mdDoc ''
+              Path to an environment file, containing the `TOKEN` environment
+              variable, that holds a token to register at the configured
+              Gitea/Forgejo instance.
+            '';
+          };
+
+          labels = mkOption {
+            type = listOf str;
+            example = literalExpression ''
+              [
+                # provide a debian base with nodejs for actions
+                "debian-latest:docker://node:18-bullseye"
+                # fake the ubuntu name, because node provides no ubuntu builds
+                "ubuntu-latest:docker://node:18-bullseye"
+                # provide native execution on the host
+                #"native:host"
+              ]
+            '';
+            description = lib.mdDoc ''
+              Labels used to map jobs to their runtime environment. Changing these
+              labels currently requires a new registration token.
+
+              Many common actions require bash, git and nodejs, as well as a filesystem
+              that follows the filesystem hierarchy standard.
+            '';
+          };
+
+          hostPackages = mkOption {
+            type = listOf package;
+            default = with pkgs; [
+              bash
+              coreutils
+              curl
+              gawk
+              gitMinimal
+              gnused
+              nodejs
+              wget
+            ];
+            defaultText = literalExpression ''
+              with pkgs; [
+                bash
+                coreutils
+                curl
+                gawk
+                gitMinimal
+                gnused
+                nodejs
+                wget
+              ]
+            '';
+            description = lib.mdDoc ''
+              List of packages, that are available to actions, when the runner is configured
+              with a host execution label.
+            '';
+          };
+        };
+      });
+    };
+  };
+
+  config = mkIf (cfg.instances != {}) {
+    assertions = [ {
+      assertion = any tokenXorTokenFile (attrValues cfg.instances);
+      message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
+    } {
+      assertion = wantsContainerRuntime -> hasDocker || hasPodman;
+      message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
+    } ];
+
+    systemd.services = let
+      mkRunnerService = name: instance: let
+        wantsContainerRuntime = hasDockerScheme instance;
+        wantsHost = hasHostScheme instance;
+        wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
+        wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
+      in
+        nameValuePair "gitea-runner-${escapeSystemdPath name}" {
+          inherit (instance) enable;
+          description = "Gitea Actions Runner";
+          after = [
+            "network-online.target"
+          ] ++ optionals (wantsDocker) [
+            "docker.service"
+          ] ++ optionals (wantsPodman) [
+            "podman.service"
+          ];
+          wantedBy = [
+            "multi-user.target"
+          ];
+          environment = optionalAttrs (instance.token != null) {
+            TOKEN = "${instance.token}";
+          } // optionalAttrs (wantsPodman) {
+            DOCKER_HOST = "unix:///run/podman/podman.sock";
+          };
+          path = with pkgs; [
+            coreutils
+          ] ++ lib.optionals wantsHost instance.hostPackages;
+          serviceConfig = {
+            DynamicUser = true;
+            User = "gitea-runner";
+            StateDirectory = "gitea-runner";
+            WorkingDirectory = "-/var/lib/gitea-runner/${name}";
+            ExecStartPre = pkgs.writeShellScript "gitea-register-runner-${name}" ''
+              export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
+              mkdir -vp "$INSTANCE_DIR"
+              cd "$INSTANCE_DIR"
+
+              # force reregistration on changed labels
+              export LABELS_FILE="$INSTANCE_DIR/.labels"
+              export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
+              export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)"
+
+              if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then
+                # remove existing registration file, so that changing the labels forces a re-registration
+                rm -v "$INSTANCE_DIR/.runner" || true
+
+                # perform the registration
+                ${cfg.package}/bin/act_runner register --no-interactive \
+                  --instance ${escapeShellArg instance.url} \
+                  --token "$TOKEN" \
+                  --name ${escapeShellArg instance.name} \
+                  --labels ${escapeShellArg (concatStringsSep "," instance.labels)}
+
+                # and write back the configured labels
+                echo "$LABELS_WANTED" > "$LABELS_FILE"
+              fi
+
+            '';
+            ExecStart = "${cfg.package}/bin/act_runner daemon";
+            SupplementaryGroups = optionals (wantsDocker) [
+              "docker"
+            ] ++ optionals (wantsPodman) [
+              "podman"
+            ];
+          } // optionalAttrs (instance.tokenFile != null) {
+            EnvironmentFile = instance.tokenFile;
+          };
+        };
+    in mapAttrs' mkRunnerService cfg.instances;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix b/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix
index 9abe13a89af1..27cfee92c75a 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix
@@ -1,377 +1,25 @@
-{ config, pkgs, lib, ... }:
+{ config
+, pkgs
+, lib
+, ...
+}@args:
+
 with lib;
+
 let
   cfg = config.services.github-runner;
-  svcName = "github-runner";
-  systemdDir = "${svcName}/${cfg.name}";
-  # %t: Runtime directory root (usually /run); see systemd.unit(5)
-  runtimeDir = "%t/${systemdDir}";
-  # %S: State directory root (usually /var/lib); see systemd.unit(5)
-  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 = {
-    enable = mkOption {
-      default = false;
-      example = true;
-      description = lib.mdDoc ''
-        Whether to enable GitHub Actions runner.
-
-        Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
-        [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
-      '';
-      type = lib.types.bool;
-    };
-
-    url = mkOption {
-      type = types.str;
-      description = lib.mdDoc ''
-        Repository to add the runner to.
-
-        Changing this option triggers a new runner registration.
-
-        IMPORTANT: If your token is org-wide (not per repository), you need to
-        provide a github org link, not a single repository, so do it like this
-        `https://github.com/nixos`, not like this
-        `https://github.com/nixos/nixpkgs`.
-        Otherwise, you are going to get a `404 NotFound`
-        from `POST https://api.github.com/actions/runner-registration`
-        in the configure script.
-      '';
-      example = "https://github.com/nixos/nixpkgs";
-    };
-
-    tokenFile = mkOption {
-      type = types.path;
-      description = lib.mdDoc ''
-        The full path to a file which contains either a runner registration token or a
-        personal access token (PAT).
-        The file should contain exactly one line with the token without any newline.
-        If a registration token is given, it can be used to re-register a runner of the same
-        name but is time-limited. If the file contains a PAT, the service creates a new
-        registration token on startup as needed. Make sure the PAT has a scope of
-        `admin:org` for organization-wide registrations or a scope of
-        `repo` for a single repository.
-
-        Changing this option or the file's content triggers a new runner registration.
-      '';
-      example = "/run/secrets/github-runner/nixos.token";
-    };
-
-    name = mkOption {
-      # Same pattern as for `networking.hostName`
-      type = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
-      description = lib.mdDoc ''
-        Name of the runner to configure. Defaults to the hostname.
-
-        Changing this option triggers a new runner registration.
-      '';
-      example = "nixos";
-      default = config.networking.hostName;
-      defaultText = literalExpression "config.networking.hostName";
-    };
-
-    runnerGroup = mkOption {
-      type = types.nullOr types.str;
-      description = lib.mdDoc ''
-        Name of the runner group to add this runner to (defaults to the default runner group).
-
-        Changing this option triggers a new runner registration.
-      '';
-      default = null;
-    };
 
-    extraLabels = mkOption {
-      type = types.listOf types.str;
-      description = lib.mdDoc ''
-        Extra labels in addition to the default (`["self-hosted", "Linux", "X64"]`).
-
-        Changing this option triggers a new runner registration.
-      '';
-      example = literalExpression ''[ "nixos" ]'';
-      default = [ ];
-    };
-
-    replace = mkOption {
-      type = types.bool;
-      description = lib.mdDoc ''
-        Replace any existing runner with the same name.
-
-        Without this flag, registering a new runner with the same name fails.
-      '';
-      default = false;
-    };
-
-    extraPackages = mkOption {
-      type = types.listOf types.package;
-      description = lib.mdDoc ''
-        Extra packages to add to `PATH` of the service to make them available to workflows.
-      '';
-      default = [ ];
-    };
-
-    package = mkOption {
-      type = types.package;
-      description = lib.mdDoc ''
-        Which github-runner derivation to use.
-      '';
-      default = pkgs.github-runner;
-      defaultText = literalExpression "pkgs.github-runner";
-    };
-
-    ephemeral = mkOption {
-      type = types.bool;
-      description = lib.mdDoc ''
-        If enabled, causes the following behavior:
-
-        - Passes the `--ephemeral` flag to the runner configuration script
-        - De-registers and stops the runner with GitHub after it has processed one job
-        - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
-        - Restarts the service after its successful exit
-        - On start, wipes the state directory and configures a new runner
-
-        You should only enable this option if `tokenFile` points to a file which contains a
-        personal access token (PAT). If you're using the option with a registration token, restarting the
-        service will fail as soon as the registration token expired.
-      '';
-      default = false;
-    };
-  };
+{
+  options.services.github-runner = import ./github-runner/options.nix (args // {
+    # Users don't need to specify options.services.github-runner.name; it will default
+    # to the hostname.
+    includeNameDefault = true;
+  });
 
   config = mkIf cfg.enable {
-    warnings = optionals (isStorePath cfg.tokenFile) [
-      ''
-        `services.github-runner.tokenFile` points to the Nix store and, therefore, is world-readable.
-        Consider using a path outside of the Nix store to keep the token private.
-      ''
-    ];
-
-    systemd.services.${svcName} = {
-      description = "GitHub Actions runner";
-
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-
-      environment = {
-        HOME = runtimeDir;
-        RUNNER_ROOT = stateDir;
-      };
-
-      path = (with pkgs; [
-        bash
-        coreutils
-        git
-        gnutar
-        gzip
-      ]) ++ [
-        config.nix.package
-      ] ++ cfg.extraPackages;
-
-      serviceConfig = rec {
-        ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service";
-
-        # Does the following, sequentially:
-        # - 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
-            # Wrapper script which expects the full path of the state, runtime and logs
-            # directory as arguments. Overrides the respective systemd variables to provide
-            # unambiguous directory names. This becomes relevant, for example, if the
-            # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
-            # to contain more than one directory. This causes systemd to set the respective
-            # environment variables with the path of all of the given directories, separated
-            # by a colon.
-            writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
-              set -euo pipefail
-
-              STATE_DIRECTORY="$1"
-              RUNTIME_DIRECTORY="$2"
-              LOGS_DIRECTORY="$3"
-
-              ${lines}
-            '';
-            currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
-            runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" ] cfg;
-            newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
-            newConfigTokenFilename = ".new-token";
-            runnerCredFiles = [
-              ".credentials"
-              ".credentials_rsaparams"
-              ".runner"
-            ];
-            unconfigureRunner = writeScript "unconfigure" ''
-              differs=
-
-              if [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then
-                # State directory is not empty
-                # 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 \
-                  "$STATE_DIRECTORY"/${currentConfigTokenFilename} \
-                  ${escapeShellArg cfg.tokenFile} \
-                  >/dev/null 2>&1 || differs=1
-                # If .credentials does not exist, assume a previous run de-registered the runner on stop (ephemeral mode)
-                [[ ! -f "$STATE_DIRECTORY/.credentials" ]] && differs=1
-              fi
-
-              if [[ -n "$differs" ]]; then
-                echo "Config has changed, removing old runner state."
-                # In ephemeral mode, the runner deletes the `.credentials` file after de-registering it with GitHub
-                [[ -f "$STATE_DIRECTORY/.credentials" ]] && 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" ''
-              if [[ -e "$STATE_DIRECTORY/${newConfigTokenFilename}" ]]; then
-                echo "Configuring GitHub Actions Runner"
-
-                args=(
-                  --unattended
-                  --disableupdate
-                  --work "$RUNTIME_DIRECTORY"
-                  --url ${escapeShellArg cfg.url}
-                  --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)}
-                  --name ${escapeShellArg cfg.name}
-                  ${optionalString cfg.replace "--replace"}
-                  ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
-                  ${optionalString cfg.ephemeral "--ephemeral"}
-                )
-
-                # If the token file contains a PAT (i.e., it starts with "ghp_"), we have to use the --pat option,
-                # if it is not a PAT, we assume it contains a registration token and use the --token option
-                token=$(<"$STATE_DIRECTORY/${newConfigTokenFilename}")
-                if [[ "$token" =~ ^ghp_* ]]; then
-                  args+=(--pat "$token")
-                else
-                  args+=(--token "$token")
-                fi
-
-                ${cfg.package}/bin/config.sh "''${args[@]}"
-
-                # Move the automatically created _diag dir to the logs dir
-                mkdir -p  "$STATE_DIRECTORY/_diag"
-                cp    -r  "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
-                rm    -rf "$STATE_DIRECTORY/_diag/"
-
-                # Cleanup token from config
-                rm "$STATE_DIRECTORY/${newConfigTokenFilename}"
-
-                # Symlink to new config
-                ln -s '${newConfigPath}' "${currentConfigPath}"
-              fi
-            '';
-            setupRuntimeDir = writeScript "setup-runtime-dirs" ''
-              # Link _diag dir
-              ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag"
-
-              # Link the runner credentials to the runtime dir
-              ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/"
-            '';
-          in
-          map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
-            "+${unconfigureRunner}" # runs as root
-            configureRunner
-            setupRuntimeDir
-          ];
-
-        # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner)
-        # to trigger a fresh registration.
-        Restart = if cfg.ephemeral then "on-success" else "no";
-
-        # Contains _diag
-        LogsDirectory = [ systemdDir ];
-        # Default RUNNER_ROOT which contains ephemeral Runner data
-        RuntimeDirectory = [ systemdDir ];
-        # Home of persistent runner data, e.g., credentials
-        StateDirectory = [ systemdDir ];
-        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;
-
-        KillSignal = "SIGINT";
-
-        # Hardening (may overlap with DynamicUser=)
-        # The following options are only for optimizing:
-        # systemd-analyze security github-runner
-        AmbientCapabilities = "";
-        CapabilityBoundingSet = "";
-        # ProtectClock= adds DeviceAllow=char-rtc r
-        DeviceAllow = "";
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectSystem = "strict";
-        RemoveIPC = true;
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-        UMask = "0066";
-        ProtectProc = "invisible";
-        SystemCallFilter = [
-          "~@clock"
-          "~@cpu-emulation"
-          "~@module"
-          "~@mount"
-          "~@obsolete"
-          "~@raw-io"
-          "~@reboot"
-          "~capset"
-          "~setdomainname"
-          "~sethostname"
-        ];
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
-
-        # Needs network access
-        PrivateNetwork = false;
-        # Cannot be true due to Node
-        MemoryDenyWriteExecute = false;
-
-        # The more restrictive "pid" option makes `nix` commands in CI emit
-        # "GC Warning: Couldn't read /proc/stat"
-        # You may want to set this to "pid" if not using `nix` commands
-        ProcSubset = "all";
-        # Coverage programs for compiled code such as `cargo-tarpaulin` disable
-        # ASLR (address space layout randomization) which requires the
-        # `personality` syscall
-        # You may want to set this to `true` if not using coverage tooling on
-        # compiled code
-        LockPersonality = false;
-      };
-    };
+    services.github-runners.${cfg.name} = cfg;
   };
+
+  meta.maintainers = with maintainers; [ veehaitch newam thomasjm ];
 }
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/github-runner/options.nix b/nixpkgs/nixos/modules/services/continuous-integration/github-runner/options.nix
new file mode 100644
index 000000000000..ce8809213724
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/github-runner/options.nix
@@ -0,0 +1,211 @@
+{ config
+, lib
+, pkgs
+, includeNameDefault
+, ...
+}:
+
+with lib;
+
+{
+  enable = mkOption {
+    default = false;
+    example = true;
+    description = lib.mdDoc ''
+      Whether to enable GitHub Actions runner.
+
+      Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
+      [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
+    '';
+    type = lib.types.bool;
+  };
+
+  url = mkOption {
+    type = types.str;
+    description = lib.mdDoc ''
+      Repository to add the runner to.
+
+      Changing this option triggers a new runner registration.
+
+      IMPORTANT: If your token is org-wide (not per repository), you need to
+      provide a github org link, not a single repository, so do it like this
+      `https://github.com/nixos`, not like this
+      `https://github.com/nixos/nixpkgs`.
+      Otherwise, you are going to get a `404 NotFound`
+      from `POST https://api.github.com/actions/runner-registration`
+      in the configure script.
+    '';
+    example = "https://github.com/nixos/nixpkgs";
+  };
+
+  tokenFile = mkOption {
+    type = types.path;
+    description = lib.mdDoc ''
+      The full path to a file which contains either
+
+      * a fine-grained personal access token (PAT),
+      * a classic PAT
+      * or a runner registration token
+
+      Changing this option or the `tokenFile`’s content triggers a new runner registration.
+
+      We suggest using the fine-grained PATs. A runner registration token is valid
+      only for 1 hour after creation, so the next time the runner configuration changes
+      this will give you hard-to-debug HTTP 404 errors in the configure step.
+
+      The file should contain exactly one line with the token without any newline.
+      (Use `echo -n '…token…' > …token file…` to make sure no newlines sneak in.)
+
+      If the file contains a PAT, the service creates a new registration token
+      on startup as needed.
+      If a registration token is given, it can be used to re-register a runner of the same
+      name but is time-limited as noted above.
+
+      For fine-grained PATs:
+
+      Give it "Read and Write access to organization/repository self hosted runners",
+      depending on whether it is organization wide or per-repository. You might have to
+      experiment a little, fine-grained PATs are a `beta` Github feature and still subject
+      to change; nonetheless they are the best option at the moment.
+
+      For classic PATs:
+
+      Make sure the PAT has a scope of `admin:org` for organization-wide registrations
+      or a scope of `repo` for a single repository.
+
+      For runner registration tokens:
+
+      Nothing special needs to be done, but updating will break after one hour,
+      so these are not recommended.
+    '';
+    example = "/run/secrets/github-runner/nixos.token";
+  };
+
+  name = let
+    # Same pattern as for `networking.hostName`
+    baseType = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
+  in mkOption {
+    type = if includeNameDefault then baseType else types.nullOr baseType;
+    description = lib.mdDoc ''
+      Name of the runner to configure. Defaults to the hostname.
+
+      Changing this option triggers a new runner registration.
+    '';
+    example = "nixos";
+  } // (if includeNameDefault then {
+    default = config.networking.hostName;
+    defaultText = literalExpression "config.networking.hostName";
+  } else {
+    default = null;
+  });
+
+  runnerGroup = mkOption {
+    type = types.nullOr types.str;
+    description = lib.mdDoc ''
+      Name of the runner group to add this runner to (defaults to the default runner group).
+
+      Changing this option triggers a new runner registration.
+    '';
+    default = null;
+  };
+
+  extraLabels = mkOption {
+    type = types.listOf types.str;
+    description = lib.mdDoc ''
+      Extra labels in addition to the default (`["self-hosted", "Linux", "X64"]`).
+
+      Changing this option triggers a new runner registration.
+    '';
+    example = literalExpression ''[ "nixos" ]'';
+    default = [ ];
+  };
+
+  replace = mkOption {
+    type = types.bool;
+    description = lib.mdDoc ''
+      Replace any existing runner with the same name.
+
+      Without this flag, registering a new runner with the same name fails.
+    '';
+    default = false;
+  };
+
+  extraPackages = mkOption {
+    type = types.listOf types.package;
+    description = lib.mdDoc ''
+      Extra packages to add to `PATH` of the service to make them available to workflows.
+    '';
+    default = [ ];
+  };
+
+  extraEnvironment = mkOption {
+    type = types.attrs;
+    description = lib.mdDoc ''
+      Extra environment variables to set for the runner, as an attrset.
+    '';
+    example = {
+      GIT_CONFIG = "/path/to/git/config";
+    };
+    default = {};
+  };
+
+  serviceOverrides = mkOption {
+    type = types.attrs;
+    description = lib.mdDoc ''
+      Modify the systemd service. Can be used to, e.g., adjust the sandboxing options.
+    '';
+    example = {
+      ProtectHome = false;
+      RestrictAddressFamilies = [ "AF_PACKET" ];
+    };
+    default = {};
+  };
+
+  package = mkOption {
+    type = types.package;
+    description = lib.mdDoc ''
+      Which github-runner derivation to use.
+    '';
+    default = pkgs.github-runner;
+    defaultText = literalExpression "pkgs.github-runner";
+  };
+
+  ephemeral = mkOption {
+    type = types.bool;
+    description = lib.mdDoc ''
+      If enabled, causes the following behavior:
+
+      - Passes the `--ephemeral` flag to the runner configuration script
+      - De-registers and stops the runner with GitHub after it has processed one job
+      - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
+      - Restarts the service after its successful exit
+      - On start, wipes the state directory and configures a new runner
+
+      You should only enable this option if `tokenFile` points to a file which contains a
+      personal access token (PAT). If you're using the option with a registration token, restarting the
+      service will fail as soon as the registration token expired.
+    '';
+    default = false;
+  };
+
+  user = mkOption {
+    type = types.nullOr types.str;
+    description = lib.mdDoc ''
+      User under which to run the service. If null, will use a systemd dynamic user.
+    '';
+    default = null;
+    defaultText = literalExpression "username";
+  };
+
+  workDir = mkOption {
+    type = with types; nullOr str;
+    description = lib.mdDoc ''
+      Working directory, available as `$GITHUB_WORKSPACE` during workflow runs
+      and used as a default for [repository checkouts](https://github.com/actions/checkout).
+      The service cleans this directory on every service start.
+
+      A value of `null` will default to the systemd `RuntimeDirectory`.
+    '';
+    default = null;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/github-runner/service.nix b/nixpkgs/nixos/modules/services/continuous-integration/github-runner/service.nix
new file mode 100644
index 000000000000..55df83362cb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/github-runner/service.nix
@@ -0,0 +1,267 @@
+{ config
+, lib
+, pkgs
+
+, cfg ? config.services.github-runner
+, svcName
+
+, systemdDir ? "${svcName}/${cfg.name}"
+  # %t: Runtime directory root (usually /run); see systemd.unit(5)
+, runtimeDir ? "%t/${systemdDir}"
+  # %S: State directory root (usually /var/lib); see systemd.unit(5)
+, 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"
+
+, ...
+}:
+
+with lib;
+
+let
+  workDir = if cfg.workDir == null then runtimeDir else cfg.workDir;
+in
+{
+  description = "GitHub Actions runner";
+
+  wantedBy = [ "multi-user.target" ];
+  wants = [ "network-online.target" ];
+  after = [ "network.target" "network-online.target" ];
+
+  environment = {
+    HOME = workDir;
+    RUNNER_ROOT = stateDir;
+  } // cfg.extraEnvironment;
+
+  path = (with pkgs; [
+    bash
+    coreutils
+    git
+    gnutar
+    gzip
+  ]) ++ [
+    config.nix.package
+  ] ++ cfg.extraPackages;
+
+  serviceConfig = mkMerge [
+    {
+      ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service";
+
+      # Does the following, sequentially:
+      # - 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
+          # Wrapper script which expects the full path of the state, working and logs
+          # directory as arguments. Overrides the respective systemd variables to provide
+          # unambiguous directory names. This becomes relevant, for example, if the
+          # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
+          # to contain more than one directory. This causes systemd to set the respective
+          # environment variables with the path of all of the given directories, separated
+          # by a colon.
+          writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
+            set -euo pipefail
+
+            STATE_DIRECTORY="$1"
+            WORK_DIRECTORY="$2"
+            LOGS_DIRECTORY="$3"
+
+            ${lines}
+          '';
+          runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" "workDir" ] cfg;
+          newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
+          currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
+          newConfigTokenPath = "$STATE_DIRECTORY/.new-token";
+          currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}";
+
+          runnerCredFiles = [
+            ".credentials"
+            ".credentials_rsaparams"
+            ".runner"
+          ];
+          unconfigureRunner = writeScript "unconfigure" ''
+            copy_tokens() {
+              # Copy the configured token file to the state dir and allow the service user to read the file
+              install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}"
+              # Also copy current file to allow for a diff on the next start
+              install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}"
+            }
+            clean_state() {
+              find "$STATE_DIRECTORY/" -mindepth 1 -delete
+              copy_tokens
+            }
+            diff_config() {
+              changed=0
+              # Check for module config changes
+              [[ -f "${currentConfigPath}" ]] \
+                && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \
+                || changed=1
+              # Also check the content of the token file
+              [[ -f "${currentConfigTokenPath}" ]] \
+                && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \
+                || changed=1
+              # If the config has changed, remove old state and copy tokens
+              if [[ "$changed" -eq 1 ]]; then
+                echo "Config has changed, removing old runner state."
+                echo "The old runner will still appear in the GitHub Actions UI." \
+                     "You have to remove it manually."
+                clean_state
+              fi
+            }
+            if [[ "${optionalString cfg.ephemeral "1"}" ]]; then
+              # In ephemeral mode, we always want to start with a clean state
+              clean_state
+            elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then
+              # There are state files from a previous run; diff them to decide if we need a new registration
+              diff_config
+            else
+              # The state directory is entirely empty which indicates a first start
+              copy_tokens
+            fi
+            # Always clean workDir
+            find -H "$WORK_DIRECTORY" -mindepth 1 -delete
+          '';
+          configureRunner = writeScript "configure" ''
+            if [[ -e "${newConfigTokenPath}" ]]; then
+              echo "Configuring GitHub Actions Runner"
+              args=(
+                --unattended
+                --disableupdate
+                --work "$WORK_DIRECTORY"
+                --url ${escapeShellArg cfg.url}
+                --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)}
+                --name ${escapeShellArg cfg.name}
+                ${optionalString cfg.replace "--replace"}
+                ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
+                ${optionalString cfg.ephemeral "--ephemeral"}
+              )
+              # If the token file contains a PAT (i.e., it starts with "ghp_" or "github_pat_"), we have to use the --pat option,
+              # if it is not a PAT, we assume it contains a registration token and use the --token option
+              token=$(<"${newConfigTokenPath}")
+              if [[ "$token" =~ ^ghp_* ]] || [[ "$token" =~ ^github_pat_* ]]; then
+                args+=(--pat "$token")
+              else
+                args+=(--token "$token")
+              fi
+              ${cfg.package}/bin/Runner.Listener configure "''${args[@]}"
+              # Move the automatically created _diag dir to the logs dir
+              mkdir -p  "$STATE_DIRECTORY/_diag"
+              cp    -r  "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
+              rm    -rf "$STATE_DIRECTORY/_diag/"
+              # Cleanup token from config
+              rm "${newConfigTokenPath}"
+              # Symlink to new config
+              ln -s '${newConfigPath}' "${currentConfigPath}"
+            fi
+          '';
+          setupWorkDir = writeScript "setup-work-dirs" ''
+            # Link _diag dir
+            ln -s "$LOGS_DIRECTORY" "$WORK_DIRECTORY/_diag"
+
+            # Link the runner credentials to the work dir
+            ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$WORK_DIRECTORY/"
+          '';
+        in
+        map (x: "${x} ${escapeShellArgs [ stateDir workDir logsDir ]}") [
+          "+${unconfigureRunner}" # runs as root
+          configureRunner
+          setupWorkDir
+        ];
+
+      # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner)
+      # to trigger a fresh registration.
+      Restart = if cfg.ephemeral then "on-success" else "no";
+      # If the runner exits with `ReturnCode.RetryableError = 2`, always restart the service:
+      # https://github.com/actions/runner/blob/40ed7f8/src/Runner.Common/Constants.cs#L146
+      RestartForceExitStatus = [ 2 ];
+
+      # Contains _diag
+      LogsDirectory = [ systemdDir ];
+      # Default RUNNER_ROOT which contains ephemeral Runner data
+      RuntimeDirectory = [ systemdDir ];
+      # Home of persistent runner data, e.g., credentials
+      StateDirectory = [ systemdDir ];
+      StateDirectoryMode = "0700";
+      WorkingDirectory = workDir;
+
+      InaccessiblePaths = [
+        # Token file path given in the configuration, if visible to the service
+        "-${cfg.tokenFile}"
+        # Token file in the state directory
+        "${stateDir}/${currentConfigTokenFilename}"
+      ];
+
+      KillSignal = "SIGINT";
+
+      # Hardening (may overlap with DynamicUser=)
+      # The following options are only for optimizing:
+      # systemd-analyze security github-runner
+      AmbientCapabilities = mkBefore [ "" ];
+      CapabilityBoundingSet = mkBefore [ "" ];
+      # ProtectClock= adds DeviceAllow=char-rtc r
+      DeviceAllow = mkBefore [ "" ];
+      NoNewPrivileges = mkDefault true;
+      PrivateDevices = mkDefault true;
+      PrivateMounts = mkDefault true;
+      PrivateTmp = mkDefault true;
+      PrivateUsers = mkDefault true;
+      ProtectClock = mkDefault true;
+      ProtectControlGroups = mkDefault true;
+      ProtectHome = mkDefault true;
+      ProtectHostname = mkDefault true;
+      ProtectKernelLogs = mkDefault true;
+      ProtectKernelModules = mkDefault true;
+      ProtectKernelTunables = mkDefault true;
+      ProtectSystem = mkDefault "strict";
+      RemoveIPC = mkDefault true;
+      RestrictNamespaces = mkDefault true;
+      RestrictRealtime = mkDefault true;
+      RestrictSUIDSGID = mkDefault true;
+      UMask = mkDefault "0066";
+      ProtectProc = mkDefault "invisible";
+      SystemCallFilter = mkBefore [
+        "~@clock"
+        "~@cpu-emulation"
+        "~@module"
+        "~@mount"
+        "~@obsolete"
+        "~@raw-io"
+        "~@reboot"
+        "~capset"
+        "~setdomainname"
+        "~sethostname"
+      ];
+      RestrictAddressFamilies = mkBefore [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
+
+      BindPaths = lib.optionals (cfg.workDir != null) [ cfg.workDir ];
+
+      # Needs network access
+      PrivateNetwork = mkDefault false;
+      # Cannot be true due to Node
+      MemoryDenyWriteExecute = mkDefault false;
+
+      # The more restrictive "pid" option makes `nix` commands in CI emit
+      # "GC Warning: Couldn't read /proc/stat"
+      # You may want to set this to "pid" if not using `nix` commands
+      ProcSubset = mkDefault "all";
+      # Coverage programs for compiled code such as `cargo-tarpaulin` disable
+      # ASLR (address space layout randomization) which requires the
+      # `personality` syscall
+      # You may want to set this to `true` if not using coverage tooling on
+      # compiled code
+      LockPersonality = mkDefault false;
+
+      # Note that this has some interactions with the User setting; so you may
+      # want to consult the systemd docs if using both.
+      DynamicUser = mkDefault true;
+    }
+    (mkIf (cfg.user != null) { User = cfg.user; })
+    cfg.serviceOverrides
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/github-runners.nix b/nixpkgs/nixos/modules/services/continuous-integration/github-runners.nix
new file mode 100644
index 000000000000..66ace9580eca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/github-runners.nix
@@ -0,0 +1,58 @@
+{ config
+, pkgs
+, lib
+, ...
+}@args:
+
+with lib;
+
+let
+  cfg = config.services.github-runners;
+
+in
+
+{
+  options.services.github-runners = mkOption {
+    default = {};
+    type = with types; attrsOf (submodule { options = import ./github-runner/options.nix (args // {
+      # services.github-runners.${name}.name doesn't have a default; it falls back to ${name} below.
+      includeNameDefault = false;
+    }); });
+    example = {
+      runner1 = {
+        enable = true;
+        url = "https://github.com/owner/repo";
+        name = "runner1";
+        tokenFile = "/secrets/token1";
+      };
+
+      runner2 = {
+        enable = true;
+        url = "https://github.com/owner/repo";
+        name = "runner2";
+        tokenFile = "/secrets/token2";
+      };
+    };
+    description = lib.mdDoc ''
+      Multiple GitHub Runners.
+    '';
+  };
+
+  config = {
+    systemd.services = flip mapAttrs' cfg (n: v:
+      let
+        svcName = "github-runner-${n}";
+      in
+        nameValuePair svcName
+        (import ./github-runner/service.nix (args // {
+          inherit svcName;
+          cfg = v // {
+            name = if v.name != null then v.name else n;
+          };
+          systemdDir = "github-runner/${n}";
+        }))
+    );
+  };
+
+  meta.maintainers = with maintainers; [ veehaitch newam ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 9f076e2d7a23..53f39f40daa5 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -4,24 +4,41 @@ with lib;
 let
   cfg = config.services.gitlab-runner;
   hasDocker = config.virtualisation.docker.enable;
+
+  /* The whole logic of this module is to diff the hashes of the desired vs existing runners
+  The hash is recorded in the runner's name because we can't do better yet
+  See https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29350 for more details
+  */
+  genRunnerName = name: service: let
+      hash = substring 0 12 (hashString "md5" (unsafeDiscardStringContext (toJSON service)));
+    in if service ? description && service.description != null
+    then "${hash} ${service.description}"
+    else "${name}_${config.networking.hostName}_${hash}";
+
   hashedServices = mapAttrs'
-    (name: service: nameValuePair
-      "${name}_${config.networking.hostName}_${
-        substring 0 12
-        (hashString "md5" (unsafeDiscardStringContext (toJSON service)))}"
-      service)
-    cfg.services;
-  configPath = "$HOME/.gitlab-runner/config.toml";
-  configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" (
-    if (cfg.configFile != null) then ''
-      mkdir -p $(dirname ${configPath})
+    (name: service: nameValuePair (genRunnerName name service) service) cfg.services;
+  configPath = ''"$HOME"/.gitlab-runner/config.toml'';
+  configureScript = pkgs.writeShellApplication {
+    name = "gitlab-runner-configure";
+    runtimeInputs = with pkgs; [
+        bash
+        gawk
+        jq
+        moreutils
+        remarshal
+        util-linux
+        cfg.package
+        perl
+        python3
+    ];
+    text = if (cfg.configFile != null) then ''
       cp ${cfg.configFile} ${configPath}
       # make config file readable by service
-      chown -R --reference=$HOME $(dirname ${configPath})
+      chown -R --reference="$HOME" "$(dirname ${configPath})"
     '' else ''
       export CONFIG_FILE=${configPath}
 
-      mkdir -p $(dirname ${configPath})
+      mkdir -p "$(dirname ${configPath})"
       touch ${configPath}
 
       # update global options
@@ -34,22 +51,43 @@ let
       # remove no longer existing services
       gitlab-runner verify --delete
 
-      # current and desired state
-      NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n")
-      REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }')
+      ${toShellVar "NEEDED_SERVICES" (lib.mapAttrs (name: value: 1) hashedServices)}
+
+      declare -A REGISTERED_SERVICES
+
+      while IFS="," read -r name token;
+      do
+        REGISTERED_SERVICES["$name"]="$token"
+      done < <(gitlab-runner --log-format json list 2>&1 | grep Token  | jq -r '.msg +"," + .Token')
+
+      echo "NEEDED_SERVICES: " "''${!NEEDED_SERVICES[@]}"
+      echo "REGISTERED_SERVICES:" "''${!REGISTERED_SERVICES[@]}"
 
       # difference between current and desired state
-      NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true)
-      OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true)
+      declare -A NEW_SERVICES
+      for name in "''${!NEEDED_SERVICES[@]}"; do
+        if [ ! -v 'REGISTERED_SERVICES[$name]' ]; then
+          NEW_SERVICES[$name]=1
+        fi
+      done
+
+      declare -A OLD_SERVICES
+      # shellcheck disable=SC2034
+      for name in "''${!REGISTERED_SERVICES[@]}"; do
+        if [ ! -v 'NEEDED_SERVICES[$name]' ]; then
+          OLD_SERVICES[$name]=1
+        fi
+      done
 
       # register new services
       ${concatStringsSep "\n" (mapAttrsToList (name: service: ''
-        if echo "$NEW_SERVICES" | grep -xq "${name}"; then
+        # TODO so here we should mention NEW_SERVICES
+        if [ -v 'NEW_SERVICES["${name}"]' ] ; then
           bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
             "set -a && source ${service.registrationConfigFile} &&"
             "gitlab-runner register"
             "--non-interactive"
-            (if service.description != null then "--description \"${service.description}\"" else "--name '${name}'")
+            "--name '${name}'"
             "--executor ${service.executor}"
             "--limit ${toString service.limit}"
             "--request-concurrency ${toString service.requestConcurrency}"
@@ -92,24 +130,28 @@ let
         fi
       '') hashedServices)}
 
+      # check key is in array https://stackoverflow.com/questions/30353951/how-to-check-if-dictionary-contains-a-key-in-bash
+
+      echo "NEW_SERVICES: ''${NEW_SERVICES[*]}"
+      echo "OLD_SERVICES: ''${OLD_SERVICES[*]}"
       # unregister old services
-      for NAME in $(echo "$OLD_SERVICES")
+      for NAME in "''${!OLD_SERVICES[@]}"
       do
-        [ ! -z "$NAME" ] && gitlab-runner unregister \
+        [ -n "$NAME" ] && gitlab-runner unregister \
           --name "$NAME" && sleep 1
       done
 
       # make config file readable by service
-      chown -R --reference=$HOME $(dirname ${configPath})
-    '');
+      chown -R --reference="$HOME" "$(dirname ${configPath})"
+    '';
+  };
   startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
     export CONFIG_FILE=${configPath}
     exec gitlab-runner run --working-directory $HOME
   '';
-in
-{
+in {
   options.services.gitlab-runner = {
-    enable = mkEnableOption "Gitlab Runner";
+    enable = mkEnableOption (lib.mdDoc "Gitlab Runner");
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
@@ -141,7 +183,7 @@ in
       default = false;
       description = lib.mdDoc ''
         Finish all remaining jobs before stopping.
-        If not set gitlab-runner will stop immediatly without waiting
+        If not set gitlab-runner will stop immediately without waiting
         for jobs to finish, which will lead to failed builds.
       '';
     };
@@ -453,11 +495,48 @@ in
         };
       });
     };
+    clear-docker-cache = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to periodically prune gitlab runner's Docker resources. If
+          enabled, a systemd timer will run {command}`clear-docker-cache` as
+          specified by the `dates` option.
+        '';
+      };
+
+      flags = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "prune" ];
+        description = lib.mdDoc ''
+          Any additional flags passed to {command}`clear-docker-cache`.
+        '';
+      };
+
+      dates = mkOption {
+        default = "weekly";
+        type = types.str;
+        description = lib.mdDoc ''
+          Specification (in the format described by
+          {manpage}`systemd.time(7)`) of the time at
+          which the prune will occur.
+        '';
+      };
+
+      package = mkOption {
+        default = config.virtualisation.docker.package;
+        defaultText = literalExpression "config.virtualisation.docker.package";
+        example = literalExpression "pkgs.docker";
+        description = lib.mdDoc "Docker package to use for clearing up docker cache.";
+      };
+    };
   };
   config = mkIf cfg.enable {
-    warnings = (mapAttrsToList
+    warnings = mapAttrsToList
       (n: v: "services.gitlab-runner.services.${n}.`registrationConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this.")
-      (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services));
+      (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services);
 
     environment.systemPackages = [ cfg.package ];
     systemd.services.gitlab-runner = {
@@ -491,12 +570,28 @@ in
         ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
         ExecStart = "${startScript}/bin/gitlab-runner-start";
         ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
-      } // optionalAttrs (cfg.gracefulTermination) {
+      } // optionalAttrs cfg.gracefulTermination {
         TimeoutStopSec = "${cfg.gracefulTimeout}";
         KillSignal = "SIGQUIT";
         KillMode = "process";
       };
     };
+    # Enable periodic clear-docker-cache script
+    systemd.services.gitlab-runner-clear-docker-cache = mkIf (cfg.clear-docker-cache.enable && (any (s: s.executor == "docker") (attrValues cfg.services))) {
+      description = "Prune gitlab-runner docker resources";
+      restartIfChanged = false;
+      unitConfig.X-StopOnRemoval = false;
+
+      serviceConfig.Type = "oneshot";
+
+      path = [ cfg.clear-docker-cache.package pkgs.gawk ];
+
+      script = ''
+        ${pkgs.gitlab-runner}/bin/clear-docker-cache ${toString cfg.clear-docker-cache.flags}
+      '';
+
+      startAt = cfg.clear-docker-cache.dates;
+    };
     # Enable docker if `docker` executor is used in any service
     virtualisation.docker.enable = mkIf (
       any (s: s.executor == "docker") (attrValues cfg.services)
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 c9e22dff1527..c0d752443a16 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.gocd-agent = {
-      enable = mkEnableOption "gocd-agent";
+      enable = mkEnableOption (lib.mdDoc "gocd-agent");
 
       user = mkOption {
         default = "gocd-agent";
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 50b5a20ad7ed..bf7fd529bfca 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.gocd-server = {
-      enable = mkEnableOption "gocd-server";
+      enable = mkEnableOption (lib.mdDoc "gocd-server");
 
       user = mkOption {
         default = "gocd-server";
@@ -46,7 +46,7 @@ in {
 
       port = mkOption {
         default = 8153;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           Specifies port number on which the Go.CD server HTTP interface listens.
         '';
@@ -106,6 +106,8 @@ in {
           "-Dcruise.config.file=${cfg.workDir}/conf/cruise-config.xml"
           "-Dcruise.server.port=${toString cfg.port}"
           "-Dcruise.server.ssl.port=${toString cfg.sslPort}"
+          "--add-opens=java.base/java.lang=ALL-UNNAMED"
+          "--add-opens=java.base/java.util=ALL-UNNAMED"
         ];
         defaultText = literalExpression ''
           [
@@ -119,6 +121,8 @@ in {
             "-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}}"
+            "--add-opens=java.base/java.lang=ALL-UNNAMED"
+            "--add-opens=java.base/java.util=ALL-UNNAMED"
           ]
         '';
 
@@ -199,7 +203,7 @@ in {
         ${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
         ${pkgs.jre}/bin/java -server ${concatStringsSep " " cfg.startupOptions} \
                                ${concatStringsSep " " cfg.extraOptions}  \
-                              -jar ${pkgs.gocd-server}/go-server/go.jar
+                              -jar ${pkgs.gocd-server}/go-server/lib/go.jar
       '';
 
       serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hail.nix b/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
index 76d7356e2472..62e8b8077c07 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
@@ -15,8 +15,8 @@ in {
       default = false;
       description = lib.mdDoc ''
         Enables the Hail Auto Update Service. Hail can automatically deploy artifacts
-        built by a Hydra Continous Integration server. A common use case is to provide
-        continous deployment for single services or a full NixOS configuration.'';
+        built by a Hydra Continuous Integration server. A common use case is to provide
+        continuous deployment for single services or a full NixOS configuration.'';
     };
     profile = mkOption {
       type = types.str;
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 9e1fb0307576..ea9b5ffbf43c 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
@@ -10,172 +10,18 @@
 let
   inherit (lib)
     filterAttrs
-    literalDocBook
     literalExpression
     mkIf
     mkOption
     mkRemovedOptionModule
     mkRenamedOptionModule
     types
-    ;
-
-  cfg =
-    config.services.hercules-ci-agent;
-
-  format = pkgs.formats.toml { };
-
-  settingsModule = { config, ... }: {
-    freeformType = format.type;
-    options = {
-      apiBaseUrl = mkOption {
-        description = lib.mdDoc ''
-          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";
-        description = lib.mdDoc ''
-          State directory (secrets, work directory, etc) for agent
-        '';
-      };
-      concurrentTasks = mkOption {
-        description = lib.mdDoc ''
-          Number of tasks to perform simultaneously.
-
-          A task is a single derivation build, an evaluation or an effect run.
-          At minimum, you need 2 concurrent tasks for `x86_64-linux`
-          in your cluster, to allow for import from derivation.
-
-          `concurrentTasks` can be around the CPU core count or lower if memory is
-          the bottleneck.
-
-          The optimal value depends on the resource consumption characteristics of your workload,
-          including memory usage and in-task parallelism. This is typically determined empirically.
-
-          When scaling, it is generally better to have a double-size machine than two machines,
-          because each split of resources causes inefficiencies; particularly with regards
-          to build latency because of extra downloads.
-        '';
-        type = types.either types.ints.positive (types.enum [ "auto" ]);
-        default = "auto";
-      };
-      labels = mkOption {
-        description = lib.mdDoc ''
-          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 = lib.mdDoc ''
-          The directory in which temporary subdirectories are created for task state. This includes sources for Nix evaluation.
-        '';
-        type = types.path;
-        default = config.baseDirectory + "/work";
-        defaultText = literalExpression ''baseDirectory + "/work"'';
-      };
-      staticSecretsDirectory = mkOption {
-        description = lib.mdDoc ''
-          This is the default directory to look for statically configured secrets like `cluster-join-token.key`.
-
-          See also `clusterJoinTokenPath` and `binaryCachesPath` for fine-grained configuration.
-        '';
-        type = types.path;
-        default = config.baseDirectory + "/secrets";
-        defaultText = literalExpression ''baseDirectory + "/secrets"'';
-      };
-      clusterJoinTokenPath = mkOption {
-        description = ''
-          Location of the cluster-join-token.key file.
-
-          You can retrieve the contents of the file when creating a new agent via
-          <link xlink:href="https://hercules-ci.com/dashboard">https://hercules-ci.com/dashboard</link>.
 
-          As this value is confidential, it should not be in the store, but
-          installed using other means, such as agenix, NixOps
-          <literal>deployment.keys</literal>, or manual installation.
-
-          The contents of the file are used for authentication between the agent and the API.
-        '';
-        type = types.path;
-        default = config.staticSecretsDirectory + "/cluster-join-token.key";
-        defaultText = literalExpression ''staticSecretsDirectory + "/cluster-join-token.key"'';
-      };
-      binaryCachesPath = mkOption {
-        description = ''
-          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"'';
-      };
-      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"'';
-      };
-    };
-  };
+    ;
 
-  # TODO (roberth, >=2022) remove
-  checkNix =
-    if !cfg.checkNix
-    then ""
-    else if lib.versionAtLeast config.nix.package.version "2.3.10"
-    then ""
-    else
-      pkgs.stdenv.mkDerivation {
-        name = "hercules-ci-check-system-nix-src";
-        inherit (config.nix.package) src patches;
-        dontConfigure = true;
-        buildPhase = ''
-          echo "Checking in-memory pathInfoCache expiry"
-          if ! grep 'PathInfoCacheValue' src/libstore/store-api.hh >/dev/null; then
-            cat 1>&2 <<EOF
+  cfg = config.services.hercules-ci-agent;
 
-            You are deploying Hercules CI Agent on a system with an incompatible
-            nix-daemon. Please make sure nix.package is set to a Nix version of at
-            least 2.3.10 or a master version more recent than Mar 12, 2020.
-          EOF
-            exit 1
-          fi
-        '';
-        installPhase = "touch $out";
-      };
+  inherit (import ./settings.nix { inherit pkgs lib; }) format settingsModule;
 
 in
 {
@@ -199,15 +45,6 @@ in
         Support is available at [help@hercules-ci.com](mailto:help@hercules-ci.com).
       '';
     };
-    checkNix = mkOption {
-      type = types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Whether to make sure that the system's Nix (nix-daemon) is compatible.
-
-        If you set this to false, please keep up with the change log.
-      '';
-    };
     package = mkOption {
       description = lib.mdDoc ''
         Package containing the bin/hercules-ci-agent executable.
@@ -236,15 +73,35 @@ in
     tomlFile = mkOption {
       type = types.path;
       internal = true;
-      defaultText = literalDocBook "generated <literal>hercules-ci-agent.toml</literal>";
-      description = ''
+      defaultText = lib.literalMD "generated `hercules-ci-agent.toml`";
+      description = lib.mdDoc ''
         The fully assembled config file.
       '';
     };
   };
 
   config = mkIf cfg.enable {
-    nix.extraOptions = lib.addContextFrom checkNix ''
+    # Make sure that nix.extraOptions does not override trusted-users
+    assertions = [
+      {
+        assertion =
+          (cfg.settings.nixUserIsTrusted or false) ->
+          builtins.match ".*(^|\n)[ \t]*trusted-users[ \t]*=.*" config.nix.extraOptions == null;
+        message = ''
+          hercules-ci-agent: Please do not set `trusted-users` in `nix.extraOptions`.
+
+          The hercules-ci-agent module by default relies on `nix.settings.trusted-users`
+          to be effectful, but a line like `trusted-users = ...` in `nix.extraOptions`
+          will override the value set in `nix.settings.trusted-users`.
+
+          Instead of setting `trusted-users` in the `nix.extraOptions` string, you should
+          set an option with additive semantics, such as
+           - the NixOS option `nix.settings.trusted-users`, or
+           - the Nix option in the `extraOptions` string, `extra-trusted-users`
+        '';
+      }
+    ];
+    nix.extraOptions = ''
       # A store path that was missing at first may well have finished building,
       # even shortly after the previous lookup. This *also* applies to the daemon.
       narinfo-cache-negative-ttl = 0
@@ -252,14 +109,9 @@ in
     services.hercules-ci-agent = {
       tomlFile =
         format.generate "hercules-ci-agent.toml" cfg.settings;
-
-      settings.labels = {
-        agent.source =
-          if options.services.hercules-ci-agent.package.highestPrio == (lib.modules.mkOptionDefault { }).priority
-          then "nixpkgs"
-          else lib.mkOptionDefault "override";
-        pkgs.version = pkgs.lib.version;
-        lib.version = lib.version;
+      settings.config._module.args = {
+        packageOption = options.services.hercules-ci-agent.package;
+        inherit pkgs;
       };
     };
   };
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 ef1933e12284..ad26b5316dde 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
@@ -35,6 +35,15 @@ in
         ExecStartPre = testCommand;
         Restart = "on-failure";
         RestartSec = 120;
+
+        # If a worker goes OOM, don't kill the main process. It needs to
+        # report the failure and it's unlikely to be part of the problem.
+        OOMPolicy = "continue";
+
+        # Work around excessive stack use by libstdc++ regex
+        # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164
+        # A 256 MiB stack allows between 400 KiB and 1.5 MiB file to be matched by ".*".
+        LimitSTACK = 256 * 1024 * 1024;
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/settings.nix b/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/settings.nix
new file mode 100644
index 000000000000..8eb902313ee8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/settings.nix
@@ -0,0 +1,153 @@
+# Not a module
+{ pkgs, lib }:
+let
+  inherit (lib)
+    types
+    literalExpression
+    mkOption
+    ;
+
+  format = pkgs.formats.toml { };
+
+  settingsModule = { config, packageOption, pkgs, ... }: {
+    freeformType = format.type;
+    options = {
+      apiBaseUrl = mkOption {
+        description = lib.mdDoc ''
+          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";
+        description = lib.mdDoc ''
+          State directory (secrets, work directory, etc) for agent
+        '';
+      };
+      concurrentTasks = mkOption {
+        description = lib.mdDoc ''
+          Number of tasks to perform simultaneously.
+
+          A task is a single derivation build, an evaluation or an effect run.
+          At minimum, you need 2 concurrent tasks for `x86_64-linux`
+          in your cluster, to allow for import from derivation.
+
+          `concurrentTasks` can be around the CPU core count or lower if memory is
+          the bottleneck.
+
+          The optimal value depends on the resource consumption characteristics of your workload,
+          including memory usage and in-task parallelism. This is typically determined empirically.
+
+          When scaling, it is generally better to have a double-size machine than two machines,
+          because each split of resources causes inefficiencies; particularly with regards
+          to build latency because of extra downloads.
+        '';
+        type = types.either types.ints.positive (types.enum [ "auto" ]);
+        default = "auto";
+        defaultText = lib.literalMD ''
+          `"auto"`, meaning equal to the number of CPU cores.
+        '';
+      };
+      labels = mkOption {
+        description = lib.mdDoc ''
+          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 = lib.mdDoc ''
+          The directory in which temporary subdirectories are created for task state. This includes sources for Nix evaluation.
+        '';
+        type = types.path;
+        default = config.baseDirectory + "/work";
+        defaultText = literalExpression ''baseDirectory + "/work"'';
+      };
+      staticSecretsDirectory = mkOption {
+        description = lib.mdDoc ''
+          This is the default directory to look for statically configured secrets like `cluster-join-token.key`.
+
+          See also `clusterJoinTokenPath` and `binaryCachesPath` for fine-grained configuration.
+        '';
+        type = types.path;
+        default = config.baseDirectory + "/secrets";
+        defaultText = literalExpression ''baseDirectory + "/secrets"'';
+      };
+      clusterJoinTokenPath = mkOption {
+        description = lib.mdDoc ''
+          Location of the cluster-join-token.key file.
+
+          You can retrieve the contents of the file when creating a new agent via
+          <https://hercules-ci.com/dashboard>.
+
+          As this value is confidential, it should not be in the store, but
+          installed using other means, such as agenix, NixOps
+          `deployment.keys`, 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"'';
+      };
+      binaryCachesPath = mkOption {
+        description = lib.mdDoc ''
+          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
+          `deployment.keys`, or manual installation.
+
+          The format is described on <https://docs.hercules-ci.com/hercules-ci-agent/binary-caches-json/>.
+        '';
+        type = types.path;
+        default = config.staticSecretsDirectory + "/binary-caches.json";
+        defaultText = literalExpression ''staticSecretsDirectory + "/binary-caches.json"'';
+      };
+      secretsJsonPath = mkOption {
+        description = lib.mdDoc ''
+          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
+          `deployment.keys`, or manual installation.
+
+          The format is described on <https://docs.hercules-ci.com/hercules-ci-agent/secrets-json/>.
+        '';
+        type = types.path;
+        default = config.staticSecretsDirectory + "/secrets.json";
+        defaultText = literalExpression ''staticSecretsDirectory + "/secrets.json"'';
+      };
+    };
+    config = {
+      labels = {
+        agent.source =
+          if packageOption.highestPrio == (lib.modules.mkOptionDefault { }).priority
+          then "nixpkgs"
+          else lib.mkOptionDefault "override";
+        pkgs.version = pkgs.lib.version;
+        lib.version = lib.version;
+      };
+    };
+  };
+in
+{
+  inherit format settingsModule;
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
index 7159ec287fc8..83078706fcae 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -42,7 +42,7 @@ let
     makeWrapperArgs = concatStringsSep " " (mapAttrsToList (key: value: "--set \"${key}\" \"${value}\"") hydraEnv);
   in pkgs.buildEnv rec {
     name = "hydra-env";
-    buildInputs = [ pkgs.makeWrapper ];
+    nativeBuildInputs = [ pkgs.makeWrapper ];
     paths = [ cfg.package ];
 
     postBuild = ''
@@ -87,7 +87,7 @@ in
         type = types.str;
         default = localDB;
         example = "dbi:Pg:dbname=hydra;host=postgres.example.org;user=foo;";
-        description = ''
+        description = lib.mdDoc ''
           The DBI string for Hydra database connection.
 
           NOTE: Attempts to set `application_name` will be overridden by
@@ -115,14 +115,14 @@ in
         type = types.str;
         default = "*";
         example = "localhost";
-        description = ''
-          The hostname or address to listen on or <literal>*</literal> to listen
+        description = lib.mdDoc ''
+          The hostname or address to listen on or `*` to listen
           on all interfaces.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
         description = lib.mdDoc ''
           TCP port the web server should listen to.
@@ -398,7 +398,7 @@ in
     systemd.services.hydra-evaluator =
       { wantedBy = [ "multi-user.target" ];
         requires = [ "hydra-init.service" ];
-        after = [ "hydra-init.service" "network.target" ];
+        after = [ "hydra-init.service" "network.target" "network-online.target" ];
         path = with pkgs; [ hydra-package nettools jq ];
         restartTriggers = [ hydraConf ];
         environment = env // {
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
index 6cd5718f4227..a9a587b41e88 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -87,8 +87,8 @@ in {
       };
 
       packages = mkOption {
-        default = [ pkgs.stdenv pkgs.git pkgs.jdk11 config.programs.ssh.package pkgs.nix ];
-        defaultText = literalExpression "[ pkgs.stdenv pkgs.git pkgs.jdk11 config.programs.ssh.package pkgs.nix ]";
+        default = [ pkgs.stdenv pkgs.git pkgs.jdk17 config.programs.ssh.package pkgs.nix ];
+        defaultText = literalExpression "[ pkgs.stdenv pkgs.git pkgs.jdk17 config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
         description = lib.mdDoc ''
           Packages to add to PATH for the jenkins process.
@@ -228,7 +228,7 @@ in {
 
       # For reference: https://wiki.jenkins.io/display/JENKINS/JenkinsLinuxStartupScript
       script = ''
-        ${pkgs.jdk11}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
+        ${pkgs.jdk17}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
                                                   --httpPort=${toString cfg.port} \
                                                   --prefix=${cfg.prefix} \
                                                   -Djava.awt.headless=true \
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
index 8dc06bf26416..d6a8c2a3f7cc 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -30,7 +30,7 @@ in {
       };
 
       accessUser = mkOption {
-        default = "";
+        default = "admin";
         type = types.str;
         description = lib.mdDoc ''
           User id in Jenkins used to reload config.
@@ -48,7 +48,8 @@ in {
       };
 
       accessTokenFile = mkOption {
-        default = "";
+        default = "${config.services.jenkins.home}/secrets/initialAdminPassword";
+        defaultText = literalExpression ''"''${config.services.jenkins.home}/secrets/initialAdminPassword"'';
         type = types.str;
         example = "/run/keys/jenkins-job-builder-access-token";
         description = lib.mdDoc ''
@@ -241,7 +242,7 @@ in {
                 jobdir="${jenkinsCfg.home}/$jenkinsjobname"
                 rm -rf "$jobdir"
             done
-          '' + (if cfg.accessUser != "" then reloadScript else "");
+          '' + (optionalString (cfg.accessUser != "") reloadScript);
       serviceConfig = {
         Type = "oneshot";
         User = jenkinsCfg.user;
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/woodpecker/agents.nix b/nixpkgs/nixos/modules/services/continuous-integration/woodpecker/agents.nix
new file mode 100644
index 000000000000..cc5b903afd59
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/woodpecker/agents.nix
@@ -0,0 +1,144 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.woodpecker-agents;
+
+  agentModule = lib.types.submodule {
+    options = {
+      enable = lib.mkEnableOption (lib.mdDoc "this Woodpecker-Agent. Agents execute tasks generated by a Server, every install will need one server and at least one agent");
+
+      package = lib.mkPackageOptionMD pkgs "woodpecker-agent" { };
+
+      environment = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf lib.types.str;
+        example = lib.literalExpression ''
+          {
+            WOODPECKER_SERVER = "localhost:9000";
+            WOODPECKER_BACKEND = "docker";
+            DOCKER_HOST = "unix:///run/podman/podman.sock";
+          }
+        '';
+        description = lib.mdDoc "woodpecker-agent config environment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/agent-config)";
+      };
+
+      extraGroups = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
+        example = [ "podman" ];
+        description = lib.mdDoc ''
+          Additional groups for the systemd service.
+        '';
+      };
+
+      environmentFile = lib.mkOption {
+        type = lib.types.listOf lib.types.path;
+        default = [ ];
+        example = [ "/var/secrets/woodpecker-agent.env" ];
+        description = lib.mdDoc ''
+          File to load environment variables
+          from. This is helpful for specifying secrets.
+          Example content of environmentFile:
+          ```
+          WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
+          ```
+        '';
+      };
+    };
+  };
+
+  mkAgentService = name: agentCfg: {
+    name = "woodpecker-agent-${name}";
+    value = {
+      description = "Woodpecker-Agent Service - ${name}";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        SupplementaryGroups = agentCfg.extraGroups;
+        EnvironmentFile = agentCfg.environmentFile;
+        ExecStart = lib.getExe agentCfg.package;
+        Restart = "on-failure";
+        RestartSec = 15;
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        BindReadOnlyPaths = [
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/ssl/certs"
+          "-/etc/static/ssl/certs"
+          "-/etc/hosts"
+          "-/etc/localtime"
+        ];
+      };
+      inherit (agentCfg) environment;
+    };
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ janik ambroisie ];
+
+  options = {
+    services.woodpecker-agents = {
+      agents = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf agentModule;
+        example = {
+          docker = {
+            environment = {
+              WOODPECKER_SERVER = "localhost:9000";
+              WOODPECKER_BACKEND = "docker";
+              DOCKER_HOST = "unix:///run/podman/podman.sock";
+            };
+
+            extraGroups = [ "docker" ];
+
+            environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
+          };
+
+          exec = {
+            environment = {
+              WOODPECKER_SERVER = "localhost:9000";
+              WOODPECKER_BACKEND = "exec";
+            };
+
+            environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
+          };
+        };
+        description = lib.mdDoc "woodpecker-agents configurations";
+      };
+    };
+  };
+
+  config = {
+    systemd.services =
+      let
+        mkServices = lib.mapAttrs' mkAgentService;
+        enabledAgents = lib.filterAttrs (_: agent: agent.enable) cfg.agents;
+      in
+      mkServices enabledAgents;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixpkgs/nixos/modules/services/continuous-integration/woodpecker/server.nix
new file mode 100644
index 000000000000..cae5ed7cf116
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/woodpecker/server.nix
@@ -0,0 +1,98 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.woodpecker-server;
+in
+{
+  meta.maintainers = with lib.maintainers; [ janik ambroisie ];
+
+
+  options = {
+    services.woodpecker-server = {
+      enable = lib.mkEnableOption (lib.mdDoc "the Woodpecker-Server, a CI/CD application for automatic builds, deployments and tests");
+      package = lib.mkPackageOptionMD pkgs "woodpecker-server" { };
+      environment = lib.mkOption {
+        default = { };
+        type = lib.types.attrsOf lib.types.str;
+        example = lib.literalExpression
+          ''
+            {
+              WOODPECKER_HOST = "https://woodpecker.example.com";
+              WOODPECKER_OPEN = "true";
+              WOODPECKER_GITEA = "true";
+              WOODPECKER_GITEA_CLIENT = "ffffffff-ffff-ffff-ffff-ffffffffffff";
+              WOODPECKER_GITEA_URL = "https://git.example.com";
+            }
+          '';
+        description = lib.mdDoc "woodpecker-server config environment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/server-config)";
+      };
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/root/woodpecker-server.env";
+        description = lib.mdDoc ''
+          File to load environment variables
+          from. This is helpful for specifying secrets.
+          Example content of environmentFile:
+          ```
+          WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here
+          WOODPECKER_GITEA_SECRET=gto_**************************************
+          ```
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services = {
+      woodpecker-server = {
+        description = "Woodpecker-Server Service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network-online.target" ];
+        wants = [ "network-online.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          WorkingDirectory = "%S/woodpecker-server";
+          StateDirectory = "woodpecker-server";
+          StateDirectoryMode = "0700";
+          UMask = "0007";
+          ConfigurationDirectory = "woodpecker-server";
+          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
+          ExecStart = "${cfg.package}/bin/woodpecker-server";
+          Restart = "on-failure";
+          RestartSec = 15;
+          CapabilityBoundingSet = "";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "strict";
+          ProtectHome = true;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
+          SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+        };
+        inherit (cfg) environment;
+      };
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/databases/aerospike.nix b/nixpkgs/nixos/modules/services/databases/aerospike.nix
index 9ffedaebf667..21df4cd0577b 100644
--- a/nixpkgs/nixos/modules/services/databases/aerospike.nix
+++ b/nixpkgs/nixos/modules/services/databases/aerospike.nix
@@ -39,7 +39,7 @@ in
   options = {
 
     services.aerospike = {
-      enable = mkEnableOption "Aerospike server";
+      enable = mkEnableOption (lib.mdDoc "Aerospike server");
 
       package = mkOption {
         default = pkgs.aerospike;
diff --git a/nixpkgs/nixos/modules/services/databases/cassandra.nix b/nixpkgs/nixos/modules/services/databases/cassandra.nix
index 38db1d2e9f71..e26acb88d8c8 100644
--- a/nixpkgs/nixos/modules/services/databases/cassandra.nix
+++ b/nixpkgs/nixos/modules/services/databases/cassandra.nix
@@ -19,6 +19,10 @@ let
 
   cfg = config.services.cassandra;
 
+  atLeast3 = versionAtLeast cfg.package.version "3";
+  atLeast3_11 = versionAtLeast cfg.package.version "3.11";
+  atLeast4 = versionAtLeast cfg.package.version "4";
+
   defaultUser = "cassandra";
 
   cassandraConfig = flip recursiveUpdate cfg.extraConfig (
@@ -39,7 +43,7 @@ let
           parameters = [{ seeds = concatStringsSep "," cfg.seedAddresses; }];
         }
       ];
-    } // optionalAttrs (versionAtLeast cfg.package.version "3") {
+    } // optionalAttrs atLeast3 {
       hints_directory = "${cfg.homeDir}/hints";
     }
   );
@@ -62,7 +66,7 @@ let
     cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
 
     passAsFile = [ "extraEnvSh" ];
-    inherit (cfg) extraEnvSh;
+    inherit (cfg) extraEnvSh package;
 
     buildCommand = ''
       mkdir -p "$out"
@@ -80,6 +84,10 @@ let
 
       # Delete default password file
       sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh"
+
+      ${lib.optionalString atLeast4 ''
+        cp $package/conf/jvm*.options $out/
+      ''}
     '';
   };
 
@@ -95,15 +103,27 @@ let
       "-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}"
     ] ++ optionals cfg.remoteJmx [
       "-Djava.rmi.server.hostname=${cfg.rpcAddress}"
+    ] ++ optionals atLeast4 [
+      # Historically, we don't use a log dir, whereas the upstream scripts do
+      # expect this. We override those by providing our own -Xlog:gc flag.
+      "-Xlog:gc=warning,heap*=warning,age*=warning,safepoint=warning,promotion*=warning"
     ];
 
+  commonEnv = {
+    # Sufficient for cassandra 2.x, 3.x
+    CASSANDRA_CONF = "${cassandraEtc}";
+
+    # Required since cassandra 4
+    CASSANDRA_LOGBACK_CONF = "${cassandraEtc}/logback.xml";
+  };
+
 in
 {
   options.services.cassandra = {
 
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       Apache Cassandra – Scalable and highly available database.
-    '';
+    '');
 
     clusterName = mkOption {
       type = types.str;
@@ -435,7 +455,7 @@ in
     jmxRolesFile = mkOption {
       type = types.nullOr types.path;
       default =
-        if versionAtLeast cfg.package.version "3.11"
+        if atLeast3_11
         then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
         else null;
       defaultText = literalMD ''generated configuration file if version is at least 3.11, otherwise `null`'';
@@ -486,8 +506,7 @@ in
     systemd.services.cassandra = {
       description = "Apache Cassandra service";
       after = [ "network.target" ];
-      environment = {
-        CASSANDRA_CONF = "${cassandraEtc}";
+      environment = commonEnv // {
         JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions;
         MAX_HEAP_SIZE = toString cfg.maxHeapSize;
         HEAP_NEWSIZE = toString cfg.heapNewSize;
@@ -508,6 +527,7 @@ in
       description = "Perform a full repair on this Cassandra node";
       after = [ "cassandra.service" ];
       requires = [ "cassandra.service" ];
+      environment = commonEnv;
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
@@ -536,6 +556,7 @@ in
       description = "Perform an incremental repair on this cassandra node.";
       after = [ "cassandra.service" ];
       requires = [ "cassandra.service" ];
+      environment = commonEnv;
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
diff --git a/nixpkgs/nixos/modules/services/databases/clickhouse.nix b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
index 53637f4171c2..dca352ef72fe 100644
--- a/nixpkgs/nixos/modules/services/databases/clickhouse.nix
+++ b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
@@ -11,12 +11,12 @@ with lib;
 
     services.clickhouse = {
 
-      enable = mkEnableOption "ClickHouse database server";
+      enable = mkEnableOption (lib.mdDoc "ClickHouse database server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.clickhouse;
-        defaultText = "pkgs.clickhouse";
+        defaultText = lib.literalExpression "pkgs.clickhouse";
         description = lib.mdDoc ''
           ClickHouse package to use.
         '';
@@ -48,13 +48,20 @@ with lib;
       after = [ "network.target" ];
 
       serviceConfig = {
+        Type = "notify";
         User = "clickhouse";
         Group = "clickhouse";
         ConfigurationDirectory = "clickhouse-server";
         AmbientCapabilities = "CAP_SYS_NICE";
         StateDirectory = "clickhouse";
         LogsDirectory = "clickhouse";
-        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=${cfg.package}/etc/clickhouse-server/config.xml";
+        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=/etc/clickhouse-server/config.xml";
+        TimeoutStartSec = "infinity";
+      };
+
+      environment = {
+        # Switching off watchdog is very important for sd_notify to work correctly.
+        CLICKHOUSE_WATCHDOG_ENABLE = "0";
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/databases/cockroachdb.nix b/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
index b8d7321d00f5..ff77d30588fe 100644
--- a/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
@@ -49,7 +49,7 @@ in
 {
   options = {
     services.cockroachdb = {
-      enable = mkEnableOption "CockroachDB Server";
+      enable = mkEnableOption (lib.mdDoc "CockroachDB Server");
 
       listen = addressOption "intra-cluster communication" 26257;
 
@@ -58,7 +58,7 @@ in
       locality = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           An ordered, comma-separated list of key-value pairs that describe the
           topography of the machine. Topography might include country,
           datacenter or rack designations. Data is automatically replicated to
@@ -68,12 +68,12 @@ in
           like datacenter.  The tiers and order must be the same on all nodes.
           Including more tiers is better than including fewer. For example:
 
-          <literal>
+          ```
               country=us,region=us-west,datacenter=us-west-1b,rack=12
               country=ca,region=ca-east,datacenter=ca-east-2,rack=4
 
               planet=earth,province=manitoba,colo=secondary,power=3
-          </literal>
+          ```
         '';
       };
 
@@ -164,7 +164,7 @@ in
         example = [ "--advertise-addr" "[fe80::f6f2:::]" ];
         description = lib.mdDoc ''
           Extra CLI arguments passed to {command}`cockroach start`.
-          For the full list of supported argumemnts, check <https://www.cockroachlabs.com/docs/stable/cockroach-start.html#flags>
+          For the full list of supported arguments, check <https://www.cockroachlabs.com/docs/stable/cockroach-start.html#flags>
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/databases/couchdb.nix b/nixpkgs/nixos/modules/services/databases/couchdb.nix
index 2a570d09a2c4..0a81a8dceeee 100644
--- a/nixpkgs/nixos/modules/services/databases/couchdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/couchdb.nix
@@ -34,13 +34,7 @@ in {
 
     services.couchdb = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to run CouchDB Server.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "CouchDB Server");
 
       package = mkOption {
         type = types.package;
@@ -128,7 +122,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 5984;
         description = lib.mdDoc ''
           Defined the port number to listen.
@@ -147,7 +141,7 @@ in {
         type = types.lines;
         default = "";
         description = lib.mdDoc ''
-          Extra configuration. Overrides any other cofiguration.
+          Extra configuration. Overrides any other configuration.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/databases/dgraph.nix b/nixpkgs/nixos/modules/services/databases/dgraph.nix
index a6178b3d1ccf..7f005a9971a6 100644
--- a/nixpkgs/nixos/modules/services/databases/dgraph.nix
+++ b/nixpkgs/nixos/modules/services/databases/dgraph.nix
@@ -12,7 +12,7 @@ let
   ''
     mkdir -p $out/bin
     makeWrapper ${cfg.package}/bin/dgraph $out/bin/dgraph \
-      --set PATH '${lib.makeBinPath [ pkgs.nodejs ]}:$PATH' \
+      --prefix PATH : "${lib.makeBinPath [ pkgs.nodejs ]}" \
   '';
   securityOptions = {
       NoNewPrivileges = true;
@@ -53,9 +53,9 @@ in
 {
   options = {
     services.dgraph = {
-      enable = mkEnableOption "Dgraph native GraphQL database with a graph backend";
+      enable = mkEnableOption (lib.mdDoc "Dgraph native GraphQL database with a graph backend");
 
-      package = lib.mkPackageOption pkgs "dgraph" { };
+      package = lib.mkPackageOptionMD pkgs "dgraph" { };
 
       settings = mkOption {
         type = settingsFormat.type;
diff --git a/nixpkgs/nixos/modules/services/databases/dragonflydb.nix b/nixpkgs/nixos/modules/services/databases/dragonflydb.nix
index e35de2019afe..46a0c188c3ae 100644
--- a/nixpkgs/nixos/modules/services/databases/dragonflydb.nix
+++ b/nixpkgs/nixos/modules/services/databases/dragonflydb.nix
@@ -25,7 +25,7 @@ in
 
   options = {
     services.dragonflydb = {
-      enable = mkEnableOption "DragonflyDB";
+      enable = mkEnableOption (lib.mdDoc "DragonflyDB");
 
       user = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/databases/firebird.nix b/nixpkgs/nixos/modules/services/databases/firebird.nix
index 4aaf4ca12aa6..26ed46f0e60c 100644
--- a/nixpkgs/nixos/modules/services/databases/firebird.nix
+++ b/nixpkgs/nixos/modules/services/databases/firebird.nix
@@ -1,6 +1,6 @@
 { config, lib, pkgs, ... }:
 
-# TODO: This may file may need additional review, eg which configuartions to
+# TODO: This may file may need additional review, eg which configurations to
 # expose to the user.
 #
 # I only used it to access some simple databases.
@@ -14,7 +14,7 @@
 #
 # Be careful, virtuoso-opensource also provides a different isql command !
 
-# There are at least two ways to run firebird. superserver has been choosen
+# There are at least two ways to run firebird. superserver has been chosen
 # however there are no strong reasons to prefer this or the other one AFAIK
 # Eg superserver is said to be most efficiently using resources according to
 # http://www.firebirdsql.org/manual/qsg25-classic-or-super.html
@@ -40,7 +40,7 @@ in
 
     services.firebird = {
 
-      enable = mkEnableOption "the Firebird super server";
+      enable = mkEnableOption (lib.mdDoc "the Firebird super server");
 
       package = mkOption {
         default = pkgs.firebird;
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.md b/nixpkgs/nixos/modules/services/databases/foundationdb.md
new file mode 100644
index 000000000000..0815c139152f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/foundationdb.md
@@ -0,0 +1,309 @@
+# FoundationDB {#module-services-foundationdb}
+
+*Source:* {file}`modules/services/databases/foundationdb.nix`
+
+*Upstream documentation:* <https://apple.github.io/foundationdb/>
+
+*Maintainer:* Austin Seipp
+
+*Available version(s):* 7.1.x
+
+FoundationDB (or "FDB") is an open source, distributed, transactional
+key-value store.
+
+## Configuring and basic setup {#module-services-foundationdb-configuring}
+
+To enable FoundationDB, add the following to your
+{file}`configuration.nix`:
+```
+services.foundationdb.enable = true;
+services.foundationdb.package = pkgs.foundationdb71; # FoundationDB 7.1.x
+```
+
+The {option}`services.foundationdb.package` option is required, and
+must always be specified. Due to the fact FoundationDB network protocols and
+on-disk storage formats may change between (major) versions, and upgrades
+must be explicitly handled by the user, you must always manually specify
+this yourself so that the NixOS module will use the proper version. Note
+that minor, bugfix releases are always compatible.
+
+After running {command}`nixos-rebuild`, you can verify whether
+FoundationDB is running by executing {command}`fdbcli` (which is
+added to {option}`environment.systemPackages`):
+```ShellSession
+$ sudo -u foundationdb fdbcli
+Using cluster file `/etc/foundationdb/fdb.cluster'.
+
+The database is available.
+
+Welcome to the fdbcli. For help, type `help'.
+fdb> status
+
+Using cluster file `/etc/foundationdb/fdb.cluster'.
+
+Configuration:
+  Redundancy mode        - single
+  Storage engine         - memory
+  Coordinators           - 1
+
+Cluster:
+  FoundationDB processes - 1
+  Machines               - 1
+  Memory availability    - 5.4 GB per process on machine with least available
+  Fault Tolerance        - 0 machines
+  Server time            - 04/20/18 15:21:14
+
+...
+
+fdb>
+```
+
+You can also write programs using the available client libraries. For
+example, the following Python program can be run in order to grab the
+cluster status, as a quick example. (This example uses
+{command}`nix-shell` shebang support to automatically supply the
+necessary Python modules).
+```ShellSession
+a@link> cat fdb-status.py
+#! /usr/bin/env nix-shell
+#! nix-shell -i python -p python pythonPackages.foundationdb71
+
+import fdb
+import json
+
+def main():
+    fdb.api_version(520)
+    db = fdb.open()
+
+    @fdb.transactional
+    def get_status(tr):
+        return str(tr['\xff\xff/status/json'])
+
+    obj = json.loads(get_status(db))
+    print('FoundationDB available: %s' % obj['client']['database_status']['available'])
+
+if __name__ == "__main__":
+    main()
+a@link> chmod +x fdb-status.py
+a@link> ./fdb-status.py
+FoundationDB available: True
+a@link>
+```
+
+FoundationDB is run under the {command}`foundationdb` user and group
+by default, but this may be changed in the NixOS configuration. The systemd
+unit {command}`foundationdb.service` controls the
+{command}`fdbmonitor` process.
+
+By default, the NixOS module for FoundationDB creates a single SSD-storage
+based database for development and basic usage. This storage engine is
+designed for SSDs and will perform poorly on HDDs; however it can handle far
+more data than the alternative "memory" engine and is a better default
+choice for most deployments. (Note that you can change the storage backend
+on-the-fly for a given FoundationDB cluster using
+{command}`fdbcli`.)
+
+Furthermore, only 1 server process and 1 backup agent are started in the
+default configuration. See below for more on scaling to increase this.
+
+FoundationDB stores all data for all server processes under
+{file}`/var/lib/foundationdb`. You can override this using
+{option}`services.foundationdb.dataDir`, e.g.
+```
+services.foundationdb.dataDir = "/data/fdb";
+```
+
+Similarly, logs are stored under {file}`/var/log/foundationdb`
+by default, and there is a corresponding
+{option}`services.foundationdb.logDir` as well.
+
+## Scaling processes and backup agents {#module-services-foundationdb-scaling}
+
+Scaling the number of server processes is quite easy; simply specify
+{option}`services.foundationdb.serverProcesses` to be the number of
+FoundationDB worker processes that should be started on the machine.
+
+FoundationDB worker processes typically require 4GB of RAM per-process at
+minimum for good performance, so this option is set to 1 by default since
+the maximum amount of RAM is unknown. You're advised to abide by this
+restriction, so pick a number of processes so that each has 4GB or more.
+
+A similar option exists in order to scale backup agent processes,
+{option}`services.foundationdb.backupProcesses`. Backup agents are
+not as performance/RAM sensitive, so feel free to experiment with the number
+of available backup processes.
+
+## Clustering {#module-services-foundationdb-clustering}
+
+FoundationDB on NixOS works similarly to other Linux systems, so this
+section will be brief. Please refer to the full FoundationDB documentation
+for more on clustering.
+
+FoundationDB organizes clusters using a set of
+*coordinators*, which are just specially-designated
+worker processes. By default, every installation of FoundationDB on NixOS
+will start as its own individual cluster, with a single coordinator: the
+first worker process on {command}`localhost`.
+
+Coordinators are specified globally using the
+{command}`/etc/foundationdb/fdb.cluster` file, which all servers and
+client applications will use to find and join coordinators. Note that this
+file *can not* be managed by NixOS so easily:
+FoundationDB is designed so that it will rewrite the file at runtime for all
+clients and nodes when cluster coordinators change, with clients
+transparently handling this without intervention. It is fundamentally a
+mutable file, and you should not try to manage it in any way in NixOS.
+
+When dealing with a cluster, there are two main things you want to do:
+
+  - Add a node to the cluster for storage/compute.
+  - Promote an ordinary worker to a coordinator.
+
+A node must already be a member of the cluster in order to properly be
+promoted to a coordinator, so you must always add it first if you wish to
+promote it.
+
+To add a machine to a FoundationDB cluster:
+
+  - Choose one of the servers to start as the initial coordinator.
+  - Copy the {command}`/etc/foundationdb/fdb.cluster` file from this
+    server to all the other servers. Restart FoundationDB on all of these
+    other servers, so they join the cluster.
+  - All of these servers are now connected and working together in the
+    cluster, under the chosen coordinator.
+
+At this point, you can add as many nodes as you want by just repeating the
+above steps. By default there will still be a single coordinator: you can
+use {command}`fdbcli` to change this and add new coordinators.
+
+As a convenience, FoundationDB can automatically assign coordinators based
+on the redundancy mode you wish to achieve for the cluster. Once all the
+nodes have been joined, simply set the replication policy, and then issue
+the {command}`coordinators auto` command
+
+For example, assuming we have 3 nodes available, we can enable double
+redundancy mode, then auto-select coordinators. For double redundancy, 3
+coordinators is ideal: therefore FoundationDB will make
+*every* node a coordinator automatically:
+
+```ShellSession
+fdbcli> configure double ssd
+fdbcli> coordinators auto
+```
+
+This will transparently update all the servers within seconds, and
+appropriately rewrite the {command}`fdb.cluster` file, as well as
+informing all client processes to do the same.
+
+## Client connectivity {#module-services-foundationdb-connectivity}
+
+By default, all clients must use the current {command}`fdb.cluster`
+file to access a given FoundationDB cluster. This file is located by default
+in {command}`/etc/foundationdb/fdb.cluster` on all machines with the
+FoundationDB service enabled, so you may copy the active one from your
+cluster to a new node in order to connect, if it is not part of the cluster.
+
+## Client authorization and TLS {#module-services-foundationdb-authorization}
+
+By default, any user who can connect to a FoundationDB process with the
+correct cluster configuration can access anything. FoundationDB uses a
+pluggable design to transport security, and out of the box it supports a
+LibreSSL-based plugin for TLS support. This plugin not only does in-flight
+encryption, but also performs client authorization based on the given
+endpoint's certificate chain. For example, a FoundationDB server may be
+configured to only accept client connections over TLS, where the client TLS
+certificate is from organization *Acme Co* in the
+*Research and Development* unit.
+
+Configuring TLS with FoundationDB is done using the
+{option}`services.foundationdb.tls` options in order to control the
+peer verification string, as well as the certificate and its private key.
+
+Note that the certificate and its private key must be accessible to the
+FoundationDB user account that the server runs under. These files are also
+NOT managed by NixOS, as putting them into the store may reveal private
+information.
+
+After you have a key and certificate file in place, it is not enough to
+simply set the NixOS module options -- you must also configure the
+{command}`fdb.cluster` file to specify that a given set of
+coordinators use TLS. This is as simple as adding the suffix
+{command}`:tls` to your cluster coordinator configuration, after the
+port number. For example, assuming you have a coordinator on localhost with
+the default configuration, simply specifying:
+
+```
+XXXXXX:XXXXXX@127.0.0.1:4500:tls
+```
+
+will configure all clients and server processes to use TLS from now on.
+
+## Backups and Disaster Recovery {#module-services-foundationdb-disaster-recovery}
+
+The usual rules for doing FoundationDB backups apply on NixOS as written in
+the FoundationDB manual. However, one important difference is the security
+profile for NixOS: by default, the {command}`foundationdb` systemd
+unit uses *Linux namespaces* to restrict write access to
+the system, except for the log directory, data directory, and the
+{command}`/etc/foundationdb/` directory. This is enforced by default
+and cannot be disabled.
+
+However, a side effect of this is that the {command}`fdbbackup`
+command doesn't work properly for local filesystem backups: FoundationDB
+uses a server process alongside the database processes to perform backups
+and copy the backups to the filesystem. As a result, this process is put
+under the restricted namespaces above: the backup process can only write to
+a limited number of paths.
+
+In order to allow flexible backup locations on local disks, the FoundationDB
+NixOS module supports a
+{option}`services.foundationdb.extraReadWritePaths` option. This
+option takes a list of paths, and adds them to the systemd unit, allowing
+the processes inside the service to write (and read) the specified
+directories.
+
+For example, to create backups in {command}`/opt/fdb-backups`, first
+set up the paths in the module options:
+
+```
+services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
+```
+
+Restart the FoundationDB service, and it will now be able to write to this
+directory (even if it does not yet exist.) Note: this path
+*must* exist before restarting the unit. Otherwise,
+systemd will not include it in the private FoundationDB namespace (and it
+will not add it dynamically at runtime).
+
+You can now perform a backup:
+
+```ShellSession
+$ sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
+$ sudo -u foundationdb fdbbackup status -t default
+```
+
+## Known limitations {#module-services-foundationdb-limitations}
+
+The FoundationDB setup for NixOS should currently be considered beta.
+FoundationDB is not new software, but the NixOS compilation and integration
+has only undergone fairly basic testing of all the available functionality.
+
+  - There is no way to specify individual parameters for individual
+    {command}`fdbserver` processes. Currently, all server processes
+    inherit all the global {command}`fdbmonitor` settings.
+  - Ruby bindings are not currently installed.
+  - Go bindings are not currently installed.
+
+## Options {#module-services-foundationdb-options}
+
+NixOS's FoundationDB module allows you to configure all of the most relevant
+configuration options for {command}`fdbmonitor`, matching it quite
+closely. A complete list of options for the FoundationDB module may be found
+[here](#opt-services.foundationdb.enable). You should
+also read the FoundationDB documentation as well.
+
+## Full documentation {#module-services-foundationdb-full-docs}
+
+FoundationDB is a complex piece of software, and requires careful
+administration to properly use. Full documentation for administration can be
+found here: <https://apple.github.io/foundationdb/>.
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.nix b/nixpkgs/nixos/modules/services/databases/foundationdb.nix
index f71228708e42..48e9898a68c2 100644
--- a/nixpkgs/nixos/modules/services/databases/foundationdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/foundationdb.nix
@@ -62,7 +62,7 @@ in
 {
   options.services.foundationdb = {
 
-    enable = mkEnableOption "FoundationDB Server";
+    enable = mkEnableOption (lib.mdDoc "FoundationDB Server");
 
     package = mkOption {
       type        = types.package;
@@ -97,9 +97,9 @@ in
     openFirewall = mkOption {
       type        = types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Open the firewall ports corresponding to FoundationDB processes and coordinators
-        using <option>config.networking.firewall.*</option>.
+        using {option}`config.networking.firewall.*`.
       '';
     };
 
@@ -424,6 +424,6 @@ in
     };
   };
 
-  meta.doc         = ./foundationdb.xml;
+  meta.doc         = ./foundationdb.md;
   meta.maintainers = with lib.maintainers; [ thoughtpolice ];
 }
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.xml b/nixpkgs/nixos/modules/services/databases/foundationdb.xml
deleted file mode 100644
index b0b1ebeab45f..000000000000
--- a/nixpkgs/nixos/modules/services/databases/foundationdb.xml
+++ /dev/null
@@ -1,443 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-foundationdb">
- <title>FoundationDB</title>
- <para>
-  <emphasis>Source:</emphasis>
-  <filename>modules/services/databases/foundationdb.nix</filename>
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis>
-  <link xlink:href="https://apple.github.io/foundationdb/"/>
- </para>
- <para>
-  <emphasis>Maintainer:</emphasis> Austin Seipp
- </para>
- <para>
-  <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x
- </para>
- <para>
-  FoundationDB (or "FDB") is an open source, distributed, transactional
-  key-value store.
- </para>
- <section xml:id="module-services-foundationdb-configuring">
-  <title>Configuring and basic setup</title>
-
-  <para>
-   To enable FoundationDB, add the following to your
-   <filename>configuration.nix</filename>:
-<programlisting>
-services.foundationdb.enable = true;
-services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
-</programlisting>
-  </para>
-
-  <para>
-   The <option>services.foundationdb.package</option> option is required, and
-   must always be specified. Due to the fact FoundationDB network protocols and
-   on-disk storage formats may change between (major) versions, and upgrades
-   must be explicitly handled by the user, you must always manually specify
-   this yourself so that the NixOS module will use the proper version. Note
-   that minor, bugfix releases are always compatible.
-  </para>
-
-  <para>
-   After running <command>nixos-rebuild</command>, you can verify whether
-   FoundationDB is running by executing <command>fdbcli</command> (which is
-   added to <option>environment.systemPackages</option>):
-<screen>
-<prompt>$ </prompt>sudo -u foundationdb fdbcli
-Using cluster file `/etc/foundationdb/fdb.cluster'.
-
-The database is available.
-
-Welcome to the fdbcli. For help, type `help'.
-<prompt>fdb> </prompt>status
-
-Using cluster file `/etc/foundationdb/fdb.cluster'.
-
-Configuration:
-  Redundancy mode        - single
-  Storage engine         - memory
-  Coordinators           - 1
-
-Cluster:
-  FoundationDB processes - 1
-  Machines               - 1
-  Memory availability    - 5.4 GB per process on machine with least available
-  Fault Tolerance        - 0 machines
-  Server time            - 04/20/18 15:21:14
-
-...
-
-<prompt>fdb></prompt>
-</screen>
-  </para>
-
-  <para>
-   You can also write programs using the available client libraries. For
-   example, the following Python program can be run in order to grab the
-   cluster status, as a quick example. (This example uses
-   <command>nix-shell</command> shebang support to automatically supply the
-   necessary Python modules).
-<screen>
-<prompt>a@link> </prompt>cat fdb-status.py
-#! /usr/bin/env nix-shell
-#! nix-shell -i python -p python pythonPackages.foundationdb52
-
-import fdb
-import json
-
-def main():
-    fdb.api_version(520)
-    db = fdb.open()
-
-    @fdb.transactional
-    def get_status(tr):
-        return str(tr['\xff\xff/status/json'])
-
-    obj = json.loads(get_status(db))
-    print('FoundationDB available: %s' % obj['client']['database_status']['available'])
-
-if __name__ == "__main__":
-    main()
-<prompt>a@link> </prompt>chmod +x fdb-status.py
-<prompt>a@link> </prompt>./fdb-status.py
-FoundationDB available: True
-<prompt>a@link></prompt>
-</screen>
-  </para>
-
-  <para>
-   FoundationDB is run under the <command>foundationdb</command> user and group
-   by default, but this may be changed in the NixOS configuration. The systemd
-   unit <command>foundationdb.service</command> controls the
-   <command>fdbmonitor</command> process.
-  </para>
-
-  <para>
-   By default, the NixOS module for FoundationDB creates a single SSD-storage
-   based database for development and basic usage. This storage engine is
-   designed for SSDs and will perform poorly on HDDs; however it can handle far
-   more data than the alternative "memory" engine and is a better default
-   choice for most deployments. (Note that you can change the storage backend
-   on-the-fly for a given FoundationDB cluster using
-   <command>fdbcli</command>.)
-  </para>
-
-  <para>
-   Furthermore, only 1 server process and 1 backup agent are started in the
-   default configuration. See below for more on scaling to increase this.
-  </para>
-
-  <para>
-   FoundationDB stores all data for all server processes under
-   <filename>/var/lib/foundationdb</filename>. You can override this using
-   <option>services.foundationdb.dataDir</option>, e.g.
-<programlisting>
-services.foundationdb.dataDir = "/data/fdb";
-</programlisting>
-  </para>
-
-  <para>
-   Similarly, logs are stored under <filename>/var/log/foundationdb</filename>
-   by default, and there is a corresponding
-   <option>services.foundationdb.logDir</option> as well.
-  </para>
- </section>
- <section xml:id="module-services-foundationdb-scaling">
-  <title>Scaling processes and backup agents</title>
-
-  <para>
-   Scaling the number of server processes is quite easy; simply specify
-   <option>services.foundationdb.serverProcesses</option> to be the number of
-   FoundationDB worker processes that should be started on the machine.
-  </para>
-
-  <para>
-   FoundationDB worker processes typically require 4GB of RAM per-process at
-   minimum for good performance, so this option is set to 1 by default since
-   the maximum amount of RAM is unknown. You're advised to abide by this
-   restriction, so pick a number of processes so that each has 4GB or more.
-  </para>
-
-  <para>
-   A similar option exists in order to scale backup agent processes,
-   <option>services.foundationdb.backupProcesses</option>. Backup agents are
-   not as performance/RAM sensitive, so feel free to experiment with the number
-   of available backup processes.
-  </para>
- </section>
- <section xml:id="module-services-foundationdb-clustering">
-  <title>Clustering</title>
-
-  <para>
-   FoundationDB on NixOS works similarly to other Linux systems, so this
-   section will be brief. Please refer to the full FoundationDB documentation
-   for more on clustering.
-  </para>
-
-  <para>
-   FoundationDB organizes clusters using a set of
-   <emphasis>coordinators</emphasis>, which are just specially-designated
-   worker processes. By default, every installation of FoundationDB on NixOS
-   will start as its own individual cluster, with a single coordinator: the
-   first worker process on <command>localhost</command>.
-  </para>
-
-  <para>
-   Coordinators are specified globally using the
-   <command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
-   client applications will use to find and join coordinators. Note that this
-   file <emphasis>can not</emphasis> be managed by NixOS so easily:
-   FoundationDB is designed so that it will rewrite the file at runtime for all
-   clients and nodes when cluster coordinators change, with clients
-   transparently handling this without intervention. It is fundamentally a
-   mutable file, and you should not try to manage it in any way in NixOS.
-  </para>
-
-  <para>
-   When dealing with a cluster, there are two main things you want to do:
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     Add a node to the cluster for storage/compute.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Promote an ordinary worker to a coordinator.
-    </para>
-   </listitem>
-  </itemizedlist>
-
-  <para>
-   A node must already be a member of the cluster in order to properly be
-   promoted to a coordinator, so you must always add it first if you wish to
-   promote it.
-  </para>
-
-  <para>
-   To add a machine to a FoundationDB cluster:
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     Choose one of the servers to start as the initial coordinator.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Copy the <command>/etc/foundationdb/fdb.cluster</command> file from this
-     server to all the other servers. Restart FoundationDB on all of these
-     other servers, so they join the cluster.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     All of these servers are now connected and working together in the
-     cluster, under the chosen coordinator.
-    </para>
-   </listitem>
-  </itemizedlist>
-
-  <para>
-   At this point, you can add as many nodes as you want by just repeating the
-   above steps. By default there will still be a single coordinator: you can
-   use <command>fdbcli</command> to change this and add new coordinators.
-  </para>
-
-  <para>
-   As a convenience, FoundationDB can automatically assign coordinators based
-   on the redundancy mode you wish to achieve for the cluster. Once all the
-   nodes have been joined, simply set the replication policy, and then issue
-   the <command>coordinators auto</command> command
-  </para>
-
-  <para>
-   For example, assuming we have 3 nodes available, we can enable double
-   redundancy mode, then auto-select coordinators. For double redundancy, 3
-   coordinators is ideal: therefore FoundationDB will make
-   <emphasis>every</emphasis> node a coordinator automatically:
-  </para>
-
-<screen>
-<prompt>fdbcli> </prompt>configure double ssd
-<prompt>fdbcli> </prompt>coordinators auto
-</screen>
-
-  <para>
-   This will transparently update all the servers within seconds, and
-   appropriately rewrite the <command>fdb.cluster</command> file, as well as
-   informing all client processes to do the same.
-  </para>
- </section>
- <section xml:id="module-services-foundationdb-connectivity">
-  <title>Client connectivity</title>
-
-  <para>
-   By default, all clients must use the current <command>fdb.cluster</command>
-   file to access a given FoundationDB cluster. This file is located by default
-   in <command>/etc/foundationdb/fdb.cluster</command> on all machines with the
-   FoundationDB service enabled, so you may copy the active one from your
-   cluster to a new node in order to connect, if it is not part of the cluster.
-  </para>
- </section>
- <section xml:id="module-services-foundationdb-authorization">
-  <title>Client authorization and TLS</title>
-
-  <para>
-   By default, any user who can connect to a FoundationDB process with the
-   correct cluster configuration can access anything. FoundationDB uses a
-   pluggable design to transport security, and out of the box it supports a
-   LibreSSL-based plugin for TLS support. This plugin not only does in-flight
-   encryption, but also performs client authorization based on the given
-   endpoint's certificate chain. For example, a FoundationDB server may be
-   configured to only accept client connections over TLS, where the client TLS
-   certificate is from organization <emphasis>Acme Co</emphasis> in the
-   <emphasis>Research and Development</emphasis> unit.
-  </para>
-
-  <para>
-   Configuring TLS with FoundationDB is done using the
-   <option>services.foundationdb.tls</option> options in order to control the
-   peer verification string, as well as the certificate and its private key.
-  </para>
-
-  <para>
-   Note that the certificate and its private key must be accessible to the
-   FoundationDB user account that the server runs under. These files are also
-   NOT managed by NixOS, as putting them into the store may reveal private
-   information.
-  </para>
-
-  <para>
-   After you have a key and certificate file in place, it is not enough to
-   simply set the NixOS module options -- you must also configure the
-   <command>fdb.cluster</command> file to specify that a given set of
-   coordinators use TLS. This is as simple as adding the suffix
-   <command>:tls</command> to your cluster coordinator configuration, after the
-   port number. For example, assuming you have a coordinator on localhost with
-   the default configuration, simply specifying:
-  </para>
-
-<programlisting>
-XXXXXX:XXXXXX@127.0.0.1:4500:tls
-</programlisting>
-
-  <para>
-   will configure all clients and server processes to use TLS from now on.
-  </para>
- </section>
- <section xml:id="module-services-foundationdb-disaster-recovery">
-  <title>Backups and Disaster Recovery</title>
-
-  <para>
-   The usual rules for doing FoundationDB backups apply on NixOS as written in
-   the FoundationDB manual. However, one important difference is the security
-   profile for NixOS: by default, the <command>foundationdb</command> systemd
-   unit uses <emphasis>Linux namespaces</emphasis> to restrict write access to
-   the system, except for the log directory, data directory, and the
-   <command>/etc/foundationdb/</command> directory. This is enforced by default
-   and cannot be disabled.
-  </para>
-
-  <para>
-   However, a side effect of this is that the <command>fdbbackup</command>
-   command doesn't work properly for local filesystem backups: FoundationDB
-   uses a server process alongside the database processes to perform backups
-   and copy the backups to the filesystem. As a result, this process is put
-   under the restricted namespaces above: the backup process can only write to
-   a limited number of paths.
-  </para>
-
-  <para>
-   In order to allow flexible backup locations on local disks, the FoundationDB
-   NixOS module supports a
-   <option>services.foundationdb.extraReadWritePaths</option> option. This
-   option takes a list of paths, and adds them to the systemd unit, allowing
-   the processes inside the service to write (and read) the specified
-   directories.
-  </para>
-
-  <para>
-   For example, to create backups in <command>/opt/fdb-backups</command>, first
-   set up the paths in the module options:
-  </para>
-
-<programlisting>
-services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
-</programlisting>
-
-  <para>
-   Restart the FoundationDB service, and it will now be able to write to this
-   directory (even if it does not yet exist.) Note: this path
-   <emphasis>must</emphasis> exist before restarting the unit. Otherwise,
-   systemd will not include it in the private FoundationDB namespace (and it
-   will not add it dynamically at runtime).
-  </para>
-
-  <para>
-   You can now perform a backup:
-  </para>
-
-<screen>
-<prompt>$ </prompt>sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
-<prompt>$ </prompt>sudo -u foundationdb fdbbackup status -t default
-</screen>
- </section>
- <section xml:id="module-services-foundationdb-limitations">
-  <title>Known limitations</title>
-
-  <para>
-   The FoundationDB setup for NixOS should currently be considered beta.
-   FoundationDB is not new software, but the NixOS compilation and integration
-   has only undergone fairly basic testing of all the available functionality.
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     There is no way to specify individual parameters for individual
-     <command>fdbserver</command> processes. Currently, all server processes
-     inherit all the global <command>fdbmonitor</command> settings.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Ruby bindings are not currently installed.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Go bindings are not currently installed.
-    </para>
-   </listitem>
-  </itemizedlist>
- </section>
- <section xml:id="module-services-foundationdb-options">
-  <title>Options</title>
-
-  <para>
-   NixOS's FoundationDB module allows you to configure all of the most relevant
-   configuration options for <command>fdbmonitor</command>, matching it quite
-   closely. A complete list of options for the FoundationDB module may be found
-   <link linkend="opt-services.foundationdb.enable">here</link>. You should
-   also read the FoundationDB documentation as well.
-  </para>
- </section>
- <section xml:id="module-services-foundationdb-full-docs">
-  <title>Full documentation</title>
-
-  <para>
-   FoundationDB is a complex piece of software, and requires careful
-   administration to properly use. Full documentation for administration can be
-   found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/databases/hbase-standalone.nix b/nixpkgs/nixos/modules/services/databases/hbase-standalone.nix
index ca891fe8a5cd..1ee73ec8d1ff 100644
--- a/nixpkgs/nixos/modules/services/databases/hbase-standalone.nix
+++ b/nixpkgs/nixos/modules/services/databases/hbase-standalone.nix
@@ -41,10 +41,10 @@ in {
   options = {
     services.hbase-standalone = {
 
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         HBase master in standalone mode with embedded regionserver and zookeper.
         Do not use this configuration for production nor for evaluating HBase performance.
-      '';
+      '');
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/databases/influxdb.nix b/nixpkgs/nixos/modules/services/databases/influxdb.nix
index 9b3922c70af3..b3361d2014ca 100644
--- a/nixpkgs/nixos/modules/services/databases/influxdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/influxdb.nix
@@ -96,10 +96,8 @@ let
     };
   } cfg.extraConfig;
 
-  configFile = pkgs.runCommandLocal "config.toml" {
-    nativeBuildInputs = [ pkgs.remarshal ];
-  } ''
-    remarshal -if json -of toml \
+  configFile = pkgs.runCommandLocal "config.toml" { } ''
+    ${pkgs.buildPackages.remarshal}/bin/remarshal -if json -of toml \
       < ${pkgs.writeText "config.json" (builtins.toJSON configOptions)} \
       > $out
   '';
diff --git a/nixpkgs/nixos/modules/services/databases/influxdb2.nix b/nixpkgs/nixos/modules/services/databases/influxdb2.nix
index 8eeec7816c29..e74de66ddc2f 100644
--- a/nixpkgs/nixos/modules/services/databases/influxdb2.nix
+++ b/nixpkgs/nixos/modules/services/databases/influxdb2.nix
@@ -10,7 +10,7 @@ in
 {
   options = {
     services.influxdb2 = {
-      enable = mkEnableOption "the influxdb2 server";
+      enable = mkEnableOption (lib.mdDoc "the influxdb2 server");
 
       package = mkOption {
         default = pkgs.influxdb2-server;
@@ -40,6 +40,7 @@ in
       after = [ "network.target" ];
       environment = {
         INFLUXD_CONFIG_PATH = configFile;
+        ZONEINFO = "${pkgs.tzdata}/share/zoneinfo";
       };
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/influxd --bolt-path \${STATE_DIRECTORY}/influxd.bolt --engine-path \${STATE_DIRECTORY}/engine";
diff --git a/nixpkgs/nixos/modules/services/databases/lldap.nix b/nixpkgs/nixos/modules/services/databases/lldap.nix
new file mode 100644
index 000000000000..960792d0805f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/lldap.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, utils, ... }:
+
+let
+  cfg = config.services.lldap;
+  format = pkgs.formats.toml { };
+in
+{
+  options.services.lldap = with lib; {
+    enable = mkEnableOption (mdDoc "lldap");
+
+    package = mkPackageOptionMD pkgs "lldap" { };
+
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      example = {
+        LLDAP_JWT_SECRET_FILE = "/run/lldap/jwt_secret";
+        LLDAP_LDAP_USER_PASS_FILE = "/run/lldap/user_password";
+      };
+      description = lib.mdDoc ''
+        Environment variables passed to the service.
+        Any config option name prefixed with `LLDAP_` takes priority over the one in the configuration file.
+      '';
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
+      '';
+    };
+
+    settings = mkOption {
+      description = mdDoc ''
+        Free-form settings written directly to the `lldap_config.toml` file.
+        Refer to <https://github.com/lldap/lldap/blob/main/lldap_config.docker_template.toml> for supported values.
+      '';
+
+      default = { };
+
+      type = types.submodule {
+        freeformType = format.type;
+        options = {
+          ldap_host = mkOption {
+            type = types.str;
+            description = mdDoc "The host address that the LDAP server will be bound to.";
+            default = "::";
+          };
+
+          ldap_port = mkOption {
+            type = types.port;
+            description = mdDoc "The port on which to have the LDAP server.";
+            default = 3890;
+          };
+
+          http_host = mkOption {
+            type = types.str;
+            description = mdDoc "The host address that the HTTP server will be bound to.";
+            default = "::";
+          };
+
+          http_port = mkOption {
+            type = types.port;
+            description = mdDoc "The port on which to have the HTTP server, for user login and administration.";
+            default = 17170;
+          };
+
+          http_url = mkOption {
+            type = types.str;
+            description = mdDoc "The public URL of the server, for password reset links.";
+            default = "http://localhost";
+          };
+
+          ldap_base_dn = mkOption {
+            type = types.str;
+            description = mdDoc "Base DN for LDAP.";
+            example = "dc=example,dc=com";
+          };
+
+          ldap_user_dn = mkOption {
+            type = types.str;
+            description = mdDoc "Admin username";
+            default = "admin";
+          };
+
+          ldap_user_email = mkOption {
+            type = types.str;
+            description = mdDoc "Admin email.";
+            default = "admin@example.com";
+          };
+
+          database_url = mkOption {
+            type = types.str;
+            description = mdDoc "Database URL.";
+            default = "sqlite://./users.db?mode=rwc";
+            example = "postgres://postgres-user:password@postgres-server/my-database";
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.lldap = {
+      description = "Lightweight LDAP server (lldap)";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package} run --config-file ${format.generate "lldap_config.toml" cfg.settings}";
+        StateDirectory = "lldap";
+        WorkingDirectory = "%S/lldap";
+        User = "lldap";
+        Group = "lldap";
+        DynamicUser = true;
+        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+      };
+      inherit (cfg) environment;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/memcached.nix b/nixpkgs/nixos/modules/services/databases/memcached.nix
index 33627e8ad349..542c80ab2e67 100644
--- a/nixpkgs/nixos/modules/services/databases/memcached.nix
+++ b/nixpkgs/nixos/modules/services/databases/memcached.nix
@@ -17,7 +17,7 @@ in
   options = {
 
     services.memcached = {
-      enable = mkEnableOption "Memcached";
+      enable = mkEnableOption (lib.mdDoc "Memcached");
 
       user = mkOption {
         type = types.str;
@@ -37,7 +37,7 @@ in
         description = lib.mdDoc "The port to bind to.";
       };
 
-      enableUnixSocket = mkEnableOption "unix socket at /run/memcached/memcached.sock";
+      enableUnixSocket = mkEnableOption (lib.mdDoc "unix socket at /run/memcached/memcached.sock");
 
       maxMemory = mkOption {
         type = types.ints.unsigned;
diff --git a/nixpkgs/nixos/modules/services/databases/monetdb.nix b/nixpkgs/nixos/modules/services/databases/monetdb.nix
index c6836128d9ab..5573b530a913 100644
--- a/nixpkgs/nixos/modules/services/databases/monetdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/monetdb.nix
@@ -12,7 +12,7 @@ in {
   options = {
     services.monetdb = {
 
-      enable = mkEnableOption "the MonetDB database server";
+      enable = mkEnableOption (lib.mdDoc "the MonetDB database server");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/databases/mongodb.nix b/nixpkgs/nixos/modules/services/databases/mongodb.nix
index 981185cc534c..8f3be1492e9e 100644
--- a/nixpkgs/nixos/modules/services/databases/mongodb.nix
+++ b/nixpkgs/nixos/modules/services/databases/mongodb.nix
@@ -29,15 +29,15 @@ in
 
     services.mongodb = {
 
-      enable = mkEnableOption "the MongoDB server";
+      enable = mkEnableOption (lib.mdDoc "the MongoDB server");
 
       package = mkOption {
         default = pkgs.mongodb;
         defaultText = literalExpression "pkgs.mongodb";
         type = types.package;
-        description = "
+        description = lib.mdDoc ''
           Which MongoDB derivation to use.
-        ";
+        '';
       };
 
       user = mkOption {
@@ -142,7 +142,7 @@ in
           User = cfg.user;
           PIDFile = cfg.pidFile;
           Type = "forking";
-          TimeoutStartSec=120; # intial creating of journal can take some time
+          TimeoutStartSec=120; # initial creating of journal can take some time
           PermissionsStartOnly = true;
         };
 
diff --git a/nixpkgs/nixos/modules/services/databases/mysql.nix b/nixpkgs/nixos/modules/services/databases/mysql.nix
index 1a096d2a88a4..128bb0862175 100644
--- a/nixpkgs/nixos/modules/services/databases/mysql.nix
+++ b/nixpkgs/nixos/modules/services/databases/mysql.nix
@@ -31,54 +31,54 @@ in
 
     services.mysql = {
 
-      enable = mkEnableOption "MySQL server";
+      enable = mkEnableOption (lib.mdDoc "MySQL server");
 
       package = mkOption {
         type = types.package;
         example = literalExpression "pkgs.mariadb";
-        description = "
+        description = lib.mdDoc ''
           Which MySQL derivation to use. MariaDB packages are supported too.
-        ";
+        '';
       };
 
       user = mkOption {
         type = types.str;
         default = "mysql";
-        description = ''
+        description = lib.mdDoc ''
           User account under which MySQL runs.
 
-          <note><para>
+          ::: {.note}
           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 = ''
+        description = lib.mdDoc ''
           Group account under which MySQL runs.
 
-          <note><para>
+          ::: {.note}
           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 = ''
+        description = lib.mdDoc ''
           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
+          ::: {.note}
+          If left as the default value of `/var/lib/mysql` 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>
+          :::
         '';
       };
 
@@ -107,20 +107,18 @@ in
       settings = mkOption {
         type = format.type;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           MySQL configuration. Refer to
-          <link xlink:href="https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html"/>,
-          <link xlink:href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html"/>,
-          and <link xlink:href="https://mariadb.com/kb/en/server-system-variables/"/>
+          <https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html>,
+          <https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html>,
+          and <https://mariadb.com/kb/en/server-system-variables/>
           for details on supported values.
 
-          <note>
-            <para>
-              MySQL configuration options such as <literal>--quick</literal> should be treated as
-              boolean options and provided values such as <literal>true</literal>, <literal>false</literal>,
-              <literal>1</literal>, or <literal>0</literal>. See the provided example below.
-            </para>
-          </note>
+          ::: {.note}
+          MySQL configuration options such as `--quick` should be treated as
+          boolean options and provided values such as `true`, `false`,
+          `1`, or `0`. See the provided example below.
+          :::
         '';
         example = literalExpression ''
           {
@@ -162,10 +160,12 @@ in
           List of database names and their initial schemas that should be used to create databases on the first startup
           of MySQL. The schema attribute is optional: If not specified, an empty database is created.
         '';
-        example = [
-          { name = "foodatabase"; schema = literalExpression "./foodatabase.sql"; }
-          { name = "bardatabase"; }
-        ];
+        example = literalExpression ''
+          [
+            { name = "foodatabase"; schema = ./foodatabase.sql; }
+            { name = "bardatabase"; }
+          ]
+        '';
       };
 
       initialScript = mkOption {
diff --git a/nixpkgs/nixos/modules/services/databases/neo4j.nix b/nixpkgs/nixos/modules/services/databases/neo4j.nix
index 833e6606d984..090502424028 100644
--- a/nixpkgs/nixos/modules/services/databases/neo4j.nix
+++ b/nixpkgs/nixos/modules/services/databases/neo4j.nix
@@ -333,9 +333,9 @@ in {
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable the HTTP connector for Neo4j. Setting this option to
-          <literal>false</literal> will stop Neo4j from listening for incoming
+          `false` will stop Neo4j from listening for incoming
           connections on the HTTPS port (7474 by default).
         '';
       };
@@ -538,7 +538,7 @@ in {
             type = types.listOf types.path;
             internal = true;
             readOnly = true;
-            description = ''
+            description = lib.mdDoc ''
               Directories of this policy that will be created automatically
               when the certificates directory is left at its default value.
               This includes all options of type path that are left at their
@@ -636,6 +636,6 @@ in {
     };
 
   meta = {
-    maintainers = with lib.maintainers; [ patternspandemic jonringer erictapen ];
+    maintainers = with lib.maintainers; [ patternspandemic jonringer ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/databases/openldap.nix b/nixpkgs/nixos/modules/services/databases/openldap.nix
index 7a59de372f24..cba3442023cb 100644
--- a/nixpkgs/nixos/modules/services/databases/openldap.nix
+++ b/nixpkgs/nixos/modules/services/databases/openldap.nix
@@ -16,7 +16,7 @@ let
       # systemd/systemd#19604
       description = ''
         LDAP value - either a string, or an attrset containing
-        <literal>path</literal> or <literal>base64</literal> for included
+        `path` or `base64` for included
         values or base-64 encoded values respectively.
       '';
       check = x: lib.isString x || (lib.isAttrs x && (x ? path || x ? base64));
diff --git a/nixpkgs/nixos/modules/services/databases/opentsdb.nix b/nixpkgs/nixos/modules/services/databases/opentsdb.nix
index 45c84b12a50e..288b716fce03 100644
--- a/nixpkgs/nixos/modules/services/databases/opentsdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/opentsdb.nix
@@ -15,13 +15,7 @@ in {
 
     services.opentsdb = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to run OpenTSDB.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "OpenTSDB");
 
       package = mkOption {
         type = types.package;
@@ -49,7 +43,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 4242;
         description = lib.mdDoc ''
           Which port OpenTSDB listens on.
diff --git a/nixpkgs/nixos/modules/services/databases/pgmanage.nix b/nixpkgs/nixos/modules/services/databases/pgmanage.nix
index 9ce2265a4dee..cbf988d596f4 100644
--- a/nixpkgs/nixos/modules/services/databases/pgmanage.nix
+++ b/nixpkgs/nixos/modules/services/databases/pgmanage.nix
@@ -44,7 +44,7 @@ let
 in {
 
   options.services.pgmanage = {
-    enable = mkEnableOption "PostgreSQL Administration for the web";
+    enable = mkEnableOption (lib.mdDoc "PostgreSQL Administration for the web");
 
     package = mkOption {
       type = types.package;
@@ -85,7 +85,7 @@ in {
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8080;
       description = lib.mdDoc ''
         This tells pgmanage what port to listen on for browser requests.
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.md b/nixpkgs/nixos/modules/services/databases/postgresql.md
new file mode 100644
index 000000000000..4d66ee38be42
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.md
@@ -0,0 +1,210 @@
+# PostgreSQL {#module-postgresql}
+
+<!-- FIXME: render nicely -->
+<!-- FIXME: source can be added automatically -->
+
+*Source:* {file}`modules/services/databases/postgresql.nix`
+
+*Upstream documentation:* <http://www.postgresql.org/docs/>
+
+<!-- FIXME: more stuff, like maintainer? -->
+
+PostgreSQL is an advanced, free relational database.
+<!-- MORE -->
+
+## Configuring {#module-services-postgres-configuring}
+
+To enable PostgreSQL, add the following to your {file}`configuration.nix`:
+```
+services.postgresql.enable = true;
+services.postgresql.package = pkgs.postgresql_11;
+```
+Note that you are required to specify the desired version of PostgreSQL (e.g. `pkgs.postgresql_11`). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for [](#opt-services.postgresql.package) such as the most recent release of PostgreSQL.
+
+<!--
+After running {command}`nixos-rebuild`, you can verify
+whether PostgreSQL works by running {command}`psql`:
+
+```ShellSession
+$ psql
+psql (9.2.9)
+Type "help" for help.
+
+alice=>
+```
+-->
+
+By default, PostgreSQL stores its databases in {file}`/var/lib/postgresql/$psqlSchema`. You can override this using [](#opt-services.postgresql.dataDir), e.g.
+```
+services.postgresql.dataDir = "/data/postgresql";
+```
+
+## Upgrading {#module-services-postgres-upgrading}
+
+::: {.note}
+The steps below demonstrate how to upgrade from an older version to `pkgs.postgresql_13`.
+These instructions are also applicable to other versions.
+:::
+
+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 {file}`/var/lib/postgresql/&lt;version&gt;` where each `version`
+can be obtained like this:
+```
+$ nix-instantiate --eval -A postgresql_13.psqlSchema
+"13"
+```
+For an upgrade, a script like this can be used to simplify the process:
+```
+{ config, pkgs, ... }:
+{
+  environment.systemPackages = [
+    (let
+      # XXX specify the postgresql package you'd like to upgrade to.
+      # Do not forget to list the extensions you need.
+      newPostgres = pkgs.postgresql_13.withPackages (pp: [
+        # pp.plv8
+      ]);
+    in pkgs.writeScriptBin "upgrade-pg-cluster" ''
+      set -eux
+      # XXX it's perhaps advisable to stop all services that depend on postgresql
+      systemctl stop postgresql
+
+      export NEWDATA="/var/lib/postgresql/${newPostgres.psqlSchema}"
+
+      export NEWBIN="${newPostgres}/bin"
+
+      export OLDDATA="${config.services.postgresql.dataDir}"
+      export OLDBIN="${config.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 \
+        "$@"
+    '')
+  ];
+}
+```
+
+The upgrade process is:
+
+  1. Rebuild nixos configuration with the configuration above added to your {file}`configuration.nix`. Alternatively, add that into separate file and reference it in `imports` list.
+  2. Login as root (`sudo su -`)
+  3. Run `upgrade-pg-cluster`. It will stop old postgresql, initialize a new one and migrate the old one to the new one. You may supply arguments like `--jobs 4` and `--link` to speedup migration process. See <https://www.postgresql.org/docs/current/pgupgrade.html> for details.
+  4. Change postgresql package in NixOS configuration to the one you were upgrading to via [](#opt-services.postgresql.package). Rebuild NixOS. This should start new postgres using upgraded data directory and all services you stopped during the upgrade.
+  5. After the upgrade it's advisable to analyze the new cluster.
+
+       - For PostgreSQL ≥ 14, use the `vacuumdb` command printed by the upgrades script.
+       - For PostgreSQL < 14, run (as `su -l postgres` in the [](#opt-services.postgresql.dataDir), in this example {file}`/var/lib/postgresql/13`):
+
+         ```
+         $ ./analyze_new_cluster.sh
+         ```
+
+     ::: {.warning}
+     The next step removes the old state-directory!
+     :::
+
+     ```
+     $ ./delete_old_cluster.sh
+     ```
+
+## Options {#module-services-postgres-options}
+
+A complete list of options for the PostgreSQL module may be found [here](#opt-services.postgresql.enable).
+
+## Plugins {#module-services-postgres-plugins}
+
+Plugins collection for each PostgreSQL version can be accessed with `.pkgs`. For example, for `pkgs.postgresql_11` package, its plugin collection is accessed by `pkgs.postgresql_11.pkgs`:
+```ShellSession
+$ nix repl '<nixpkgs>'
+
+Loading '<nixpkgs>'...
+Added 10574 variables.
+
+nix-repl> postgresql_11.pkgs.<TAB><TAB>
+postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
+postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
+postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
+postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
+postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
+postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
+...
+```
+
+To add plugins via NixOS configuration, set `services.postgresql.extraPlugins`:
+```
+services.postgresql.package = pkgs.postgresql_11;
+services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [
+  pg_repack
+  postgis
+];
+```
+
+You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function `.withPackages`. For example, creating a custom PostgreSQL package in an overlay can look like:
+```
+self: super: {
+  postgresql_custom = self.postgresql_11.withPackages (ps: [
+    ps.pg_repack
+    ps.postgis
+  ]);
+}
+```
+
+Here's a recipe on how to override a particular plugin through an overlay:
+```
+self: super: {
+  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
+    pkgs = super.postgresql_11.pkgs // {
+      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
+        name = "pg_repack-v20181024";
+        src = self.fetchzip {
+          url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
+          sha256 = "17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5";
+        };
+      });
+    };
+  };
+}
+```
+
+## JIT (Just-In-Time compilation) {#module-services-postgres-jit}
+
+[JIT](https://www.postgresql.org/docs/current/jit-reason.html)-support in the PostgreSQL package
+is disabled by default because of the ~300MiB closure-size increase from the LLVM dependency. It
+can be optionally enabled in PostgreSQL with the following config option:
+
+```nix
+{
+  services.postgresql.enableJIT = true;
+}
+```
+
+This makes sure that the [`jit`](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-JIT)-setting
+is set to `on` and a PostgreSQL package with JIT enabled is used. Further tweaking of the JIT compiler, e.g. setting a different
+query cost threshold via [`jit_above_cost`](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-JIT-ABOVE-COST)
+can be done manually via [`services.postgresql.settings`](#opt-services.postgresql.settings).
+
+The attribute-names of JIT-enabled PostgreSQL packages are suffixed with `_jit`, i.e. for each `pkgs.postgresql`
+(and `pkgs.postgresql_<major>`) in `nixpkgs` there's also a `pkgs.postgresql_jit` (and `pkgs.postgresql_<major>_jit`).
+Alternatively, a JIT-enabled variant can be derived from a given `postgresql` package via `postgresql.withJIT`.
+This is also useful if it's not clear which attribute from `nixpkgs` was originally used (e.g. when working with
+[`config.services.postgresql.package`](#opt-services.postgresql.package) or if the package was modified via an
+overlay) since all modifications are propagated to `withJIT`. I.e.
+
+```nix
+with import <nixpkgs> {
+  overlays = [
+    (self: super: {
+      postgresql = super.postgresql.overrideAttrs (_: { pname = "foobar"; });
+    })
+  ];
+};
+postgresql.withJIT.pname
+```
+
+evaluates to `"foobar"`.
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.nix b/nixpkgs/nixos/modules/services/databases/postgresql.nix
index d0135647d6af..a7016bbee3a8 100644
--- a/nixpkgs/nixos/modules/services/databases/postgresql.nix
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.nix
@@ -7,9 +7,18 @@ let
   cfg = config.services.postgresql;
 
   postgresql =
+    let
+      # ensure that
+      #   services.postgresql = {
+      #     enableJIT = true;
+      #     package = pkgs.postgresql_<major>;
+      #   };
+      # works.
+      base = if cfg.enableJIT && !cfg.package.jitSupport then cfg.package.withJIT else cfg.package;
+    in
     if cfg.extraPlugins == []
-      then cfg.package
-      else cfg.package.withPackages (_: cfg.extraPlugins);
+      then base
+      else base.withPackages (_: cfg.extraPlugins);
 
   toStr = value:
     if true == value then "yes"
@@ -40,7 +49,9 @@ in
 
     services.postgresql = {
 
-      enable = mkEnableOption "PostgreSQL Server";
+      enable = mkEnableOption (lib.mdDoc "PostgreSQL Server");
+
+      enableJIT = mkEnableOption (lib.mdDoc "JIT support");
 
       package = mkOption {
         type = types.package;
@@ -51,7 +62,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 5432;
         description = lib.mdDoc ''
           The port on which PostgreSQL listens.
@@ -79,15 +90,15 @@ in
       authentication = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Defines how users authenticate themselves to the server. See the
-          <link xlink:href="https://www.postgresql.org/docs/current/auth-pg-hba-conf.html">PostgreSQL documentation for pg_hba.conf</link>
+          [PostgreSQL documentation for pg_hba.conf](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html)
           for details on the expected format of this option. By default,
           peer based authentication will be used for users connecting
           via the Unix socket, and md5 password authentication will be
           used for users connecting via TCP. Any added rules will be
           inserted above the default rules. If you'd like to replace the
-          default rules entirely, you can use <function>lib.mkForce</function> in your
+          default rules entirely, you can use `lib.mkForce` in your
           module.
         '';
       };
@@ -146,6 +157,7 @@ in
                 Name of the user to ensure.
               '';
             };
+
             ensurePermissions = mkOption {
               type = types.attrsOf types.str;
               default = {};
@@ -167,6 +179,154 @@ in
                 }
               '';
             };
+
+            ensureClauses = mkOption {
+              description = lib.mdDoc ''
+                An attrset of clauses to grant to the user. Under the hood this uses the
+                [ALTER USER syntax](https://www.postgresql.org/docs/current/sql-alteruser.html) for each attrName where
+                the attrValue is true in the attrSet:
+                `ALTER USER user.name WITH attrName`
+              '';
+              example = literalExpression ''
+                {
+                  superuser = true;
+                  createrole = true;
+                  createdb = true;
+                }
+              '';
+              default = {};
+              defaultText = lib.literalMD ''
+                The default, `null`, means that the user created will have the default permissions assigned by PostgreSQL. Subsequent server starts will not set or unset the clause, so imperative changes are preserved.
+              '';
+              type = types.submodule {
+                options = let
+                  defaultText = lib.literalMD ''
+                    `null`: do not set. For newly created roles, use PostgreSQL's default. For existing roles, do not touch this clause.
+                  '';
+                in {
+                  superuser = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user, created by the ensureUser attr, superuser permissions. From the postgres docs:
+
+                      A database superuser bypasses all permission checks,
+                      except the right to log in. This is a dangerous privilege
+                      and should not be used carelessly; it is best to do most
+                      of your work as a role that is not a superuser. To create
+                      a new database superuser, use CREATE ROLE name SUPERUSER.
+                      You must do this as a role that is already a superuser.
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                  createrole = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user, created by the ensureUser attr, createrole permissions. From the postgres docs:
+
+                      A role must be explicitly given permission to create more
+                      roles (except for superusers, since those bypass all
+                      permission checks). To create such a role, use CREATE
+                      ROLE name CREATEROLE. A role with CREATEROLE privilege
+                      can alter and drop other roles, too, as well as grant or
+                      revoke membership in them. However, to create, alter,
+                      drop, or change membership of a superuser role, superuser
+                      status is required; CREATEROLE is insufficient for that.
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                  createdb = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user, created by the ensureUser attr, createdb permissions. From the postgres docs:
+
+                      A role must be explicitly given permission to create
+                      databases (except for superusers, since those bypass all
+                      permission checks). To create such a role, use CREATE
+                      ROLE name CREATEDB.
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                  "inherit" = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user created inherit permissions. From the postgres docs:
+
+                      A role is given permission to inherit the privileges of
+                      roles it is a member of, by default. However, to create a
+                      role without the permission, use CREATE ROLE name
+                      NOINHERIT.
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                  login = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user, created by the ensureUser attr, login permissions. From the postgres docs:
+
+                      Only roles that have the LOGIN attribute can be used as
+                      the initial role name for a database connection. A role
+                      with the LOGIN attribute can be considered the same as a
+                      “database user”. To create a role with login privilege,
+                      use either:
+
+                      CREATE ROLE name LOGIN; CREATE USER name;
+
+                      (CREATE USER is equivalent to CREATE ROLE except that
+                      CREATE USER includes LOGIN by default, while CREATE ROLE
+                      does not.)
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                  replication = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user, created by the ensureUser attr, replication permissions. From the postgres docs:
+
+                      A role must explicitly be given permission to initiate
+                      streaming replication (except for superusers, since those
+                      bypass all permission checks). A role used for streaming
+                      replication must have LOGIN permission as well. To create
+                      such a role, use CREATE ROLE name REPLICATION LOGIN.
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                  bypassrls = mkOption {
+                    type = types.nullOr types.bool;
+                    description = lib.mdDoc ''
+                      Grants the user, created by the ensureUser attr, replication permissions. From the postgres docs:
+
+                      A role must be explicitly given permission to bypass
+                      every row-level security (RLS) policy (except for
+                      superusers, since those bypass all permission checks). To
+                      create such a role, use CREATE ROLE name BYPASSRLS as a
+                      superuser.
+
+                      More information on postgres roles can be found [here](https://www.postgresql.org/docs/current/role-attributes.html)
+                    '';
+                    default = null;
+                    inherit defaultText;
+                  };
+                };
+              };
+            };
           };
         });
         default = [];
@@ -230,15 +390,15 @@ in
       settings = mkOption {
         type = with types; attrsOf (oneOf [ bool float int str ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           PostgreSQL configuration. Refer to
-          <link xlink:href="https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE"/>
-          for an overview of <literal>postgresql.conf</literal>.
+          <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
+          for an overview of `postgresql.conf`.
 
-          <note><para>
-            String values will automatically be enclosed in single quotes. Single quotes will be
-            escaped with two single quotes as described by the upstream documentation linked above.
-          </para></note>
+          ::: {.note}
+          String values will automatically be enclosed in single quotes. Single quotes will be
+          escaped with two single quotes as described by the upstream documentation linked above.
+          :::
         '';
         example = literalExpression ''
           {
@@ -264,7 +424,7 @@ in
         default = "postgres";
         internal = true;
         readOnly = true;
-        description = ''
+        description = lib.mdDoc ''
           PostgreSQL superuser account to use for various operations. Internal since changing
           this value would lead to breakage while setting up databases.
         '';
@@ -286,19 +446,21 @@ in
         log_line_prefix = cfg.logLinePrefix;
         listen_addresses = if cfg.enableTCPIP then "*" else "localhost";
         port = cfg.port;
+        jit = mkDefault (if cfg.enableJIT then "on" else "off");
       };
 
     services.postgresql.package = let
         mkThrow = ver: throw "postgresql_${ver} was removed, please upgrade your postgresql version.";
+        base = if versionAtLeast config.system.stateVersion "22.05" then pkgs.postgresql_14
+            else 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 mkThrow "9_6"
+            else mkThrow "9_5";
     in
       # Note: when changing the default, make it conditional on
       # ‘system.stateVersion’ to maintain compatibility with existing
       # systems!
-      mkDefault (if versionAtLeast config.system.stateVersion "22.05" then pkgs.postgresql_14
-            else 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 mkThrow "9_6"
-            else mkThrow "9_5");
+      mkDefault (if cfg.enableJIT then base.withJIT else base);
 
     services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}";
 
@@ -327,7 +489,7 @@ in
      "/share/postgresql"
     ];
 
-    system.extraDependencies = lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) configFileCheck;
+    system.checks = lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) configFileCheck;
 
     systemd.services.postgresql =
       { description = "PostgreSQL Server";
@@ -380,12 +542,29 @@ in
               $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"'
             '') cfg.ensureDatabases}
           '' + ''
-            ${concatMapStrings (user: ''
-              $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
-              ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
-                $PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"'
-              '') user.ensurePermissions)}
-            '') cfg.ensureUsers}
+            ${
+              concatMapStrings
+              (user:
+                let
+                  userPermissions = concatStringsSep "\n"
+                    (mapAttrsToList
+                      (database: permission: ''$PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' '')
+                      user.ensurePermissions
+                    );
+
+                  filteredClauses = filterAttrs (name: value: value != null) user.ensureClauses;
+
+                  clauseSqlStatements = attrValues (mapAttrs (n: v: if v then n else "no${n}") filteredClauses);
+
+                  userClauses = ''$PSQL -tAc 'ALTER ROLE "${user.name}" ${concatStringsSep " " clauseSqlStatements}' '';
+                in ''
+                  $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
+                  ${userPermissions}
+                  ${userClauses}
+                ''
+              )
+              cfg.ensureUsers
+            }
           '';
 
         serviceConfig = mkMerge [
@@ -419,6 +598,6 @@ in
 
   };
 
-  meta.doc = ./postgresql.xml;
+  meta.doc = ./postgresql.md;
   meta.maintainers = with lib.maintainers; [ thoughtpolice danbst ];
 }
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.xml b/nixpkgs/nixos/modules/services/databases/postgresql.xml
deleted file mode 100644
index 0ca9f3faed21..000000000000
--- a/nixpkgs/nixos/modules/services/databases/postgresql.xml
+++ /dev/null
@@ -1,214 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-postgresql">
- <title>PostgreSQL</title>
-<!-- FIXME: render nicely -->
-<!-- FIXME: source can be added automatically -->
- <para>
-  <emphasis>Source:</emphasis> <filename>modules/services/databases/postgresql.nix</filename>
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis> <link xlink:href="http://www.postgresql.org/docs/"/>
- </para>
-<!-- FIXME: more stuff, like maintainer? -->
- <para>
-  PostgreSQL is an advanced, free relational database.
-<!-- MORE -->
- </para>
- <section xml:id="module-services-postgres-configuring">
-  <title>Configuring</title>
-
-  <para>
-   To enable PostgreSQL, add the following to your <filename>configuration.nix</filename>:
-<programlisting>
-<xref linkend="opt-services.postgresql.enable"/> = true;
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
-</programlisting>
-   Note that you are required to specify the desired version of PostgreSQL (e.g. <literal>pkgs.postgresql_11</literal>). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for <xref linkend="opt-services.postgresql.package"/> such as the most recent release of PostgreSQL.
-  </para>
-
-<!--
-<para>After running <command>nixos-rebuild</command>, you can verify
-whether PostgreSQL works by running <command>psql</command>:
-
-<screen>
-<prompt>$ </prompt>psql
-psql (9.2.9)
-Type "help" for help.
-
-<prompt>alice=></prompt>
-</screen>
--->
-
-  <para>
-   By default, PostgreSQL stores its databases in <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using <xref linkend="opt-services.postgresql.dataDir"/>, e.g.
-<programlisting>
-<xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql";
-</programlisting>
-  </para>
- </section>
- <section xml:id="module-services-postgres-upgrading">
-  <title>Upgrading</title>
-
-  <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 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>
-<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>
-
-  <para>
-   The upgrade process is:
-  </para>
-
-  <orderedlist>
-   <listitem>
-    <para>
-     Rebuild nixos configuration with the configuration above added to your <filename>configuration.nix</filename>. Alternatively, add that into separate file and reference it in <literal>imports</literal> list.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Login as root (<literal>sudo su -</literal>)
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     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 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 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>
- </section>
- <section xml:id="module-services-postgres-options">
-  <title>Options</title>
-
-  <para>
-   A complete list of options for the PostgreSQL module may be found <link linkend="opt-services.postgresql.enable">here</link>.
-  </para>
- </section>
- <section xml:id="module-services-postgres-plugins">
-  <title>Plugins</title>
-
-  <para>
-   Plugins collection for each PostgreSQL version can be accessed with <literal>.pkgs</literal>. For example, for <literal>pkgs.postgresql_11</literal> package, its plugin collection is accessed by <literal>pkgs.postgresql_11.pkgs</literal>:
-<screen>
-<prompt>$ </prompt>nix repl '&lt;nixpkgs&gt;'
-
-Loading '&lt;nixpkgs&gt;'...
-Added 10574 variables.
-
-<prompt>nix-repl&gt; </prompt>postgresql_11.pkgs.&lt;TAB&gt;&lt;TAB&gt;
-postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
-postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
-postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
-postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
-postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
-postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
-...
-</screen>
-  </para>
-
-  <para>
-   To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>:
-<programlisting>
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
-<xref linkend="opt-services.postgresql.extraPlugins"/> = with pkgs.postgresql_11.pkgs; [
-  pg_repack
-  postgis
-];
-</programlisting>
-  </para>
-
-  <para>
-   You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function <literal>.withPackages</literal>. For example, creating a custom PostgreSQL package in an overlay can look like:
-<programlisting>
-self: super: {
-  postgresql_custom = self.postgresql_11.withPackages (ps: [
-    ps.pg_repack
-    ps.postgis
-  ]);
-}
-</programlisting>
-  </para>
-
-  <para>
-   Here's a recipe on how to override a particular plugin through an overlay:
-<programlisting>
-self: super: {
-  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
-    pkgs = super.postgresql_11.pkgs // {
-      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
-        name = "pg_repack-v20181024";
-        src = self.fetchzip {
-          url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
-          sha256 = "17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5";
-        };
-      });
-    };
-  };
-}
-</programlisting>
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/databases/redis.nix b/nixpkgs/nixos/modules/services/databases/redis.nix
index b346438cfffc..1464f4487e39 100644
--- a/nixpkgs/nixos/modules/services/databases/redis.nix
+++ b/nixpkgs/nixos/modules/services/databases/redis.nix
@@ -61,22 +61,22 @@ in {
         description = lib.mdDoc "Which Redis derivation to use.";
       };
 
-      vmOverCommit = mkEnableOption ''
+      vmOverCommit = mkEnableOption (lib.mdDoc ''
         setting of vm.overcommit_memory to 1
         (Suggested for Background Saving: http://redis.io/topics/faq)
-      '';
+      '');
 
       servers = mkOption {
-        type = with types; attrsOf (submodule ({config, name, ...}@args: {
+        type = with types; attrsOf (submodule ({ config, name, ... }: {
           options = {
-            enable = mkEnableOption ''
+            enable = mkEnableOption (lib.mdDoc ''
               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;
@@ -105,6 +105,13 @@ in {
               '';
             };
 
+            extraParams = mkOption {
+              type = with types; listOf str;
+              default = [];
+              description = lib.mdDoc "Extra parameters to append to redis-server invocation";
+              example = [ "--sentinel" ];
+            };
+
             bind = mkOption {
               type = with types; nullOr str;
               default = "127.0.0.1";
@@ -264,14 +271,11 @@ in {
           };
           config.settings = mkMerge [
             {
-              port = config.port;
+              inherit (config) port logfile databases maxclients appendOnly;
               daemonize = false;
               supervised = "systemd";
               loglevel = config.logLevel;
-              logfile = config.logfile;
               syslog-enabled = config.syslog;
-              databases = config.databases;
-              maxclients = config.maxclients;
               save = if config.save == []
                 then ''""'' # Disable saving with `save = ""`
                 else map
@@ -279,12 +283,11 @@ in {
                   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.bind != null) { inherit (config) bind; })
             (mkIf (config.unixSocket != null) {
               unixsocket = config.unixSocket;
               unixsocketperm = toString config.unixSocketPerm;
@@ -340,16 +343,26 @@ in {
       after = [ "network.target" ];
 
       serviceConfig = {
-        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) ''
+        ExecStart = "${cfg.package}/bin/redis-server /var/lib/${redisName name}/redis.conf ${escapeShellArgs conf.extraParams}";
+        ExecStartPre = "+"+pkgs.writeShellScript "${redisName name}-prep-conf" (let
+          redisConfVar = "/var/lib/${redisName name}/redis.conf";
+          redisConfRun = "/run/${redisName name}/nixos.conf";
+          redisConfStore = redisConfig conf.settings;
+        in ''
+          touch "${redisConfVar}" "${redisConfRun}"
+          chown '${conf.user}' "${redisConfVar}" "${redisConfRun}"
+          chmod 0600 "${redisConfVar}" "${redisConfRun}"
+          if [ ! -s ${redisConfVar} ]; then
+            echo 'include "${redisConfRun}"' > "${redisConfVar}"
+          fi
+          echo 'include "${redisConfStore}"' > "${redisConfRun}"
+          ${optionalString (conf.requirePassFile != null) ''
             {
-              printf requirePass' '
+              echo -n "requirepass "
               cat ${escapeShellArg conf.requirePassFile}
-            } >>/run/${redisName name}/redis.conf
-          '')
-        )];
+            } >> "${redisConfRun}"
+          ''}
+        '');
         Type = "notify";
         # User and group
         User = conf.user;
diff --git a/nixpkgs/nixos/modules/services/databases/rethinkdb.nix b/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
index d93f15e86368..f5391b48e89c 100644
--- a/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
@@ -15,7 +15,7 @@ in
 
     services.rethinkdb = {
 
-      enable = mkEnableOption "RethinkDB server";
+      enable = mkEnableOption (lib.mdDoc "RethinkDB server");
 
       #package = mkOption {
       #  default = pkgs.rethinkdb;
diff --git a/nixpkgs/nixos/modules/services/databases/surrealdb.nix b/nixpkgs/nixos/modules/services/databases/surrealdb.nix
new file mode 100644
index 000000000000..050a5336cb4c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/surrealdb.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.surrealdb;
+in {
+
+  options = {
+    services.surrealdb = {
+      enable = mkEnableOption (lib.mdDoc "A scalable, distributed, collaborative, document-graph database, for the realtime web ");
+
+      package = mkOption {
+        default = pkgs.surrealdb;
+        defaultText = literalExpression "pkgs.surrealdb";
+        type = types.package;
+        description = lib.mdDoc ''
+          Which surrealdb derivation to use.
+        '';
+      };
+
+      dbPath = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          The path that surrealdb will write data to. Use null for in-memory.
+          Can be one of "memory", "file://:path", "tikv://:addr".
+        '';
+        default = "file:///var/lib/surrealdb/";
+        example = "memory";
+      };
+
+      host = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          The host that surrealdb will connect to.
+        '';
+        default = "127.0.0.1";
+        example = "127.0.0.1";
+      };
+
+      port = mkOption {
+        type = types.port;
+        description = lib.mdDoc ''
+          The port that surrealdb will connect to.
+        '';
+        default = 8000;
+        example = 8000;
+      };
+
+      userNamePath = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          Path to read the username from.
+        '';
+      };
+
+      passwordPath = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          Path to read the password from.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    # Used to connect to the running service
+    environment.systemPackages = [ cfg.package ] ;
+
+    systemd.services.surrealdb = {
+      description = "A scalable, distributed, collaborative, document-graph database, for the realtime web ";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      script = ''
+        ${cfg.package}/bin/surreal start \
+          --user $(${pkgs.systemd}/bin/systemd-creds cat SURREALDB_USERNAME) \
+          --pass $(${pkgs.systemd}/bin/systemd-creds cat SURREALDB_PASSWORD) \
+          --bind ${cfg.host}:${toString cfg.port} \
+          -- ${cfg.dbPath}
+      '';
+      serviceConfig = {
+        LoadCredential = [
+          "SURREALDB_USERNAME:${cfg.userNamePath}"
+          "SURREALDB_PASSWORD:${cfg.passwordPath}"
+        ];
+
+        DynamicUser = true;
+        Restart = "on-failure";
+        StateDirectory = "surrealdb";
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/victoriametrics.nix b/nixpkgs/nixos/modules/services/databases/victoriametrics.nix
index f87a5862f641..638066a42dbd 100644
--- a/nixpkgs/nixos/modules/services/databases/victoriametrics.nix
+++ b/nixpkgs/nixos/modules/services/databases/victoriametrics.nix
@@ -2,7 +2,7 @@
 let cfg = config.services.victoriametrics; in
 {
   options.services.victoriametrics = with lib; {
-    enable = mkEnableOption "victoriametrics";
+    enable = mkEnableOption (lib.mdDoc "victoriametrics");
     package = mkOption {
       type = types.package;
       default = pkgs.victoriametrics;
diff --git a/nixpkgs/nixos/modules/services/desktops/bamf.nix b/nixpkgs/nixos/modules/services/desktops/bamf.nix
index 13de3a44328f..3e40a7055348 100644
--- a/nixpkgs/nixos/modules/services/desktops/bamf.nix
+++ b/nixpkgs/nixos/modules/services/desktops/bamf.nix
@@ -13,7 +13,7 @@ with lib;
 
   options = {
     services.bamf = {
-      enable = mkEnableOption "bamf";
+      enable = mkEnableOption (lib.mdDoc "bamf");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/blueman.nix b/nixpkgs/nixos/modules/services/desktops/blueman.nix
index 18ad610247ed..fad2f21bce5b 100644
--- a/nixpkgs/nixos/modules/services/desktops/blueman.nix
+++ b/nixpkgs/nixos/modules/services/desktops/blueman.nix
@@ -9,7 +9,7 @@ in {
   ###### interface
   options = {
     services.blueman = {
-      enable = mkEnableOption "blueman";
+      enable = mkEnableOption (lib.mdDoc "blueman");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/deepin/app-services.nix b/nixpkgs/nixos/modules/services/desktops/deepin/app-services.nix
new file mode 100644
index 000000000000..6f9932e48733
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/deepin/app-services.nix
@@ -0,0 +1,36 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.deepin.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.deepin.app-services = {
+
+      enable = mkEnableOption (lib.mdDoc "Service collection of DDE applications, including dconfig-center");
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.deepin.app-services.enable {
+
+    environment.systemPackages = [ pkgs.deepin.dde-app-services ];
+
+    services.dbus.packages = [ pkgs.deepin.dde-app-services ];
+
+    environment.pathsToLink = [ "/share/dsg" ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/deepin/dde-api.nix b/nixpkgs/nixos/modules/services/desktops/deepin/dde-api.nix
new file mode 100644
index 000000000000..472d9860c108
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/deepin/dde-api.nix
@@ -0,0 +1,50 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.deepin.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.deepin.dde-api = {
+
+      enable = mkEnableOption (lib.mdDoc ''
+        Provides some dbus interfaces that is used for screen zone detecting,
+        thumbnail generating, and sound playing in Deepin Desktop Environment.
+      '');
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.deepin.dde-api.enable {
+
+     environment.systemPackages = [ pkgs.deepin.dde-api ];
+
+     services.dbus.packages = [ pkgs.deepin.dde-api ];
+
+     systemd.packages = [ pkgs.deepin.dde-api ];
+
+     environment.pathsToLink = [ "/lib/deepin-api" ];
+
+     users.groups.deepin-sound-player = { };
+     users.users.deepin-sound-player = {
+       description = "Deepin sound player";
+       home = "/var/lib/deepin-sound-player";
+       createHome = true;
+       group = "deepin-sound-player";
+       isSystemUser = true;
+     };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/deepin/dde-daemon.nix b/nixpkgs/nixos/modules/services/desktops/deepin/dde-daemon.nix
new file mode 100644
index 000000000000..9377f523ebf9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/deepin/dde-daemon.nix
@@ -0,0 +1,40 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.deepin.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.deepin.dde-daemon = {
+
+      enable = mkEnableOption (lib.mdDoc "Daemon for handling the deepin session settings");
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.deepin.dde-daemon.enable {
+
+    environment.systemPackages = [ pkgs.deepin.dde-daemon ];
+
+    services.dbus.packages = [ pkgs.deepin.dde-daemon ];
+
+    services.udev.packages = [ pkgs.deepin.dde-daemon ];
+
+    systemd.packages = [ pkgs.deepin.dde-daemon ];
+
+    environment.pathsToLink = [ "/lib/deepin-daemon" ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/espanso.nix b/nixpkgs/nixos/modules/services/desktops/espanso.nix
index 4ef6724dda0a..cbc48034795e 100644
--- a/nixpkgs/nixos/modules/services/desktops/espanso.nix
+++ b/nixpkgs/nixos/modules/services/desktops/espanso.nix
@@ -6,7 +6,7 @@ in {
   meta = { maintainers = with lib.maintainers; [ numkem ]; };
 
   options = {
-    services.espanso = { enable = options.mkEnableOption "Espanso"; };
+    services.espanso = { enable = options.mkEnableOption (lib.mdDoc "Espanso"); };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.md b/nixpkgs/nixos/modules/services/desktops/flatpak.md
new file mode 100644
index 000000000000..65b1554d79b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/flatpak.md
@@ -0,0 +1,39 @@
+# Flatpak {#module-services-flatpak}
+
+*Source:* {file}`modules/services/desktop/flatpak.nix`
+
+*Upstream documentation:* <https://github.com/flatpak/flatpak/wiki>
+
+Flatpak is a system for building, distributing, and running sandboxed desktop
+applications on Linux.
+
+To enable Flatpak, add the following to your {file}`configuration.nix`:
+```
+  services.flatpak.enable = true;
+```
+
+For the sandboxed apps to work correctly, desktop integration portals need to
+be installed. If you run GNOME, this will be handled automatically for you;
+in other cases, you will need to add something like the following to your
+{file}`configuration.nix`:
+```
+  xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+```
+
+Then, you will need to add a repository, for example,
+[Flathub](https://github.com/flatpak/flatpak/wiki),
+either using the following commands:
+```ShellSession
+$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+$ flatpak update
+```
+or by opening the
+[repository file](https://flathub.org/repo/flathub.flatpakrepo) in GNOME Software.
+
+Finally, you can search and install programs:
+```ShellSession
+$ flatpak search bustle
+$ flatpak install flathub org.freedesktop.Bustle
+$ flatpak run org.freedesktop.Bustle
+```
+Again, GNOME Software offers graphical interface for these tasks.
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.nix b/nixpkgs/nixos/modules/services/desktops/flatpak.nix
index 5fecc64b4f70..d99faf381e01 100644
--- a/nixpkgs/nixos/modules/services/desktops/flatpak.nix
+++ b/nixpkgs/nixos/modules/services/desktops/flatpak.nix
@@ -7,14 +7,14 @@ let
   cfg = config.services.flatpak;
 in {
   meta = {
-    doc = ./flatpak.xml;
+    doc = ./flatpak.md;
     maintainers = pkgs.flatpak.meta.maintainers;
   };
 
   ###### interface
   options = {
     services.flatpak = {
-      enable = mkEnableOption "flatpak";
+      enable = mkEnableOption (lib.mdDoc "flatpak");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.xml b/nixpkgs/nixos/modules/services/desktops/flatpak.xml
deleted file mode 100644
index 8f080b250228..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/flatpak.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-flatpak">
- <title>Flatpak</title>
- <para>
-  <emphasis>Source:</emphasis>
-  <filename>modules/services/desktop/flatpak.nix</filename>
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis>
-  <link xlink:href="https://github.com/flatpak/flatpak/wiki"/>
- </para>
- <para>
-  Flatpak is a system for building, distributing, and running sandboxed desktop
-  applications on Linux.
- </para>
- <para>
-  To enable Flatpak, add the following to your
-  <filename>configuration.nix</filename>:
-<programlisting>
-  <xref linkend="opt-services.flatpak.enable"/> = true;
-</programlisting>
- </para>
- <para>
-  For the sandboxed apps to work correctly, desktop integration portals need to
-  be installed. If you run GNOME, this will be handled automatically for you;
-  in other cases, you will need to add something like the following to your
-  <filename>configuration.nix</filename>:
-<programlisting>
-  <xref linkend="opt-xdg.portal.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
-</programlisting>
- </para>
- <para>
-  Then, you will need to add a repository, for example,
-  <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>,
-  either using the following commands:
-<screen>
-<prompt>$ </prompt>flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
-<prompt>$ </prompt>flatpak update
-</screen>
-  or by opening the
-  <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository
-  file</link> in GNOME Software.
- </para>
- <para>
-  Finally, you can search and install programs:
-<screen>
-<prompt>$ </prompt>flatpak search bustle
-<prompt>$ </prompt>flatpak install flathub org.freedesktop.Bustle
-<prompt>$ </prompt>flatpak run org.freedesktop.Bustle
-</screen>
-  Again, GNOME Software offers graphical interface for these tasks.
- </para>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/desktops/geoclue2.nix b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
index a9712b962ffd..b04f46c26a56 100644
--- a/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
@@ -200,6 +200,7 @@ in
     };
 
     systemd.services.geoclue = {
+      after = lib.optionals cfg.enableWifi [ "network-online.target" ];
       # restart geoclue service when the configuration changes
       restartTriggers = [
         config.environment.etc."geoclue/geoclue.conf".source
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/at-spi2-core.nix b/nixpkgs/nixos/modules/services/desktops/gnome/at-spi2-core.nix
index 495ea5af9879..10a2f1f9eca0 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/at-spi2-core.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/at-spi2-core.nix
@@ -51,7 +51,10 @@ with lib;
     })
 
     (mkIf (!config.services.gnome.at-spi2-core.enable) {
-      environment.variables.NO_AT_BRIDGE = "1";
+      environment.variables = {
+        NO_AT_BRIDGE = "1";
+        GTK_A11Y = "none";
+      };
     })
   ];
 }
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix b/nixpkgs/nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix
deleted file mode 100644
index 15c5bfbd8210..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-# Chrome GNOME Shell native host connector.
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-  meta = {
-    maintainers = teams.gnome.members;
-  };
-
-  # Added 2021-05-07
-  imports = [
-    (mkRenamedOptionModule
-      [ "services" "gnome3" "chrome-gnome-shell" "enable" ]
-      [ "services" "gnome" "chrome-gnome-shell" "enable" ]
-    )
-  ];
-
-  ###### interface
-  options = {
-    services.gnome.chrome-gnome-shell.enable = mkEnableOption ''
-      Chrome GNOME Shell native host connector, a DBus service
-      allowing to install GNOME Shell extensions from a web browser.
-    '';
-  };
-
-
-  ###### implementation
-  config = mkIf config.services.gnome.chrome-gnome-shell.enable {
-    environment.etc = {
-      "chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.chrome-gnome-shell}/etc/chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
-      "opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.chrome-gnome-shell}/etc/opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
-    };
-
-    environment.systemPackages = [ pkgs.chrome-gnome-shell ];
-
-    services.dbus.packages = [ pkgs.chrome-gnome-shell ];
-
-    nixpkgs.config.firefox.enableGnomeExtensions = true;
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/evolution-data-server.nix b/nixpkgs/nixos/modules/services/desktops/gnome/evolution-data-server.nix
index 65bb75c62d2c..a8db7dce8fdf 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/evolution-data-server.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/evolution-data-server.nix
@@ -27,7 +27,7 @@ with lib;
   options = {
 
     services.gnome.evolution-data-server = {
-      enable = mkEnableOption "Evolution Data Server, a collection of services for storing addressbooks and calendars.";
+      enable = mkEnableOption (lib.mdDoc "Evolution Data Server, a collection of services for storing addressbooks and calendars");
       plugins = mkOption {
         type = types.listOf types.package;
         default = [ ];
@@ -35,7 +35,7 @@ with lib;
       };
     };
     programs.evolution = {
-      enable = mkEnableOption "Evolution, a Personal information management application that provides integrated mail, calendaring and address book functionality.";
+      enable = mkEnableOption (lib.mdDoc "Evolution, a Personal information management application that provides integrated mail, calendaring and address book functionality");
       plugins = mkOption {
         type = types.listOf types.package;
         default = [ ];
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/glib-networking.nix b/nixpkgs/nixos/modules/services/desktops/gnome/glib-networking.nix
index 1039605391ab..6b54f46f0cf5 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/glib-networking.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/glib-networking.nix
@@ -24,7 +24,7 @@ with lib;
 
     services.gnome.glib-networking = {
 
-      enable = mkEnableOption "network extensions for GLib";
+      enable = mkEnableOption (lib.mdDoc "network extensions for GLib");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
new file mode 100644
index 000000000000..5d4ddce94220
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mdDoc mkEnableOption mkIf mkRenamedOptionModule teams;
+in
+
+{
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  imports = [
+    # Added 2021-05-07
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "chrome-gnome-shell" "enable" ]
+      [ "services" "gnome" "gnome-browser-connector" "enable" ]
+    )
+    # Added 2022-07-25
+    (mkRenamedOptionModule
+      [ "services" "gnome" "chrome-gnome-shell" "enable" ]
+      [ "services" "gnome" "gnome-browser-connector" "enable" ]
+    )
+  ];
+
+  options = {
+    services.gnome.gnome-browser-connector.enable = mkEnableOption (mdDoc ''
+      Native host connector for the GNOME Shell browser extension, a DBus service
+      allowing to install GNOME Shell extensions from a web browser.
+    '');
+  };
+
+  config = mkIf config.services.gnome.gnome-browser-connector.enable {
+    environment.etc = {
+      "chromium/native-messaging-hosts/org.gnome.browser_connector.json".source = "${pkgs.gnome-browser-connector}/etc/chromium/native-messaging-hosts/org.gnome.browser_connector.json";
+      "opt/chrome/native-messaging-hosts/org.gnome.browser_connector.json".source = "${pkgs.gnome-browser-connector}/etc/opt/chrome/native-messaging-hosts/org.gnome.browser_connector.json";
+      # Legacy paths.
+      "chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.gnome-browser-connector}/etc/chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
+      "opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.gnome-browser-connector}/etc/opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
+    };
+
+    environment.systemPackages = [ pkgs.gnome-browser-connector ];
+
+    services.dbus.packages = [ pkgs.gnome-browser-connector ];
+
+    nixpkgs.config.firefox.enableGnomeExtensions = true;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
index 9e9771cf5415..f24e6f1eb155 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
@@ -62,7 +62,7 @@ in
 
     services.gnome.gnome-initial-setup = {
 
-      enable = mkEnableOption "GNOME Initial Setup, a Simple, easy, and safe way to prepare a new system";
+      enable = mkEnableOption (lib.mdDoc "GNOME Initial Setup, a Simple, easy, and safe way to prepare a new system");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix
index b5573d2fc21b..0a5b67eb2722 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix
@@ -19,7 +19,7 @@ with lib;
   ###### interface
   options = {
     services.gnome.gnome-remote-desktop = {
-      enable = mkEnableOption "Remote Desktop support using Pipewire";
+      enable = mkEnableOption (lib.mdDoc "Remote Desktop support using Pipewire");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix
index 9c68c9b76e9e..ca739b06a5a5 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix
@@ -34,7 +34,7 @@ in
 
     services.gnome.gnome-settings-daemon = {
 
-      enable = mkEnableOption "GNOME Settings Daemon";
+      enable = mkEnableOption (lib.mdDoc "GNOME Settings Daemon");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-user-share.nix b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-user-share.nix
index 38256af309cc..0c88d13b343d 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/gnome-user-share.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/gnome-user-share.nix
@@ -24,7 +24,7 @@ with lib;
 
     services.gnome.gnome-user-share = {
 
-      enable = mkEnableOption "GNOME User Share, a user-level file sharing service for GNOME";
+      enable = mkEnableOption (lib.mdDoc "GNOME User Share, a user-level file sharing service for GNOME");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome/tracker.nix b/nixpkgs/nixos/modules/services/desktops/gnome/tracker.nix
index 485632712f68..e6404c84a26f 100644
--- a/nixpkgs/nixos/modules/services/desktops/gnome/tracker.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gnome/tracker.nix
@@ -40,7 +40,7 @@ in
         type = types.listOf types.package;
         default = [ ];
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           List of packages containing tracker3 subcommands.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/desktops/gvfs.nix b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
index 84cd29638723..7e15b433fcc2 100644
--- a/nixpkgs/nixos/modules/services/desktops/gvfs.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
@@ -29,7 +29,7 @@ in
 
     services.gvfs = {
 
-      enable = mkEnableOption "GVfs, a userspace virtual filesystem";
+      enable = mkEnableOption (lib.mdDoc "GVfs, a userspace virtual filesystem");
 
       # gvfs can be built with multiple configurations
       package = mkOption {
diff --git a/nixpkgs/nixos/modules/services/desktops/malcontent.nix b/nixpkgs/nixos/modules/services/desktops/malcontent.nix
index 1fbeb17e6aeb..27b4577f4c2a 100644
--- a/nixpkgs/nixos/modules/services/desktops/malcontent.nix
+++ b/nixpkgs/nixos/modules/services/desktops/malcontent.nix
@@ -12,7 +12,7 @@ with lib;
 
     services.malcontent = {
 
-      enable = mkEnableOption "Malcontent, parental control support for applications";
+      enable = mkEnableOption (lib.mdDoc "Malcontent, parental control support for applications");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/neard.nix b/nixpkgs/nixos/modules/services/desktops/neard.nix
index 9b0f8d1b3a77..9130b8d3d216 100644
--- a/nixpkgs/nixos/modules/services/desktops/neard.nix
+++ b/nixpkgs/nixos/modules/services/desktops/neard.nix
@@ -7,7 +7,7 @@ with lib;
   ###### interface
   options = {
     services.neard = {
-      enable = mkEnableOption "neard, NFC daemon";
+      enable = mkEnableOption (lib.mdDoc "neard, NFC daemon");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
deleted file mode 100644
index 9aa51b61431d..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "filter.properties": {},
-  "stream.properties": {}
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
deleted file mode 100644
index 71294a0e78a2..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "filter.properties": {},
-  "stream.properties": {}
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
deleted file mode 100644
index 128178bfa027..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "context.properties": {
-    "log.level": 0
-  },
-  "context.spa-libs": {
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    }
-  ],
-  "jack.properties": {},
-  "jack.rules": [
-    {
-      "matches": [
-        {}
-      ],
-      "actions": {
-        "update-props": {}
-      }
-    }
-  ]
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
deleted file mode 100644
index 0f1ebe5749c6..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
+++ /dev/null
@@ -1,120 +0,0 @@
-{
-  "context.properties": {
-    "link.max-buffers": 16,
-    "core.daemon": true,
-    "core.name": "pipewire-0",
-    "settings.check-quantum": true,
-    "settings.check-rate": true,
-    "vm.overrides": {
-      "default.clock.min-quantum": 1024
-    }
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "api.alsa.*": "alsa/libspa-alsa",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-profiler"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-spa-node-factory"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-access",
-      "args": {}
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-link-factory"
-    }
-  ],
-  "context.objects": [
-    {
-      "factory": "metadata",
-      "args": {
-        "metadata.name": "default"
-      }
-    },
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Dummy-Driver",
-        "node.group": "pipewire.dummy",
-        "priority.driver": 20000
-      }
-    },
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Freewheel-Driver",
-        "priority.driver": 19000,
-        "node.group": "pipewire.freewheel",
-        "node.freewheel": true
-      }
-    },
-    {
-      "factory": "adapter",
-      "args": {
-        "factory.name": "api.alsa.pcm.source",
-        "node.name": "system",
-        "node.description": "system",
-        "media.class": "Audio/Source",
-        "api.alsa.path": "hw:0",
-        "node.suspend-on-idle": true,
-        "resample.disable": true,
-        "channelmix.disable": true,
-        "adapter.auto-port-config": {
-          "mode": "dsp",
-          "monitor": false,
-          "control": false,
-          "position": "unknown"
-        }
-      }
-    },
-    {
-      "factory": "adapter",
-      "args": {
-        "factory.name": "api.alsa.pcm.sink",
-        "node.name": "system",
-        "node.description": "system",
-        "media.class": "Audio/Sink",
-        "api.alsa.path": "hw:0",
-        "node.suspend-on-idle": true,
-        "resample.disable": true,
-        "channelmix.disable": true,
-        "adapter.auto-port-config": {
-          "mode": "dsp",
-          "monitor": false,
-          "control": false,
-          "position": "unknown"
-        }
-      }
-    }
-  ],
-  "context.exec": []
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
deleted file mode 100644
index 114afbfb0ea4..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
-  "context.properties": {},
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-protocol-pulse",
-      "args": {}
-    }
-  ],
-  "context.exec": [
-    {
-      "path": "pactl",
-      "args": "load-module module-always-sink"
-    }
-  ],
-  "stream.properties": {},
-  "pulse.properties": {
-    "server.address": [
-      "unix:native"
-    ],
-    "vm.overrides": {
-      "pulse.min.quantum": "1024/48000"
-    }
-  },
-  "pulse.rules": [
-    {
-      "matches": [
-        {}
-      ],
-      "actions": {
-        "update-props": {}
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.process.binary": "teams"
-        },
-        {
-          "application.process.binary": "teams-insiders"
-        },
-        {
-          "application.process.binary": "skypeforlinux"
-        }
-      ],
-      "actions": {
-        "quirks": [
-          "force-s16-info"
-        ]
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.process.binary": "firefox"
-        }
-      ],
-      "actions": {
-        "quirks": [
-          "remove-capture-dont-move"
-        ]
-      }
-    },
-    {
-      "matches": [
-        {
-          "application.name": "~speech-dispatcher*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "pulse.min.req": "1024/48000",
-          "pulse.min.quantum": "1024/48000"
-        }
-      }
-    }
-  ]
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
deleted file mode 100644
index bf3b2d660827..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
+++ /dev/null
@@ -1,97 +0,0 @@
-{
-  "context.properties": {
-    "link.max-buffers": 16,
-    "core.daemon": true,
-    "core.name": "pipewire-0",
-    "default.clock.min-quantum": 16,
-    "vm.overrides": {
-      "default.clock.min-quantum": 1024
-    }
-  },
-  "context.spa-libs": {
-    "audio.convert.*": "audioconvert/libspa-audioconvert",
-    "avb.*": "avb/libspa-avb",
-    "api.alsa.*": "alsa/libspa-alsa",
-    "api.v4l2.*": "v4l2/libspa-v4l2",
-    "api.libcamera.*": "libcamera/libspa-libcamera",
-    "api.bluez5.*": "bluez5/libspa-bluez5",
-    "api.vulkan.*": "vulkan/libspa-vulkan",
-    "api.jack.*": "jack/libspa-jack",
-    "support.*": "support/libspa-support"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rt",
-      "args": {
-        "nice.level": -11
-      },
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-profiler"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-spa-device-factory"
-    },
-    {
-      "name": "libpipewire-module-spa-node-factory"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-portal",
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-access",
-      "args": {}
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-link-factory"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "context.objects": [
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Dummy-Driver",
-        "node.group": "pipewire.dummy",
-        "priority.driver": 20000
-      }
-    },
-    {
-      "factory": "spa-node-factory",
-      "args": {
-        "factory.name": "support.node.driver",
-        "node.name": "Freewheel-Driver",
-        "priority.driver": 19000,
-        "node.group": "pipewire.freewheel",
-        "node.freewheel": true
-      }
-    }
-  ],
-  "context.exec": []
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
deleted file mode 100644
index 53fc9cc96343..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "properties": {},
-  "rules": [
-    {
-      "matches": [
-        {
-          "device.name": "~alsa_card.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "api.alsa.use-acp": true,
-          "api.acp.auto-profile": false,
-          "api.acp.auto-port": false
-        }
-      }
-    },
-    {
-      "matches": [
-        {
-          "node.name": "~alsa_input.*"
-        },
-        {
-          "node.name": "~alsa_output.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
deleted file mode 100644
index 6d1c23e82569..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
-  "properties": {},
-  "rules": [
-    {
-      "matches": [
-        {
-          "device.name": "~bluez_card.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "bluez5.auto-connect": [
-            "hfp_hf",
-            "hsp_hs",
-            "a2dp_sink"
-          ]
-        }
-      }
-    },
-    {
-      "matches": [
-        {
-          "node.name": "~bluez_input.*"
-        },
-        {
-          "node.name": "~bluez_output.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
deleted file mode 100644
index 4b4e302af387..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
-  "context.properties": {},
-  "context.spa-libs": {
-    "api.bluez5.*": "bluez5/libspa-bluez5",
-    "api.alsa.*": "alsa/libspa-alsa",
-    "api.v4l2.*": "v4l2/libspa-v4l2",
-    "api.libcamera.*": "libcamera/libspa-libcamera"
-  },
-  "context.modules": [
-    {
-      "name": "libpipewire-module-rtkit",
-      "args": {},
-      "flags": [
-        "ifexists",
-        "nofail"
-      ]
-    },
-    {
-      "name": "libpipewire-module-protocol-native"
-    },
-    {
-      "name": "libpipewire-module-client-node"
-    },
-    {
-      "name": "libpipewire-module-client-device"
-    },
-    {
-      "name": "libpipewire-module-adapter"
-    },
-    {
-      "name": "libpipewire-module-metadata"
-    },
-    {
-      "name": "libpipewire-module-session-manager"
-    }
-  ],
-  "session.modules": {
-    "default": [
-      "flatpak",
-      "portal",
-      "v4l2",
-      "suspend-node",
-      "policy-node"
-    ],
-    "with-audio": [
-      "metadata",
-      "default-nodes",
-      "default-profile",
-      "default-routes",
-      "alsa-seq",
-      "alsa-monitor"
-    ],
-    "with-alsa": [
-      "with-audio"
-    ],
-    "with-jack": [
-      "with-audio"
-    ],
-    "with-pulseaudio": [
-      "with-audio",
-      "bluez5",
-      "bluez5-autoswitch",
-      "logind",
-      "restore-stream",
-      "streams-follow-default"
-    ]
-  }
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
deleted file mode 100644
index b08cba1b604b..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "properties": {},
-  "rules": [
-    {
-      "matches": [
-        {
-          "device.name": "~v4l2_device.*"
-        }
-      ],
-      "actions": {
-        "update-props": {}
-      }
-    },
-    {
-      "matches": [
-        {
-          "node.name": "~v4l2_input.*"
-        },
-        {
-          "node.name": "~v4l2_output.*"
-        }
-      ],
-      "actions": {
-        "update-props": {
-          "node.pause-on-idle": false
-        }
-      }
-    }
-  ]
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
deleted file mode 100644
index 203139294c6b..000000000000
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ /dev/null
@@ -1,141 +0,0 @@
-# pipewire example session manager.
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  json = pkgs.formats.json {};
-  cfg = config.services.pipewire.media-session;
-  enable32BitAlsaPlugins = cfg.alsa.support32Bit
-                           && pkgs.stdenv.isx86_64
-                           && pkgs.pkgsi686Linux.pipewire != null;
-
-  # 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 = lib.importJSON ./media-session/alsa-monitor.conf.json;
-    bluez-monitor = lib.importJSON ./media-session/bluez-monitor.conf.json;
-    media-session = lib.importJSON ./media-session/media-session.conf.json;
-    v4l2-monitor = lib.importJSON ./media-session/v4l2-monitor.conf.json;
-  };
-
-  configs = {
-    alsa-monitor = recursiveUpdate defaults.alsa-monitor cfg.config.alsa-monitor;
-    bluez-monitor = recursiveUpdate defaults.bluez-monitor cfg.config.bluez-monitor;
-    media-session = recursiveUpdate defaults.media-session cfg.config.media-session;
-    v4l2-monitor = recursiveUpdate defaults.v4l2-monitor cfg.config.v4l2-monitor;
-  };
-in {
-
-  meta = {
-    maintainers = teams.freedesktop.members;
-    # uses attributes of the linked package
-    buildDocsInSandbox = false;
-  };
-
-  ###### interface
-  options = {
-    services.pipewire.media-session = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether to enable the deprecated example Pipewire session manager";
-      };
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.pipewire-media-session;
-        defaultText = literalExpression "pkgs.pipewire-media-session";
-        description = lib.mdDoc ''
-          The pipewire-media-session derivation to use.
-        '';
-      };
-
-      config = {
-        media-session = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the media session core. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf
-          '';
-          default = defaults.media-session;
-        };
-
-        alsa-monitor = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the alsa monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf
-          '';
-          default = defaults.alsa-monitor;
-        };
-
-        bluez-monitor = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the bluez5 monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf
-          '';
-          default = defaults.bluez-monitor;
-        };
-
-        v4l2-monitor = mkOption {
-          type = json.type;
-          description = lib.mdDoc ''
-            Configuration for the V4L2 monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf
-          '';
-          default = defaults.v4l2-monitor;
-        };
-      };
-    };
-  };
-
-  ###### implementation
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ cfg.package ];
-    systemd.packages = [ cfg.package ];
-
-    # Enable either system or user units.
-    systemd.services.pipewire-media-session.enable = config.services.pipewire.systemWide;
-    systemd.user.services.pipewire-media-session.enable = !config.services.pipewire.systemWide;
-
-    systemd.services.pipewire-media-session.wantedBy = [ "pipewire.service" ];
-    systemd.user.services.pipewire-media-session.wantedBy = [ "pipewire.service" ];
-
-    environment.etc."pipewire/media-session.d/media-session.conf" = {
-      source = json.generate "media-session.conf" configs.media-session;
-    };
-    environment.etc."pipewire/media-session.d/v4l2-monitor.conf" = {
-      source = json.generate "v4l2-monitor.conf" configs.v4l2-monitor;
-    };
-
-    environment.etc."pipewire/media-session.d/with-audio" =
-      mkIf config.services.pipewire.audio.enable {
-        text = "";
-      };
-
-    environment.etc."pipewire/media-session.d/with-alsa" =
-      mkIf config.services.pipewire.alsa.enable {
-        text = "";
-      };
-    environment.etc."pipewire/media-session.d/alsa-monitor.conf" =
-      mkIf config.services.pipewire.alsa.enable {
-        source = json.generate "alsa-monitor.conf" configs.alsa-monitor;
-      };
-
-    environment.etc."pipewire/media-session.d/with-pulseaudio" =
-      mkIf config.services.pipewire.pulse.enable {
-        text = "";
-      };
-    environment.etc."pipewire/media-session.d/bluez-monitor.conf" =
-      mkIf config.services.pipewire.pulse.enable {
-        source = json.generate "bluez-monitor.conf" configs.bluez-monitor;
-      };
-
-    environment.etc."pipewire/media-session.d/with-jack" =
-      mkIf config.services.pipewire.jack.enable {
-        text = "";
-      };
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix
index ed64406ab6a8..ae695baf42c6 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -4,7 +4,6 @@
 with lib;
 
 let
-  json = pkgs.formats.json {};
   cfg = config.services.pipewire;
   enable32BitAlsaPlugins = cfg.alsa.support32Bit
                            && pkgs.stdenv.isx86_64
@@ -18,39 +17,13 @@ let
     mkdir -p "$out/lib"
     ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire"
   '';
-
-  # 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 = lib.importJSON ./daemon/client.conf.json;
-    client-rt = lib.importJSON ./daemon/client-rt.conf.json;
-    jack = lib.importJSON ./daemon/jack.conf.json;
-    minimal = lib.importJSON ./daemon/minimal.conf.json;
-    pipewire = lib.importJSON ./daemon/pipewire.conf.json;
-    pipewire-pulse = lib.importJSON ./daemon/pipewire-pulse.conf.json;
-  };
-
-  useSessionManager = cfg.wireplumber.enable || cfg.media-session.enable;
-
-  configs = {
-    client = recursiveUpdate defaults.client cfg.config.client;
-    client-rt = recursiveUpdate defaults.client-rt cfg.config.client-rt;
-    jack = recursiveUpdate defaults.jack cfg.config.jack;
-    pipewire = recursiveUpdate (if useSessionManager then defaults.pipewire else defaults.minimal) cfg.config.pipewire;
-    pipewire-pulse = recursiveUpdate defaults.pipewire-pulse cfg.config.pipewire-pulse;
-  };
 in {
-
-  meta = {
-    maintainers = teams.freedesktop.members;
-    # uses attributes of the linked package
-    buildDocsInSandbox = false;
-  };
+  meta.maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ];
 
   ###### interface
   options = {
     services.pipewire = {
-      enable = mkEnableOption "pipewire service";
+      enable = mkEnableOption (lib.mdDoc "pipewire service");
 
       package = mkOption {
         type = types.package;
@@ -69,53 +42,6 @@ in {
         '';
       };
 
-      config = {
-        client = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for pipewire clients. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client.conf.in
-          '';
-        };
-
-        client-rt = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for realtime pipewire clients. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client-rt.conf.in
-          '';
-        };
-
-        jack = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for the pipewire daemon's jack module. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/jack.conf.in
-          '';
-        };
-
-        pipewire = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for the pipewire daemon. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire.conf.in
-          '';
-        };
-
-        pipewire-pulse = mkOption {
-          type = json.type;
-          default = {};
-          description = lib.mdDoc ''
-            Configuration for the pipewire-pulse daemon. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire-pulse.conf.in
-          '';
-        };
-      };
-
       audio = {
         enable = lib.mkOption {
           type = lib.types.bool;
@@ -127,16 +53,16 @@ in {
       };
 
       alsa = {
-        enable = mkEnableOption "ALSA support";
-        support32Bit = mkEnableOption "32-bit ALSA support on 64-bit systems";
+        enable = mkEnableOption (lib.mdDoc "ALSA support");
+        support32Bit = mkEnableOption (lib.mdDoc "32-bit ALSA support on 64-bit systems");
       };
 
       jack = {
-        enable = mkEnableOption "JACK audio emulation";
+        enable = mkEnableOption (lib.mdDoc "JACK audio emulation");
       };
 
       pulse = {
-        enable = mkEnableOption "PulseAudio server emulation";
+        enable = mkEnableOption (lib.mdDoc "PulseAudio server emulation");
       };
 
       systemWide = lib.mkOption {
@@ -153,10 +79,20 @@ in {
           https://github.com/PipeWire/pipewire/blob/master/NEWS
         '';
       };
-
     };
   };
 
+  imports = [
+    (lib.mkRemovedOptionModule ["services" "pipewire" "config"] ''
+      Overriding default Pipewire configuration through NixOS options never worked correctly and is no longer supported.
+      Please create drop-in files in /etc/pipewire/pipewire.conf.d/ to make the desired setting changes instead.
+    '')
+
+    (lib.mkRemovedOptionModule ["services" "pipewire" "media-session"] ''
+      pipewire-media-session is no longer supported upstream and has been removed.
+      Please switch to `services.pipewire.wireplumber` instead.
+    '')
+  ];
 
   ###### implementation
   config = mkIf cfg.enable {
@@ -222,22 +158,6 @@ in {
       source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
     };
 
-    environment.etc."pipewire/client.conf" = {
-      source = json.generate "client.conf" configs.client;
-    };
-    environment.etc."pipewire/client-rt.conf" = {
-      source = json.generate "client-rt.conf" configs.client-rt;
-    };
-    environment.etc."pipewire/jack.conf" = {
-      source = json.generate "jack.conf" configs.jack;
-    };
-    environment.etc."pipewire/pipewire.conf" = {
-      source = json.generate "pipewire.conf" configs.pipewire;
-    };
-    environment.etc."pipewire/pipewire-pulse.conf" = mkIf cfg.pulse.enable {
-      source = json.generate "pipewire-pulse.conf" configs.pipewire-pulse;
-    };
-
     environment.sessionVariables.LD_LIBRARY_PATH =
       lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
 
@@ -256,12 +176,5 @@ in {
       };
       groups.pipewire.gid = config.ids.gids.pipewire;
     };
-
-    # https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/464#note_723554
-    systemd.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1";
-    systemd.user.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1";
-
-    # pipewire-pulse default config expects pactl to be in PATH
-    systemd.user.services.pipewire-pulse.path = lib.mkIf cfg.pulse.enable [ pkgs.pulseaudio ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 32490773b5e9..95a7ece26c5d 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -29,8 +29,8 @@ in
   config = lib.mkIf cfg.enable {
     assertions = [
       {
-        assertion = !config.services.pipewire.media-session.enable;
-        message = "WirePlumber and pipewire-media-session can't be enabled at the same time.";
+        assertion = !config.hardware.bluetooth.hsphfpd.enable;
+        message = "Using Wireplumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false";
       }
     ];
 
diff --git a/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix b/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
index 55f27b0e6534..caebfabf146c 100644
--- a/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
+++ b/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
@@ -10,7 +10,7 @@ with lib;
 
     services.system-config-printer = {
 
-      enable = mkEnableOption "system-config-printer, a service for CUPS administration used by printing interfaces";
+      enable = mkEnableOption (lib.mdDoc "system-config-printer, a service for CUPS administration used by printing interfaces");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/system76-scheduler.nix b/nixpkgs/nixos/modules/services/desktops/system76-scheduler.nix
new file mode 100644
index 000000000000..267b528cc5dd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/system76-scheduler.nix
@@ -0,0 +1,296 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.system76-scheduler;
+
+  inherit (builtins) concatStringsSep map toString attrNames;
+  inherit (lib) boolToString types mkOption literalExpression mdDoc optional mkIf mkMerge;
+  inherit (types) nullOr listOf bool int ints float str enum;
+
+  withDefaults = optionSpecs: defaults:
+    lib.genAttrs (attrNames optionSpecs) (name:
+      mkOption (optionSpecs.${name} // {
+        default = optionSpecs.${name}.default or defaults.${name} or null;
+      }));
+
+  latencyProfile = withDefaults {
+    latency = {
+      type = int;
+      description = mdDoc "`sched_latency_ns`.";
+    };
+    nr-latency = {
+      type = int;
+      description = mdDoc "`sched_nr_latency`.";
+    };
+    wakeup-granularity = {
+      type = float;
+      description = mdDoc "`sched_wakeup_granularity_ns`.";
+    };
+    bandwidth-size = {
+      type = int;
+      description = mdDoc "`sched_cfs_bandwidth_slice_us`.";
+    };
+    preempt = {
+      type = enum [ "none" "voluntary" "full" ];
+      description = mdDoc "Preemption mode.";
+    };
+  };
+  schedulerProfile = withDefaults {
+    nice = {
+      type = nullOr (ints.between (-20) 19);
+      description = mdDoc "Niceness.";
+    };
+    class = {
+      type = nullOr (enum [ "idle" "batch" "other" "rr" "fifo" ]);
+      example = literalExpression "\"batch\"";
+      description = mdDoc "CPU scheduler class.";
+    };
+    prio = {
+      type = nullOr (ints.between 1 99);
+      example = literalExpression "49";
+      description = mdDoc "CPU scheduler priority.";
+    };
+    ioClass = {
+      type = nullOr (enum [ "idle" "best-effort" "realtime" ]);
+      example = literalExpression "\"best-effort\"";
+      description = mdDoc "IO scheduler class.";
+    };
+    ioPrio = {
+      type = nullOr (ints.between 0 7);
+      example = literalExpression "4";
+      description = mdDoc "IO scheduler priority.";
+    };
+    matchers = {
+      type = nullOr (listOf str);
+      default = [];
+      example = literalExpression ''
+        [
+          "include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
+          "emacs"
+        ]
+      '';
+      description = mdDoc "Process matchers.";
+    };
+  };
+
+  cfsProfileToString = name: let
+    p = cfg.settings.cfsProfiles.${name};
+  in
+    "${name} latency=${toString p.latency} nr-latency=${toString p.nr-latency} wakeup-granularity=${toString p.wakeup-granularity} bandwidth-size=${toString p.bandwidth-size} preempt=\"${p.preempt}\"";
+
+  prioToString = class: prio: if prio == null then "\"${class}\"" else "(${class})${toString prio}";
+
+  schedulerProfileToString = name: a: indent:
+    concatStringsSep " "
+      (["${indent}${name}"]
+       ++ (optional (a.nice != null) "nice=${toString a.nice}")
+       ++ (optional (a.class != null) "sched=${prioToString a.class a.prio}")
+       ++ (optional (a.ioClass != null) "io=${prioToString a.ioClass a.ioPrio}")
+       ++ (optional ((builtins.length a.matchers) != 0) ("{\n${concatStringsSep "\n" (map (m: "  ${indent}${m}") a.matchers)}\n${indent}}")));
+
+in {
+  options = {
+    services.system76-scheduler = {
+      enable = lib.mkEnableOption (lib.mdDoc "system76-scheduler");
+
+      package = mkOption {
+        type = types.package;
+        default = config.boot.kernelPackages.system76-scheduler;
+        defaultText = literalExpression "config.boot.kernelPackages.system76-scheduler";
+        description = mdDoc "Which System76-Scheduler package to use.";
+      };
+
+      useStockConfig = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Use the (reasonable and featureful) stock configuration.
+
+          When this option is `true`, `services.system76-scheduler.settings`
+          are ignored.
+        '';
+      };
+
+      settings = {
+        cfsProfiles = {
+          enable = mkOption {
+            type = bool;
+            default = true;
+            description = mdDoc "Tweak CFS latency parameters when going on/off battery";
+          };
+
+          default = latencyProfile {
+            latency = 6;
+            nr-latency = 8;
+            wakeup-granularity = 1.0;
+            bandwidth-size = 5;
+            preempt = "voluntary";
+          };
+          responsive = latencyProfile {
+            latency = 4;
+            nr-latency = 10;
+            wakeup-granularity = 0.5;
+            bandwidth-size = 3;
+            preempt = "full";
+          };
+        };
+
+        processScheduler = {
+          enable = mkOption {
+            type = bool;
+            default = true;
+            description = mdDoc "Tweak scheduling of individual processes in real time.";
+          };
+
+          useExecsnoop = mkOption {
+            type = bool;
+            default = true;
+            description = mdDoc "Use execsnoop (otherwise poll the precess list periodically).";
+          };
+
+          refreshInterval = mkOption {
+            type = int;
+            default = 60;
+            description = mdDoc "Process list poll interval, in seconds";
+          };
+
+          foregroundBoost = {
+            enable = mkOption {
+              type = bool;
+              default = true;
+              description = mdDoc ''
+                Boost foreground process priorities.
+
+                (And de-boost background ones).  Note that this option needs cooperation
+                from the desktop environment to work.  On Gnome the client side is
+                implemented by the "System76 Scheduler" shell extension.
+              '';
+            };
+            foreground = schedulerProfile {
+              nice = 0;
+              ioClass = "best-effort";
+              ioPrio = 0;
+            };
+            background = schedulerProfile {
+              nice = 6;
+              ioClass = "idle";
+            };
+          };
+
+          pipewireBoost = {
+            enable = mkOption {
+              type = bool;
+              default = true;
+              description = mdDoc "Boost Pipewire client priorities.";
+            };
+            profile = schedulerProfile {
+              nice = -6;
+              ioClass = "best-effort";
+              ioPrio = 0;
+            };
+          };
+        };
+      };
+
+      assignments = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = schedulerProfile { };
+        });
+        default = {};
+        example = literalExpression ''
+          {
+            nix-builds = {
+              nice = 15;
+              class = "batch";
+              ioClass = "idle";
+              matchers = [
+                "nix-daemon"
+              ];
+            };
+          }
+        '';
+        description = mdDoc "Process profile assignments.";
+      };
+
+      exceptions = mkOption {
+        type = types.listOf str;
+        default = [];
+        example = literalExpression ''
+          [
+            "include descends=\"schedtool\""
+            "schedtool"
+          ]
+        '';
+        description = mdDoc "Processes that are left alone.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.dbus.packages = [ cfg.package ];
+
+    systemd.services.system76-scheduler = {
+      description = "Manage process priorities and CFS scheduler latencies for improved responsiveness on the desktop";
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        # execsnoop needs those to extract kernel headers:
+        pkgs.kmod
+        pkgs.gnutar
+        pkgs.xz
+      ];
+      serviceConfig = {
+        Type = "dbus";
+        BusName= "com.system76.Scheduler";
+        ExecStart = "${cfg.package}/bin/system76-scheduler daemon";
+        ExecReload = "${cfg.package}/bin/system76-scheduler daemon reload";
+      };
+    };
+
+    environment.etc = mkMerge [
+      (mkIf cfg.useStockConfig {
+        # No custom settings: just use stock configuration with a fix for Pipewire
+        "system76-scheduler/config.kdl".source = "${cfg.package}/data/config.kdl";
+        "system76-scheduler/process-scheduler/00-dist.kdl".source = "${cfg.package}/data/pop_os.kdl";
+        "system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source = ../../../../pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl;
+      })
+
+      (let
+        settings = cfg.settings;
+        cfsp = settings.cfsProfiles;
+        ps = settings.processScheduler;
+      in mkIf (!cfg.useStockConfig) {
+        "system76-scheduler/config.kdl".text = ''
+          version "2.0"
+          autogroup-enabled false
+          cfs-profiles enable=${boolToString cfsp.enable} {
+            ${cfsProfileToString "default"}
+            ${cfsProfileToString "responsive"}
+          }
+          process-scheduler enable=${boolToString ps.enable} {
+            execsnoop ${boolToString ps.useExecsnoop}
+            refresh-rate ${toString ps.refreshInterval}
+            assignments {
+              ${if ps.foregroundBoost.enable then (schedulerProfileToString "foreground" ps.foregroundBoost.foreground "    ") else ""}
+              ${if ps.foregroundBoost.enable then (schedulerProfileToString "background" ps.foregroundBoost.background "    ") else ""}
+              ${if ps.pipewireBoost.enable then (schedulerProfileToString "pipewire" ps.pipewireBoost.profile "    ") else ""}
+            }
+          }
+        '';
+      })
+
+      {
+        "system76-scheduler/process-scheduler/02-config.kdl".text =
+          "exceptions {\n${concatStringsSep "\n" (map (e: "  ${e}") cfg.exceptions)}\n}\n"
+          + "assignments {\n"
+          + (concatStringsSep "\n" (map (name: schedulerProfileToString name cfg.assignments.${name} "  ")
+            (attrNames cfg.assignments)))
+          + "\n}\n";
+      }
+    ];
+  };
+
+  meta = {
+    maintainers = [ lib.maintainers.cmm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/tumbler.nix b/nixpkgs/nixos/modules/services/desktops/tumbler.nix
index f5341df2f7a4..203071ec660d 100644
--- a/nixpkgs/nixos/modules/services/desktops/tumbler.nix
+++ b/nixpkgs/nixos/modules/services/desktops/tumbler.nix
@@ -28,7 +28,7 @@ in
 
     services.tumbler = {
 
-      enable = mkEnableOption "Tumbler, A D-Bus thumbnailer service";
+      enable = mkEnableOption (lib.mdDoc "Tumbler, A D-Bus thumbnailer service");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix b/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
index 297fd1d3ff2f..0eb2a4c9c371 100644
--- a/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
+++ b/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
@@ -14,7 +14,7 @@ with lib;
 
   options = {
     services.zeitgeist = {
-      enable = mkEnableOption "zeitgeist";
+      enable = mkEnableOption (lib.mdDoc "zeitgeist");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/development/blackfire.md b/nixpkgs/nixos/modules/services/development/blackfire.md
new file mode 100644
index 000000000000..e2e7e4780c79
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/blackfire.md
@@ -0,0 +1,39 @@
+# Blackfire profiler {#module-services-blackfire}
+
+*Source:* {file}`modules/services/development/blackfire.nix`
+
+*Upstream documentation:* <https://blackfire.io/docs/introduction>
+
+[Blackfire](https://blackfire.io) is a proprietary tool for profiling applications. There are several languages supported by the product but currently only PHP support is packaged in Nixpkgs. The back-end consists of a module that is loaded into the language runtime (called *probe*) and a service (*agent*) that the probe connects to and that sends the profiles to the server.
+
+To use it, you will need to enable the agent and the probe on your server. The exact method will depend on the way you use PHP but here is an example of NixOS configuration for PHP-FPM:
+```
+let
+  php = pkgs.php.withExtensions ({ enabled, all }: enabled ++ (with all; [
+    blackfire
+  ]));
+in {
+  # Enable the probe extension for PHP-FPM.
+  services.phpfpm = {
+    phpPackage = php;
+  };
+
+  # Enable and configure the agent.
+  services.blackfire-agent = {
+    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/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" ];
+}
+```
+
+On your developer machine, you will also want to install [the client](https://blackfire.io/docs/up-and-running/installation#install-a-profiling-client) (see `blackfire` package) or the browser extension to actually trigger the profiling.
diff --git a/nixpkgs/nixos/modules/services/development/blackfire.nix b/nixpkgs/nixos/modules/services/development/blackfire.nix
index 6b71e59d4bd5..3c98d7a281c6 100644
--- a/nixpkgs/nixos/modules/services/development/blackfire.nix
+++ b/nixpkgs/nixos/modules/services/development/blackfire.nix
@@ -11,12 +11,12 @@ let
 in {
   meta = {
     maintainers = pkgs.blackfire.meta.maintainers;
-    doc = ./blackfire.xml;
+    doc = ./blackfire.md;
   };
 
   options = {
     services.blackfire-agent = {
-      enable = lib.mkEnableOption "Blackfire profiler agent";
+      enable = lib.mkEnableOption (lib.mdDoc "Blackfire profiler agent");
       settings = lib.mkOption {
         description = lib.mdDoc ''
           See https://blackfire.io/docs/up-and-running/configuration/agent
diff --git a/nixpkgs/nixos/modules/services/development/blackfire.xml b/nixpkgs/nixos/modules/services/development/blackfire.xml
deleted file mode 100644
index cecd249dda48..000000000000
--- a/nixpkgs/nixos/modules/services/development/blackfire.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="module-services-blackfire">
- <title>Blackfire profiler</title>
- <para>
-  <emphasis>Source:</emphasis>
-  <filename>modules/services/development/blackfire.nix</filename>
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis>
-  <link xlink:href="https://blackfire.io/docs/introduction"/>
- </para>
- <para>
-  <link xlink:href="https://blackfire.io">Blackfire</link> is a proprietary tool for profiling applications. There are several languages supported by the product but currently only PHP support is packaged in Nixpkgs. The back-end consists of a module that is loaded into the language runtime (called <firstterm>probe</firstterm>) and a service (<firstterm>agent</firstterm>) that the probe connects to and that sends the profiles to the server.
- </para>
- <para>
-  To use it, you will need to enable the agent and the probe on your server. The exact method will depend on the way you use PHP but here is an example of NixOS configuration for PHP-FPM:
-<programlisting>let
-  php = pkgs.php.withExtensions ({ enabled, all }: enabled ++ (with all; [
-    blackfire
-  ]));
-in {
-  # Enable the probe extension for PHP-FPM.
-  services.phpfpm = {
-    phpPackage = php;
-  };
-
-  # Enable and configure the agent.
-  services.blackfire-agent = {
-    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/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>
- </para>
- <para>
-  On your developer machine, you will also want to install <link xlink:href="https://blackfire.io/docs/up-and-running/installation#install-a-profiling-client">the client</link> (see <package>blackfire</package> package) or the browser extension to actually trigger the profiling.
- </para>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/development/distccd.nix b/nixpkgs/nixos/modules/services/development/distccd.nix
index 7a8e780c3eb6..a3c909eb1959 100644
--- a/nixpkgs/nixos/modules/services/development/distccd.nix
+++ b/nixpkgs/nixos/modules/services/development/distccd.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.distccd = {
-      enable = mkEnableOption "distccd";
+      enable = mkEnableOption (lib.mdDoc "distccd");
 
       allowedClients = mkOption {
         type = types.listOf types.str;
@@ -84,7 +84,7 @@ in
       };
 
       stats = {
-        enable = mkEnableOption "statistics reporting via HTTP server";
+        enable = mkEnableOption (lib.mdDoc "statistics reporting via HTTP server");
         port = mkOption {
           type = types.port;
           default = 3633;
diff --git a/nixpkgs/nixos/modules/services/development/gemstash.nix b/nixpkgs/nixos/modules/services/development/gemstash.nix
new file mode 100644
index 000000000000..eb7ccb98bde8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/gemstash.nix
@@ -0,0 +1,103 @@
+{ lib, pkgs, config, ... }:
+with lib;
+
+let
+  settingsFormat = pkgs.formats.yaml { };
+
+  # gemstash uses a yaml config where the keys are ruby symbols,
+  # which means they start with ':'. This would be annoying to use
+  # on the nix side, so we rewrite plain names instead.
+  prefixColon = s: listToAttrs (map
+    (attrName: {
+      name = ":${attrName}";
+      value =
+        if isAttrs s.${attrName}
+        then prefixColon s."${attrName}"
+        else s."${attrName}";
+    })
+    (attrNames s));
+
+  # parse the port number out of the tcp://ip:port bind setting string
+  parseBindPort = bind: strings.toInt (last (strings.splitString ":" bind));
+
+  cfg = config.services.gemstash;
+in
+{
+  options.services.gemstash = {
+    enable = mkEnableOption (lib.mdDoc "gemstash service");
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to open the firewall for the port in {option}`services.gemstash.bind`.
+      '';
+    };
+
+    settings = mkOption {
+      default = {};
+      description = lib.mdDoc ''
+        Configuration for Gemstash. The details can be found at in
+        [gemstash documentation](https://github.com/rubygems/gemstash/blob/master/man/gemstash-configuration.5.md).
+        Each key set here is automatically prefixed with ":" to match the gemstash expectations.
+      '';
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          base_path = mkOption {
+            type = types.path;
+            default = "/var/lib/gemstash";
+            description = lib.mdDoc "Path to store the gem files and the sqlite database. If left unchanged, the directory will be created.";
+          };
+          bind = mkOption {
+            type = types.str;
+            default = "tcp://0.0.0.0:9292";
+            description = lib.mdDoc "Host and port combination for the server to listen on.";
+          };
+          db_adapter = mkOption {
+            type = types.nullOr (types.enum [ "sqlite3" "postgres" "mysql" "mysql2" ]);
+            default = null;
+            description = lib.mdDoc "Which database type to use. For choices other than sqlite3, the dbUrl has to be specified as well.";
+          };
+          db_url = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = lib.mdDoc "The database to connect to when using postgres, mysql, or mysql2.";
+          };
+        };
+      };
+    };
+  };
+
+  config =
+    mkIf cfg.enable {
+      users = {
+        users.gemstash = {
+          group = "gemstash";
+          isSystemUser = true;
+        };
+        groups.gemstash = { };
+      };
+
+      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ (parseBindPort cfg.settings.bind) ];
+
+      systemd.services.gemstash = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig = mkMerge [
+          {
+            ExecStart = "${pkgs.gemstash}/bin/gemstash start --no-daemonize --config-file ${settingsFormat.generate "gemstash.yaml" (prefixColon cfg.settings)}";
+            NoNewPrivileges = true;
+            User = "gemstash";
+            Group = "gemstash";
+            PrivateTmp = true;
+            RestrictSUIDSGID = true;
+            LockPersonality = true;
+          }
+          (mkIf (cfg.settings.base_path == "/var/lib/gemstash") {
+            StateDirectory = "gemstash";
+          })
+        ];
+      };
+    };
+}
diff --git a/nixpkgs/nixos/modules/services/development/hoogle.nix b/nixpkgs/nixos/modules/services/development/hoogle.nix
index 399ffccabfc6..88dd01fd8aab 100644
--- a/nixpkgs/nixos/modules/services/development/hoogle.nix
+++ b/nixpkgs/nixos/modules/services/development/hoogle.nix
@@ -14,7 +14,7 @@ let
 in {
 
   options.services.hoogle = {
-    enable = mkEnableOption "Haskell documentation server";
+    enable = mkEnableOption (lib.mdDoc "Haskell documentation server");
 
     port = mkOption {
       type = types.port;
@@ -29,11 +29,11 @@ in {
       default = hp: [];
       defaultText = literalExpression "hp: []";
       example = literalExpression "hp: with hp; [ text lens ]";
-      description = ''
+      description = lib.mdDoc ''
         The Haskell packages to generate documentation for.
 
         The option value is a function that takes the package set specified in
-        the <varname>haskellPackages</varname> option as its sole parameter and
+        the {var}`haskellPackages` option as its sole parameter and
         returns a list of packages.
       '';
     };
diff --git a/nixpkgs/nixos/modules/services/development/jupyter/default.nix b/nixpkgs/nixos/modules/services/development/jupyter/default.nix
index b77f5adbd5e9..9f7910844468 100644
--- a/nixpkgs/nixos/modules/services/development/jupyter/default.nix
+++ b/nixpkgs/nixos/modules/services/development/jupyter/default.nix
@@ -24,7 +24,7 @@ in {
   meta.maintainers = with maintainers; [ aborsu ];
 
   options.services.jupyter = {
-    enable = mkEnableOption "Jupyter development server";
+    enable = mkEnableOption (lib.mdDoc "Jupyter development server");
 
     ip = mkOption {
       type = types.str;
@@ -57,7 +57,7 @@ in {
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8888;
       description = lib.mdDoc ''
         Port number Jupyter will be listening on.
@@ -119,7 +119,7 @@ in {
 
     kernels = mkOption {
       type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
-        inherit lib;
+        inherit lib pkgs;
       })));
 
       default = null;
@@ -149,13 +149,14 @@ in {
           };
         }
       '';
-      description = "Declarative kernel config
+      description = lib.mdDoc ''
+        Declarative kernel config.
 
-      Kernels can be declared in any language that supports and has the required
-      dependencies to communicate with a jupyter server.
-      In python's case, it means that ipykernel package must always be included in
-      the list of packages of the targeted environment.
-      ";
+        Kernels can be declared in any language that supports and has the required
+        dependencies to communicate with a jupyter server.
+        In python's case, it means that ipykernel package must always be included in
+        the list of packages of the targeted environment.
+      '';
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix b/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
index 42af47aeb3c8..6e406152de47 100644
--- a/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
+++ b/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
@@ -1,9 +1,11 @@
 # Options that can be used for creating a jupyter kernel.
-{lib }:
+{ lib, pkgs }:
 
 with lib;
 
 {
+  freeformType = (pkgs.formats.json { }).type;
+
   options = {
 
     displayName = mkOption {
@@ -40,6 +42,15 @@ with lib;
       '';
     };
 
+    env = mkOption {
+      type = types.attrsOf types.str;
+      default = { };
+      example = { OMP_NUM_THREADS = "1"; };
+      description = lib.mdDoc ''
+        Environment variables to set for the kernel.
+      '';
+    };
+
     logo32 = mkOption {
       type = types.nullOr types.path;
       default = null;
diff --git a/nixpkgs/nixos/modules/services/development/jupyterhub/default.nix b/nixpkgs/nixos/modules/services/development/jupyterhub/default.nix
index bd8a5f0bd258..cebc35a50476 100644
--- a/nixpkgs/nixos/modules/services/development/jupyterhub/default.nix
+++ b/nixpkgs/nixos/modules/services/development/jupyterhub/default.nix
@@ -30,7 +30,7 @@ in {
   meta.maintainers = with maintainers; [ costrouc ];
 
   options.services.jupyterhub = {
-    enable = mkEnableOption "Jupyterhub development server";
+    enable = mkEnableOption (lib.mdDoc "Jupyterhub development server");
 
     authentication = mkOption {
       type = types.str;
@@ -119,7 +119,7 @@ in {
 
     kernels = mkOption {
       type = types.nullOr (types.attrsOf(types.submodule (import ../jupyter/kernel-options.nix {
-        inherit lib;
+        inherit lib pkgs;
       })));
 
       default = null;
diff --git a/nixpkgs/nixos/modules/services/development/lorri.nix b/nixpkgs/nixos/modules/services/development/lorri.nix
index a82b3f57f8e8..74f56f5890fc 100644
--- a/nixpkgs/nixos/modules/services/development/lorri.nix
+++ b/nixpkgs/nixos/modules/services/development/lorri.nix
@@ -9,7 +9,7 @@ in {
       enable = lib.mkOption {
         default = false;
         type = lib.types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enables the daemon for `lorri`, a nix-shell replacement for project
           development. The socket-activated daemon starts on the first request
           issued by the `lorri` command.
@@ -50,6 +50,6 @@ in {
       };
     };
 
-    environment.systemPackages = [ cfg.package ];
+    environment.systemPackages = [ cfg.package pkgs.direnv ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/development/rstudio-server/default.nix b/nixpkgs/nixos/modules/services/development/rstudio-server/default.nix
index 74a7cd2f4e57..bf4c7727bf74 100644
--- a/nixpkgs/nixos/modules/services/development/rstudio-server/default.nix
+++ b/nixpkgs/nixos/modules/services/development/rstudio-server/default.nix
@@ -21,7 +21,7 @@ in
   meta.maintainers = with maintainers; [ jbedo cfhammill ];
 
   options.services.rstudio-server = {
-    enable = mkEnableOption "RStudio server";
+    enable = mkEnableOption (lib.mdDoc "RStudio server");
 
     serverWorkingDir = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/development/zammad.nix b/nixpkgs/nixos/modules/services/development/zammad.nix
index e81eef3c0a51..7dd143eebf12 100644
--- a/nixpkgs/nixos/modules/services/development/zammad.nix
+++ b/nixpkgs/nixos/modules/services/development/zammad.nix
@@ -28,7 +28,7 @@ in
 
   options = {
     services.zammad = {
-      enable = mkEnableOption "Zammad, a web-based, open source user support/ticketing solution.";
+      enable = mkEnableOption (lib.mdDoc "Zammad, a web-based, open source user support/ticketing solution");
 
       package = mkOption {
         type = types.package;
@@ -137,9 +137,9 @@ in
             {
             }
           '';
-          description = ''
-            The <filename>database.yml</filename> configuration file as key value set.
-            See <link xlink:href="TODO"/>
+          description = lib.mdDoc ''
+            The {file}`database.yml` configuration file as key value set.
+            See \<TODO\>
             for list of configuration parameters.
           '';
         };
@@ -149,20 +149,20 @@ in
         type = types.nullOr types.path;
         default = null;
         example = "/run/keys/secret_key_base";
-        description = ''
+        description = lib.mdDoc ''
           The path to a file containing the
-          <literal>secret_key_base</literal> secret.
+          `secret_key_base` secret.
 
-          Zammad uses <literal>secret_key_base</literal> to encrypt
+          Zammad uses `secret_key_base` to encrypt
           the cookie store, which contains session data, and to digest
           user auth tokens.
 
           Needs to be a 64 byte long string of hexadecimal
           characters. You can generate one by running
 
-          <screen>
-          <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file
-          </screen>
+          ```
+          openssl rand -hex 64 >/path/to/secret_key_base_file
+          ```
 
           This should be a string, not a nix path, since nix paths are
           copied into the world-readable nix store.
@@ -254,7 +254,7 @@ in
       preStart = ''
         # Blindly copy the whole project here.
         chmod -R +w .
-        rm -rf ./public/assets/*
+        rm -rf ./public/assets/
         rm -rf ./tmp/*
         rm -rf ./log/*
         cp -r --no-preserve=owner ${cfg.package}/* .
diff --git a/nixpkgs/nixos/modules/services/display-managers/greetd.nix b/nixpkgs/nixos/modules/services/display-managers/greetd.nix
index a81fcbf19d17..3a0f59f62afb 100644
--- a/nixpkgs/nixos/modules/services/display-managers/greetd.nix
+++ b/nixpkgs/nixos/modules/services/display-managers/greetd.nix
@@ -8,7 +8,7 @@ let
 in
 {
   options.services.greetd = {
-    enable = mkEnableOption "greetd";
+    enable = mkEnableOption (lib.mdDoc "greetd");
 
     package = mkOption {
       type = types.package;
@@ -45,7 +45,7 @@ in
       default = !(cfg.settings ? initial_session);
       defaultText = literalExpression "!(config.services.greetd.settings ? initial_session)";
       description = lib.mdDoc ''
-        Wether to restart greetd when it terminates (e.g. on failure).
+        Whether to restart greetd when it terminates (e.g. on failure).
         This is usually desirable so a user can always log in, but should be disabled when using 'settings.initial_session' (autologin),
         because every greetd restart will trigger the autologin again.
       '';
@@ -89,6 +89,8 @@ in
         SendSIGHUP = true;
         TimeoutStopSec = "30s";
         KeyringMode = "shared";
+
+        Type = "idle";
       };
 
       # Don't kill a user session when using nixos-rebuild
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.md b/nixpkgs/nixos/modules/services/editors/emacs.md
new file mode 100644
index 000000000000..72364b295144
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/emacs.md
@@ -0,0 +1,420 @@
+# Emacs {#module-services-emacs}
+
+<!--
+    Documentation contributors:
+      Damien Cassou @DamienCassou
+      Thomas Tuegel @ttuegel
+      Rodney Lorrimar @rvl
+      Adam Hoese @adisbladis
+  -->
+
+[Emacs](https://www.gnu.org/software/emacs/) is an
+extensible, customizable, self-documenting real-time display editor — and
+more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp
+programming language with extensions to support text editing.
+
+Emacs runs within a graphical desktop environment using the X Window System,
+but works equally well on a text terminal. Under
+macOS, a "Mac port" edition is available, which
+uses Apple's native GUI frameworks.
+
+Nixpkgs provides a superior environment for
+running Emacs. It's simple to create custom builds
+by overriding the default packages. Chaotic collections of Emacs Lisp code
+and extensions can be brought under control using declarative package
+management. NixOS even provides a
+{command}`systemd` user service for automatically starting the Emacs
+daemon.
+
+## Installing Emacs {#module-services-emacs-installing}
+
+Emacs can be installed in the normal way for Nix (see
+[](#sec-package-management)). In addition, a NixOS
+*service* can be enabled.
+
+### The Different Releases of Emacs {#module-services-emacs-releases}
+
+Nixpkgs defines several basic Emacs packages.
+The following are attributes belonging to the {var}`pkgs` set:
+
+  {var}`emacs`
+  : The latest stable version of Emacs using the [GTK 2](http://www.gtk.org)
+    widget toolkit.
+
+  {var}`emacs-nox`
+  : Emacs built without any dependency on X11 libraries.
+
+  {var}`emacsMacport`
+  : Emacs with the "Mac port" patches, providing a more native look and
+    feel under macOS.
+
+If those aren't suitable, then the following imitation Emacs editors are
+also available in Nixpkgs:
+[Zile](https://www.gnu.org/software/zile/),
+[mg](http://homepage.boetes.org/software/mg/),
+[Yi](http://yi-editor.github.io/),
+[jmacs](https://joe-editor.sourceforge.io/).
+
+### Adding Packages to Emacs {#module-services-emacs-adding-packages}
+
+Emacs includes an entire ecosystem of functionality beyond text editing,
+including a project planner, mail and news reader, debugger interface,
+calendar, and more.
+
+Most extensions are gotten with the Emacs packaging system
+({file}`package.el`) from
+[Emacs Lisp Package Archive (ELPA)](https://elpa.gnu.org/),
+[MELPA](https://melpa.org/),
+[MELPA Stable](https://stable.melpa.org/), and
+[Org ELPA](http://orgmode.org/elpa.html). Nixpkgs is
+regularly updated to mirror all these archives.
+
+Under NixOS, you can continue to use
+`package-list-packages` and
+`package-install` to install packages. You can also
+declare the set of Emacs packages you need using the derivations from
+Nixpkgs. The rest of this section discusses declarative installation of
+Emacs packages through nixpkgs.
+
+The first step to declare the list of packages you want in your Emacs
+installation is to create a dedicated derivation. This can be done in a
+dedicated {file}`emacs.nix` file such as:
+
+::: {.example #ex-emacsNix}
+### Nix expression to build Emacs with packages (`emacs.nix`)
+
+```nix
+/*
+This is a nix expression to build Emacs and some Emacs packages I like
+from source on any distribution where Nix is installed. This will install
+all the dependencies from the nixpkgs repository and build the binary files
+without interfering with the host distribution.
+
+To build the project, type the following from the current directory:
+
+$ nix-build emacs.nix
+
+To run the newly compiled executable:
+
+$ ./result/bin/emacs
+*/
+
+# The first non-comment line in this file indicates that
+# the whole file represents a function.
+{ pkgs ? import <nixpkgs> {} }:
+
+let
+  # The let expression below defines a myEmacs binding pointing to the
+  # current stable version of Emacs. This binding is here to separate
+  # the choice of the Emacs binary from the specification of the
+  # required packages.
+  myEmacs = pkgs.emacs;
+  # This generates an emacsWithPackages function. It takes a single
+  # argument: a function from a package set to a list of packages
+  # (the packages that will be available in Emacs).
+  emacsWithPackages = (pkgs.emacsPackagesFor myEmacs).emacsWithPackages;
+in
+  # The rest of the file specifies the list of packages to install. In the
+  # example, two packages (magit and zerodark-theme) are taken from
+  # MELPA stable.
+  emacsWithPackages (epkgs: (with epkgs.melpaStablePackages; [
+    magit          # ; Integrate git <C-x g>
+    zerodark-theme # ; Nicolas' theme
+  ])
+  # Two packages (undo-tree and zoom-frm) are taken from MELPA.
+  ++ (with epkgs.melpaPackages; [
+    undo-tree      # ; <C-x u> to show the undo tree
+    zoom-frm       # ; increase/decrease font size for all buffers %lt;C-x C-+>
+  ])
+  # Three packages are taken from GNU ELPA.
+  ++ (with epkgs.elpaPackages; [
+    auctex         # ; LaTeX mode
+    beacon         # ; highlight my cursor when scrolling
+    nameless       # ; hide current package name everywhere in elisp code
+  ])
+  # notmuch is taken from a nixpkgs derivation which contains an Emacs mode.
+  ++ [
+    pkgs.notmuch   # From main packages set
+  ])
+```
+:::
+
+The result of this configuration will be an {command}`emacs`
+command which launches Emacs with all of your chosen packages in the
+{var}`load-path`.
+
+You can check that it works by executing this in a terminal:
+```ShellSession
+$ nix-build emacs.nix
+$ ./result/bin/emacs -q
+```
+and then typing `M-x package-initialize`. Check that you
+can use all the packages you want in this Emacs instance. For example, try
+switching to the zerodark theme through `M-x load-theme <RET> zerodark <RET> y`.
+
+::: {.tip}
+A few popular extensions worth checking out are: auctex, company,
+edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile,
+and yasnippet.
+:::
+
+The list of available packages in the various ELPA repositories can be seen
+with the following commands:
+::: {.example #module-services-emacs-querying-packages}
+### Querying Emacs packages
+
+```
+nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.elpaPackages
+nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.melpaPackages
+nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.melpaStablePackages
+nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.orgPackages
+```
+:::
+
+If you are on NixOS, you can install this particular Emacs for all users by
+adding it to the list of system packages (see
+[](#sec-declarative-package-mgmt)). Simply modify your file
+{file}`configuration.nix` to make it contain:
+::: {.example #module-services-emacs-configuration-nix}
+### Custom Emacs in `configuration.nix`
+
+```
+{
+ environment.systemPackages = [
+   # [...]
+   (import /path/to/emacs.nix { inherit pkgs; })
+  ];
+}
+```
+:::
+
+In this case, the next {command}`nixos-rebuild switch` will take
+care of adding your {command}`emacs` to the {var}`PATH`
+environment variable (see [](#sec-changing-config)).
+
+<!-- fixme: i think the following is better done with config.nix
+https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
+-->
+
+If you are not on NixOS or want to install this particular Emacs only for
+yourself, you can do so by adding it to your
+{file}`~/.config/nixpkgs/config.nix` (see
+[Nixpkgs manual](https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides)):
+::: {.example #module-services-emacs-config-nix}
+### Custom Emacs in `~/.config/nixpkgs/config.nix`
+
+```
+{
+  packageOverrides = super: let self = super.pkgs; in {
+    myemacs = import /path/to/emacs.nix { pkgs = self; };
+  };
+}
+```
+:::
+
+In this case, the next `nix-env -f '<nixpkgs>' -iA
+myemacs` will take care of adding your emacs to the
+{var}`PATH` environment variable.
+
+### Advanced Emacs Configuration {#module-services-emacs-advanced}
+
+If you want, you can tweak the Emacs package itself from your
+{file}`emacs.nix`. For example, if you want to have a
+GTK 3-based Emacs instead of the default GTK 2-based binary and remove the
+automatically generated {file}`emacs.desktop` (useful if you
+only use {command}`emacsclient`), you can change your file
+{file}`emacs.nix` in this way:
+
+::: {.example #ex-emacsGtk3Nix}
+### Custom Emacs build
+
+```
+{ pkgs ? import <nixpkgs> {} }:
+let
+  myEmacs = (pkgs.emacs.override {
+    # Use gtk3 instead of the default gtk2
+    withGTK3 = true;
+    withGTK2 = false;
+  }).overrideAttrs (attrs: {
+    # I don't want emacs.desktop file because I only use
+    # emacsclient.
+    postInstall = (attrs.postInstall or "") + ''
+      rm $out/share/applications/emacs.desktop
+    '';
+  });
+in [...]
+```
+:::
+
+After building this file as shown in [](#ex-emacsNix), you
+will get an GTK 3-based Emacs binary pre-loaded with your favorite packages.
+
+## Running Emacs as a Service {#module-services-emacs-running}
+
+NixOS provides an optional
+{command}`systemd` service which launches
+[Emacs daemon](https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html)
+with the user's login session.
+
+*Source:* {file}`modules/services/editors/emacs.nix`
+
+### Enabling the Service {#module-services-emacs-enabling}
+
+To install and enable the {command}`systemd` user service for Emacs
+daemon, add the following to your {file}`configuration.nix`:
+```
+services.emacs.enable = true;
+services.emacs.package = import /home/cassou/.emacs.d { pkgs = pkgs; };
+```
+
+The {var}`services.emacs.package` option allows a custom
+derivation to be used, for example, one created by
+`emacsWithPackages`.
+
+Ensure that the Emacs server is enabled for your user's Emacs
+configuration, either by customizing the {var}`server-mode`
+variable, or by adding `(server-start)` to
+{file}`~/.emacs.d/init.el`.
+
+To start the daemon, execute the following:
+```ShellSession
+$ nixos-rebuild switch  # to activate the new configuration.nix
+$ systemctl --user daemon-reload        # to force systemd reload
+$ systemctl --user start emacs.service  # to start the Emacs daemon
+```
+The server should now be ready to serve Emacs clients.
+
+### Starting the client {#module-services-emacs-starting-client}
+
+Ensure that the emacs server is enabled, either by customizing the
+{var}`server-mode` variable, or by adding
+`(server-start)` to {file}`~/.emacs`.
+
+To connect to the emacs daemon, run one of the following:
+```
+emacsclient FILENAME
+emacsclient --create-frame  # opens a new frame (window)
+emacsclient --create-frame --tty  # opens a new frame on the current terminal
+```
+
+### Configuring the {var}`EDITOR` variable {#module-services-emacs-editor-variable}
+
+<!--<title>{command}`emacsclient` as the Default Editor</title>-->
+
+If [](#opt-services.emacs.defaultEditor) is
+`true`, the {var}`EDITOR` variable will be set
+to a wrapper script which launches {command}`emacsclient`.
+
+Any setting of {var}`EDITOR` in the shell config files will
+override {var}`services.emacs.defaultEditor`. To make sure
+{var}`EDITOR` refers to the Emacs wrapper script, remove any
+existing {var}`EDITOR` assignment from
+{file}`.profile`, {file}`.bashrc`,
+{file}`.zshenv` or any other shell config file.
+
+If you have formed certain bad habits when editing files, these can be
+corrected with a shell alias to the wrapper script:
+```
+alias vi=$EDITOR
+```
+
+### Per-User Enabling of the Service {#module-services-emacs-per-user}
+
+In general, {command}`systemd` user services are globally enabled
+by symlinks in {file}`/etc/systemd/user`. In the case where
+Emacs daemon is not wanted for all users, it is possible to install the
+service but not globally enable it:
+```
+services.emacs.enable = false;
+services.emacs.install = true;
+```
+
+To enable the {command}`systemd` user service for just the
+currently logged in user, run:
+```
+systemctl --user enable emacs
+```
+This will add the symlink
+{file}`~/.config/systemd/user/emacs.service`.
+
+## Configuring Emacs {#module-services-emacs-configuring}
+
+The Emacs init file should be changed to load the extension packages at
+startup:
+
+::: {.example #module-services-emacs-package-initialisation}
+### Package initialization in `.emacs`
+
+```
+(require 'package)
+
+;; optional. makes unpure packages archives unavailable
+(setq package-archives nil)
+
+(setq package-enable-at-startup nil)
+(package-initialize)
+```
+:::
+
+After the declarative emacs package configuration has been tested,
+previously downloaded packages can be cleaned up by removing
+{file}`~/.emacs.d/elpa` (do make a backup first, in case you
+forgot a package).
+
+<!--
+      todo: is it worth documenting customizations for
+      server-switch-hook, server-done-hook?
+  -->
+
+### A Major Mode for Nix Expressions {#module-services-emacs-major-mode}
+
+Of interest may be {var}`melpaPackages.nix-mode`, which
+provides syntax highlighting for the Nix language. This is particularly
+convenient if you regularly edit Nix files.
+
+### Accessing man pages {#module-services-emacs-man-pages}
+
+You can use `woman` to get completion of all available
+man pages. For example, type `M-x woman <RET> nixos-rebuild <RET>.`
+
+### Editing DocBook 5 XML Documents {#sec-emacs-docbook-xml}
+
+Emacs includes
+[nXML](https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html),
+a major-mode for validating and editing XML documents. When editing DocBook
+5.0 documents, such as [this one](#book-nixos-manual),
+nXML needs to be configured with the relevant schema, which is not
+included.
+
+To install the DocBook 5.0 schemas, either add
+{var}`pkgs.docbook5` to [](#opt-environment.systemPackages)
+([NixOS](#sec-declarative-package-mgmt)), or run
+`nix-env -f '<nixpkgs>' -iA docbook5`
+([Nix](#sec-ad-hoc-packages)).
+
+Then customize the variable {var}`rng-schema-locating-files` to
+include {file}`~/.emacs.d/schemas.xml` and put the following
+text into that file:
+::: {.example #ex-emacs-docbook-xml}
+### nXML Schema Configuration (`~/.emacs.d/schemas.xml`)
+
+```xml
+<?xml version="1.0"?>
+<!--
+  To let emacs find this file, evaluate:
+  (add-to-list 'rng-schema-locating-files "~/.emacs.d/schemas.xml")
+-->
+<locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0">
+  <!--
+    Use this variation if pkgs.docbook5 is added to environment.systemPackages
+  -->
+  <namespace ns="http://docbook.org/ns/docbook"
+             uri="/run/current-system/sw/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
+  <!--
+    Use this variation if installing schema with "nix-env -iA pkgs.docbook5".
+  <namespace ns="http://docbook.org/ns/docbook"
+             uri="../.nix-profile/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
+  -->
+</locatingRules>
+```
+:::
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.nix b/nixpkgs/nixos/modules/services/editors/emacs.nix
index 0d9949d2ba5d..2be46e47d64c 100644
--- a/nixpkgs/nixos/modules/services/editors/emacs.nix
+++ b/nixpkgs/nixos/modules/services/editors/emacs.nix
@@ -41,24 +41,24 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to enable a user service for the Emacs daemon. Use <literal>emacsclient</literal> to connect to the
-        daemon. If <literal>true</literal>, <varname>services.emacs.install</varname> is
-        considered <literal>true</literal>, whatever its value.
+      description = lib.mdDoc ''
+        Whether to enable a user service for the Emacs daemon. Use `emacsclient` to connect to the
+        daemon. If `true`, {var}`services.emacs.install` is
+        considered `true`, whatever its value.
       '';
     };
 
     install = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install a user service for the Emacs daemon. Once
         the service is started, use emacsclient to connect to the
         daemon.
 
         The service must be manually started for each user with
         "systemctl --user start emacs" or globally through
-        <varname>services.emacs.enable</varname>.
+        {var}`services.emacs.enable`.
       '';
     };
 
@@ -99,5 +99,5 @@ in
     environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "${editorScript}/bin/emacseditor");
   };
 
-  meta.doc = ./emacs.xml;
+  meta.doc = ./emacs.md;
 }
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.xml b/nixpkgs/nixos/modules/services/editors/emacs.xml
deleted file mode 100644
index fd99ee9442c9..000000000000
--- a/nixpkgs/nixos/modules/services/editors/emacs.xml
+++ /dev/null
@@ -1,580 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-emacs">
- <title>Emacs</title>
-<!--
-    Documentation contributors:
-      Damien Cassou @DamienCassou
-      Thomas Tuegel @ttuegel
-      Rodney Lorrimar @rvl
-      Adam Hoese @adisbladis
-  -->
- <para>
-  <link xlink:href="https://www.gnu.org/software/emacs/">Emacs</link> is an
-  extensible, customizable, self-documenting real-time display editor — and
-  more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp
-  programming language with extensions to support text editing.
- </para>
- <para>
-  Emacs runs within a graphical desktop environment using the X Window System,
-  but works equally well on a text terminal. Under
-  <productname>macOS</productname>, a "Mac port" edition is available, which
-  uses Apple's native GUI frameworks.
- </para>
- <para>
-  <productname>Nixpkgs</productname> provides a superior environment for
-  running <application>Emacs</application>. It's simple to create custom builds
-  by overriding the default packages. Chaotic collections of Emacs Lisp code
-  and extensions can be brought under control using declarative package
-  management. <productname>NixOS</productname> even provides a
-  <command>systemd</command> user service for automatically starting the Emacs
-  daemon.
- </para>
- <section xml:id="module-services-emacs-installing">
-  <title>Installing <application>Emacs</application></title>
-
-  <para>
-   Emacs can be installed in the normal way for Nix (see
-   <xref linkend="sec-package-management" />). In addition, a NixOS
-   <emphasis>service</emphasis> can be enabled.
-  </para>
-
-  <section xml:id="module-services-emacs-releases">
-   <title>The Different Releases of Emacs</title>
-
-   <para>
-    <productname>Nixpkgs</productname> defines several basic Emacs packages.
-    The following are attributes belonging to the <varname>pkgs</varname> set:
-    <variablelist>
-     <varlistentry>
-      <term>
-       <varname>emacs</varname>
-      </term>
-      <term>
-       <varname>emacs</varname>
-      </term>
-      <listitem>
-       <para>
-        The latest stable version of Emacs using the
-        <link
-                xlink:href="http://www.gtk.org">GTK 2</link>
-        widget toolkit.
-       </para>
-      </listitem>
-     </varlistentry>
-     <varlistentry>
-      <term>
-       <varname>emacs-nox</varname>
-      </term>
-      <listitem>
-       <para>
-        Emacs built without any dependency on X11 libraries.
-       </para>
-      </listitem>
-     </varlistentry>
-     <varlistentry>
-      <term>
-       <varname>emacsMacport</varname>
-      </term>
-      <term>
-       <varname>emacsMacport</varname>
-      </term>
-      <listitem>
-       <para>
-        Emacs with the "Mac port" patches, providing a more native look and
-        feel under macOS.
-       </para>
-      </listitem>
-     </varlistentry>
-    </variablelist>
-   </para>
-
-   <para>
-    If those aren't suitable, then the following imitation Emacs editors are
-    also available in Nixpkgs:
-    <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
-    <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
-    <link xlink:href="http://yi-editor.github.io/">Yi</link>,
-    <link xlink:href="https://joe-editor.sourceforge.io/">jmacs</link>.
-   </para>
-  </section>
-
-  <section xml:id="module-services-emacs-adding-packages">
-   <title>Adding Packages to Emacs</title>
-
-   <para>
-    Emacs includes an entire ecosystem of functionality beyond text editing,
-    including a project planner, mail and news reader, debugger interface,
-    calendar, and more.
-   </para>
-
-   <para>
-    Most extensions are gotten with the Emacs packaging system
-    (<filename>package.el</filename>) from
-    <link
-        xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
-    (<acronym>ELPA</acronym>)</link>,
-    <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
-    <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, and
-    <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. Nixpkgs is
-    regularly updated to mirror all these archives.
-   </para>
-
-   <para>
-    Under NixOS, you can continue to use
-    <function>package-list-packages</function> and
-    <function>package-install</function> to install packages. You can also
-    declare the set of Emacs packages you need using the derivations from
-    Nixpkgs. The rest of this section discusses declarative installation of
-    Emacs packages through nixpkgs.
-   </para>
-
-   <para>
-    The first step to declare the list of packages you want in your Emacs
-    installation is to create a dedicated derivation. This can be done in a
-    dedicated <filename>emacs.nix</filename> file such as:
-    <example xml:id="ex-emacsNix">
-     <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
-<programlisting language="nix">
-/*
-This is a nix expression to build Emacs and some Emacs packages I like
-from source on any distribution where Nix is installed. This will install
-all the dependencies from the nixpkgs repository and build the binary files
-without interfering with the host distribution.
-
-To build the project, type the following from the current directory:
-
-$ nix-build emacs.nix
-
-To run the newly compiled executable:
-
-$ ./result/bin/emacs
-*/
-{ pkgs ? import &lt;nixpkgs&gt; {} }: <co xml:id="ex-emacsNix-1" />
-
-let
-  myEmacs = pkgs.emacs; <co xml:id="ex-emacsNix-2" />
-  emacsWithPackages = (pkgs.emacsPackagesFor myEmacs).emacsWithPackages; <co xml:id="ex-emacsNix-3" />
-in
-  emacsWithPackages (epkgs: (with epkgs.melpaStablePackages; [ <co xml:id="ex-emacsNix-4" />
-    magit          # ; Integrate git &lt;C-x g&gt;
-    zerodark-theme # ; Nicolas' theme
-  ]) ++ (with epkgs.melpaPackages; [ <co xml:id="ex-emacsNix-5" />
-    undo-tree      # ; &lt;C-x u&gt; to show the undo tree
-    zoom-frm       # ; increase/decrease font size for all buffers %lt;C-x C-+&gt;
-  ]) ++ (with epkgs.elpaPackages; [ <co xml:id="ex-emacsNix-6" />
-    auctex         # ; LaTeX mode
-    beacon         # ; highlight my cursor when scrolling
-    nameless       # ; hide current package name everywhere in elisp code
-  ]) ++ [
-    pkgs.notmuch   # From main packages set <co xml:id="ex-emacsNix-7" />
-  ])
-</programlisting>
-    </example>
-    <calloutlist>
-     <callout arearefs="ex-emacsNix-1">
-      <para>
-       The first non-comment line in this file (<literal>{ pkgs ? ...
-       }</literal>) indicates that the whole file represents a function.
-      </para>
-     </callout>
-     <callout arearefs="ex-emacsNix-2">
-      <para>
-       The <varname>let</varname> expression below defines a
-       <varname>myEmacs</varname> binding pointing to the current stable
-       version of Emacs. This binding is here to separate the choice of the
-       Emacs binary from the specification of the required packages.
-      </para>
-     </callout>
-     <callout arearefs="ex-emacsNix-3">
-      <para>
-       This generates an <varname>emacsWithPackages</varname> function. It
-       takes a single argument: a function from a package set to a list of
-       packages (the packages that will be available in Emacs).
-      </para>
-     </callout>
-     <callout arearefs="ex-emacsNix-4">
-      <para>
-       The rest of the file specifies the list of packages to install. In the
-       example, two packages (<varname>magit</varname> and
-       <varname>zerodark-theme</varname>) are taken from MELPA stable.
-      </para>
-     </callout>
-     <callout arearefs="ex-emacsNix-5">
-      <para>
-       Two packages (<varname>undo-tree</varname> and
-       <varname>zoom-frm</varname>) are taken from MELPA.
-      </para>
-     </callout>
-     <callout arearefs="ex-emacsNix-6">
-      <para>
-       Three packages are taken from GNU ELPA.
-      </para>
-     </callout>
-     <callout arearefs="ex-emacsNix-7">
-      <para>
-       <varname>notmuch</varname> is taken from a nixpkgs derivation which
-       contains an Emacs mode.
-      </para>
-     </callout>
-    </calloutlist>
-   </para>
-
-   <para>
-    The result of this configuration will be an <command>emacs</command>
-    command which launches Emacs with all of your chosen packages in the
-    <varname>load-path</varname>.
-   </para>
-
-   <para>
-    You can check that it works by executing this in a terminal:
-<screen>
-<prompt>$ </prompt>nix-build emacs.nix
-<prompt>$ </prompt>./result/bin/emacs -q
-</screen>
-    and then typing <literal>M-x package-initialize</literal>. Check that you
-    can use all the packages you want in this Emacs instance. For example, try
-    switching to the zerodark theme through <literal>M-x load-theme &lt;RET&gt;
-    zerodark &lt;RET&gt; y</literal>.
-   </para>
-
-   <tip>
-    <para>
-     A few popular extensions worth checking out are: auctex, company,
-     edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile,
-     and yasnippet.
-    </para>
-   </tip>
-
-   <para>
-    The list of available packages in the various ELPA repositories can be seen
-    with the following commands:
-    <example xml:id="module-services-emacs-querying-packages">
-     <title>Querying Emacs packages</title>
-<programlisting><![CDATA[
-nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.elpaPackages
-nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.melpaPackages
-nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.melpaStablePackages
-nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.orgPackages
-]]></programlisting>
-    </example>
-   </para>
-
-   <para>
-    If you are on NixOS, you can install this particular Emacs for all users by
-    adding it to the list of system packages (see
-    <xref linkend="sec-declarative-package-mgmt" />). Simply modify your file
-    <filename>configuration.nix</filename> to make it contain:
-    <example xml:id="module-services-emacs-configuration-nix">
-     <title>Custom Emacs in <filename>configuration.nix</filename></title>
-<programlisting><![CDATA[
-{
- environment.systemPackages = [
-   # [...]
-   (import /path/to/emacs.nix { inherit pkgs; })
-  ];
-}
-]]></programlisting>
-    </example>
-   </para>
-
-   <para>
-    In this case, the next <command>nixos-rebuild switch</command> will take
-    care of adding your <command>emacs</command> to the <varname>PATH</varname>
-    environment variable (see <xref linkend="sec-changing-config" />).
-   </para>
-
-<!-- fixme: i think the following is better done with config.nix
-https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
--->
-
-   <para>
-    If you are not on NixOS or want to install this particular Emacs only for
-    yourself, you can do so by adding it to your
-    <filename>~/.config/nixpkgs/config.nix</filename> (see
-    <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
-    manual</link>):
-    <example xml:id="module-services-emacs-config-nix">
-     <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
-<programlisting><![CDATA[
-{
-  packageOverrides = super: let self = super.pkgs; in {
-    myemacs = import /path/to/emacs.nix { pkgs = self; };
-  };
-}
-]]></programlisting>
-    </example>
-   </para>
-
-   <para>
-    In this case, the next <literal>nix-env -f '&lt;nixpkgs&gt;' -iA
-    myemacs</literal> will take care of adding your emacs to the
-    <varname>PATH</varname> environment variable.
-   </para>
-  </section>
-
-  <section xml:id="module-services-emacs-advanced">
-   <title>Advanced Emacs Configuration</title>
-
-   <para>
-    If you want, you can tweak the Emacs package itself from your
-    <filename>emacs.nix</filename>. For example, if you want to have a
-    GTK 3-based Emacs instead of the default GTK 2-based binary and remove the
-    automatically generated <filename>emacs.desktop</filename> (useful if you
-    only use <command>emacsclient</command>), you can change your file
-    <filename>emacs.nix</filename> in this way:
-   </para>
-
-   <example xml:id="ex-emacsGtk3Nix">
-    <title>Custom Emacs build</title>
-<programlisting><![CDATA[
-{ pkgs ? import <nixpkgs> {} }:
-let
-  myEmacs = (pkgs.emacs.override {
-    # Use gtk3 instead of the default gtk2
-    withGTK3 = true;
-    withGTK2 = false;
-  }).overrideAttrs (attrs: {
-    # I don't want emacs.desktop file because I only use
-    # emacsclient.
-    postInstall = (attrs.postInstall or "") + ''
-      rm $out/share/applications/emacs.desktop
-    '';
-  });
-in [...]
-]]></programlisting>
-   </example>
-
-   <para>
-    After building this file as shown in <xref linkend="ex-emacsNix" />, you
-    will get an GTK 3-based Emacs binary pre-loaded with your favorite packages.
-   </para>
-  </section>
- </section>
- <section xml:id="module-services-emacs-running">
-  <title>Running Emacs as a Service</title>
-
-  <para>
-   <productname>NixOS</productname> provides an optional
-   <command>systemd</command> service which launches
-   <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
-   Emacs daemon </link> with the user's login session.
-  </para>
-
-  <para>
-   <emphasis>Source:</emphasis>
-   <filename>modules/services/editors/emacs.nix</filename>
-  </para>
-
-  <section xml:id="module-services-emacs-enabling">
-   <title>Enabling the Service</title>
-
-   <para>
-    To install and enable the <command>systemd</command> user service for Emacs
-    daemon, add the following to your <filename>configuration.nix</filename>:
-<programlisting>
-<xref linkend="opt-services.emacs.enable"/> = true;
-<xref linkend="opt-services.emacs.package"/> = import /home/cassou/.emacs.d { pkgs = pkgs; };
-</programlisting>
-   </para>
-
-   <para>
-    The <varname>services.emacs.package</varname> option allows a custom
-    derivation to be used, for example, one created by
-    <function>emacsWithPackages</function>.
-   </para>
-
-   <para>
-    Ensure that the Emacs server is enabled for your user's Emacs
-    configuration, either by customizing the <varname>server-mode</varname>
-    variable, or by adding <literal>(server-start)</literal> to
-    <filename>~/.emacs.d/init.el</filename>.
-   </para>
-
-   <para>
-    To start the daemon, execute the following:
-<screen>
-<prompt>$ </prompt>nixos-rebuild switch  # to activate the new configuration.nix
-<prompt>$ </prompt>systemctl --user daemon-reload        # to force systemd reload
-<prompt>$ </prompt>systemctl --user start emacs.service  # to start the Emacs daemon
-</screen>
-    The server should now be ready to serve Emacs clients.
-   </para>
-  </section>
-
-  <section xml:id="module-services-emacs-starting-client">
-   <title>Starting the client</title>
-
-   <para>
-    Ensure that the emacs server is enabled, either by customizing the
-    <varname>server-mode</varname> variable, or by adding
-    <literal>(server-start)</literal> to <filename>~/.emacs</filename>.
-   </para>
-
-   <para>
-    To connect to the emacs daemon, run one of the following:
-<programlisting><![CDATA[
-emacsclient FILENAME
-emacsclient --create-frame  # opens a new frame (window)
-emacsclient --create-frame --tty  # opens a new frame on the current terminal
-]]></programlisting>
-   </para>
-  </section>
-
-  <section xml:id="module-services-emacs-editor-variable">
-   <title>Configuring the <varname>EDITOR</varname> variable</title>
-
-<!--<title><command>emacsclient</command> as the Default Editor</title>-->
-
-   <para>
-    If <xref linkend="opt-services.emacs.defaultEditor"/> is
-    <literal>true</literal>, the <varname>EDITOR</varname> variable will be set
-    to a wrapper script which launches <command>emacsclient</command>.
-   </para>
-
-   <para>
-    Any setting of <varname>EDITOR</varname> in the shell config files will
-    override <varname>services.emacs.defaultEditor</varname>. To make sure
-    <varname>EDITOR</varname> refers to the Emacs wrapper script, remove any
-    existing <varname>EDITOR</varname> assignment from
-    <filename>.profile</filename>, <filename>.bashrc</filename>,
-    <filename>.zshenv</filename> or any other shell config file.
-   </para>
-
-   <para>
-    If you have formed certain bad habits when editing files, these can be
-    corrected with a shell alias to the wrapper script:
-<programlisting>alias vi=$EDITOR</programlisting>
-   </para>
-  </section>
-
-  <section xml:id="module-services-emacs-per-user">
-   <title>Per-User Enabling of the Service</title>
-
-   <para>
-    In general, <command>systemd</command> user services are globally enabled
-    by symlinks in <filename>/etc/systemd/user</filename>. In the case where
-    Emacs daemon is not wanted for all users, it is possible to install the
-    service but not globally enable it:
-<programlisting>
-<xref linkend="opt-services.emacs.enable"/> = false;
-<xref linkend="opt-services.emacs.install"/> = true;
-</programlisting>
-   </para>
-
-   <para>
-    To enable the <command>systemd</command> user service for just the
-    currently logged in user, run:
-<programlisting>systemctl --user enable emacs</programlisting>
-    This will add the symlink
-    <filename>~/.config/systemd/user/emacs.service</filename>.
-   </para>
-  </section>
- </section>
- <section xml:id="module-services-emacs-configuring">
-  <title>Configuring Emacs</title>
-
-  <para>
-   The Emacs init file should be changed to load the extension packages at
-   startup:
-   <example xml:id="module-services-emacs-package-initialisation">
-    <title>Package initialization in <filename>.emacs</filename></title>
-<programlisting><![CDATA[
-(require 'package)
-
-;; optional. makes unpure packages archives unavailable
-(setq package-archives nil)
-
-(setq package-enable-at-startup nil)
-(package-initialize)
-]]></programlisting>
-   </example>
-  </para>
-
-  <para>
-   After the declarative emacs package configuration has been tested,
-   previously downloaded packages can be cleaned up by removing
-   <filename>~/.emacs.d/elpa</filename> (do make a backup first, in case you
-   forgot a package).
-  </para>
-
-<!--
-      todo: is it worth documenting customizations for
-      server-switch-hook, server-done-hook?
-  -->
-
-  <section xml:id="module-services-emacs-major-mode">
-   <title>A Major Mode for Nix Expressions</title>
-
-   <para>
-    Of interest may be <varname>melpaPackages.nix-mode</varname>, which
-    provides syntax highlighting for the Nix language. This is particularly
-    convenient if you regularly edit Nix files.
-   </para>
-  </section>
-
-  <section xml:id="module-services-emacs-man-pages">
-   <title>Accessing man pages</title>
-
-   <para>
-    You can use <function>woman</function> to get completion of all available
-    man pages. For example, type <literal>M-x woman &lt;RET&gt; nixos-rebuild
-    &lt;RET&gt;.</literal>
-   </para>
-  </section>
-
-  <section xml:id="sec-emacs-docbook-xml">
-   <title>Editing DocBook 5 XML Documents</title>
-
-   <para>
-    Emacs includes
-    <link
-      xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
-    a major-mode for validating and editing XML documents. When editing DocBook
-    5.0 documents, such as <link linkend="book-nixos-manual">this one</link>,
-    nXML needs to be configured with the relevant schema, which is not
-    included.
-   </para>
-
-   <para>
-    To install the DocBook 5.0 schemas, either add
-    <varname>pkgs.docbook5</varname> to
-    <xref linkend="opt-environment.systemPackages"/>
-    (<link
-      linkend="sec-declarative-package-mgmt">NixOS</link>), or run
-    <literal>nix-env -f '&lt;nixpkgs&gt;' -iA docbook5</literal>
-    (<link linkend="sec-ad-hoc-packages">Nix</link>).
-   </para>
-
-   <para>
-    Then customize the variable <varname>rng-schema-locating-files</varname> to
-    include <filename>~/.emacs.d/schemas.xml</filename> and put the following
-    text into that file:
-    <example xml:id="ex-emacs-docbook-xml">
-     <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
-<programlisting language="xml"><![CDATA[
-<?xml version="1.0"?>
-<!--
-  To let emacs find this file, evaluate:
-  (add-to-list 'rng-schema-locating-files "~/.emacs.d/schemas.xml")
--->
-<locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0">
-  <!--
-    Use this variation if pkgs.docbook5 is added to environment.systemPackages
-  -->
-  <namespace ns="http://docbook.org/ns/docbook"
-             uri="/run/current-system/sw/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
-  <!--
-    Use this variation if installing schema with "nix-env -iA pkgs.docbook5".
-  <namespace ns="http://docbook.org/ns/docbook"
-             uri="../.nix-profile/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
-  -->
-</locatingRules>
-]]></programlisting>
-    </example>
-   </para>
-  </section>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/editors/haste.nix b/nixpkgs/nixos/modules/services/editors/haste.nix
index 2208dccbc03a..a46415d43634 100644
--- a/nixpkgs/nixos/modules/services/editors/haste.nix
+++ b/nixpkgs/nixos/modules/services/editors/haste.nix
@@ -10,8 +10,8 @@ let
 in
 {
   options.services.haste-server = {
-    enable = mkEnableOption "haste-server";
-    openFirewall = mkEnableOption "firewall passthrough for haste-server";
+    enable = mkEnableOption (lib.mdDoc "haste-server");
+    openFirewall = mkEnableOption (lib.mdDoc "firewall passthrough for haste-server");
 
     settings = mkOption {
       description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/services/editors/infinoted.nix b/nixpkgs/nixos/modules/services/editors/infinoted.nix
index d2eb4946f2fb..de0989994019 100644
--- a/nixpkgs/nixos/modules/services/editors/infinoted.nix
+++ b/nixpkgs/nixos/modules/services/editors/infinoted.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.infinoted;
 in {
   options.services.infinoted = {
-    enable = mkEnableOption "infinoted";
+    enable = mkEnableOption (lib.mdDoc "infinoted");
 
     package = mkOption {
       type = types.package;
@@ -36,7 +36,7 @@ in {
     certificateChain = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Chain of CA-certificates to which our `certificateFile` is relative.
         Optional for TLS.
       '';
diff --git a/nixpkgs/nixos/modules/services/finance/odoo.nix b/nixpkgs/nixos/modules/services/finance/odoo.nix
index 78c54a9e056b..fee9af574b5d 100644
--- a/nixpkgs/nixos/modules/services/finance/odoo.nix
+++ b/nixpkgs/nixos/modules/services/finance/odoo.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.odoo = {
-      enable = mkEnableOption "odoo";
+      enable = mkEnableOption (lib.mdDoc "odoo");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/games/asf.nix b/nixpkgs/nixos/modules/services/games/asf.nix
index b7892900376f..f15d7077d965 100644
--- a/nixpkgs/nixos/modules/services/games/asf.nix
+++ b/nixpkgs/nixos/modules/services/games/asf.nix
@@ -35,7 +35,7 @@ in
       description = lib.mdDoc ''
         If enabled, starts the ArchisSteamFarm service.
         For configuring the SteamGuard token you will need to use the web-ui, which is enabled by default over on 127.0.0.1:1242.
-        You cannot configure ASF in any way outside of nix, since all the config files get wiped on restart and replaced with the programatically set ones by nix.
+        You cannot configure ASF in any way outside of nix, since all the config files get wiped on restart and replaced with the programnatically set ones by nix.
       '';
       default = false;
     };
@@ -43,12 +43,14 @@ in
     web-ui = mkOption {
       type = types.submodule {
         options = {
-          enable = mkEnableOption
-            "Wheter to start the web-ui. This is the preferred way of configuring things such as the steam guard token";
+          enable = mkEnableOption "" // {
+            description = lib.mdDoc "Whether to start the web-ui. This is the preferred way of configuring things such as the steam guard token.";
+          };
 
           package = mkOption {
             type = types.package;
             default = pkgs.ArchiSteamFarm.ui;
+            defaultText = lib.literalExpression "pkgs.ArchiSteamFarm.ui";
             description =
               lib.mdDoc "Web-UI package to use. Contents must be in lib/dist.";
           };
@@ -56,7 +58,6 @@ in
       };
       default = {
         enable = true;
-        package = pkgs.ArchiSteamFarm.ui;
       };
       example = {
         enable = false;
@@ -67,6 +68,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.ArchiSteamFarm;
+      defaultText = lib.literalExpression "pkgs.ArchiSteamFarm";
       description =
         lib.mdDoc "Package to use. Should always be the latest version, for security reasons, since this module uses very new features and to not get out of sync with the Steam API.";
     };
@@ -81,11 +83,11 @@ in
 
     settings = mkOption {
       type = format.type;
-      description = ''
-        The ASF.json file, all the options are documented <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#global-config">here</link>.
+      description = lib.mdDoc ''
+        The ASF.json file, all the options are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#global-config).
         Do note that `AutoRestart`  and `UpdateChannel` is always to `false` respectively `0` because NixOS takes care of updating everything.
         `Headless` is also always set to `true` because there is no way to provide inputs via a systemd service.
-        You should try to keep ASF up to date since upstream does not provide support for anything but the latest version and you're exposing yourself to all kinds of issues - as is outlined <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#updateperiod">here</link>.
+        You should try to keep ASF up to date since upstream does not provide support for anything but the latest version and you're exposing yourself to all kinds of issues - as is outlined [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#updateperiod).
       '';
       example = {
         Statistics = false;
@@ -96,7 +98,7 @@ in
     ipcPasswordFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = lib.mdDoc "Path to a file containig the password. The file must be readable by the `asf` user/group.";
+      description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `asf` user/group.";
     };
 
     ipcSettings = mkOption {
@@ -127,7 +129,7 @@ in
           };
           passwordFile = mkOption {
             type = types.path;
-            description = lib.mdDoc "Path to a file containig the password. The file must be readable by the `asf` user/group.";
+            description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `asf` user/group.";
           };
           enabled = mkOption {
             type = types.bool;
@@ -243,7 +245,7 @@ in
 
             rm -f www
             ${optionalString cfg.web-ui.enable ''
-              ln -s ${cfg.web-ui.package}/lib/dist www
+              ln -s ${cfg.web-ui.package}/ www
             ''}
           '';
       };
diff --git a/nixpkgs/nixos/modules/services/games/crossfire-server.nix b/nixpkgs/nixos/modules/services/games/crossfire-server.nix
index a0dc1e6c564c..0849667e61c9 100644
--- a/nixpkgs/nixos/modules/services/games/crossfire-server.nix
+++ b/nixpkgs/nixos/modules/services/games/crossfire-server.nix
@@ -41,7 +41,7 @@ in {
     stateDir = mkOption {
       type = types.str;
       default = "/var/lib/crossfire";
-      description = ''
+      description = lib.mdDoc ''
         Where to store runtime data (save files, persistent items, etc).
 
         If left at the default, this will be automatically created on server
@@ -61,7 +61,7 @@ in {
 
     configFiles = mkOption {
       type = types.attrsOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         Text to append to the corresponding configuration files. Note that the
         files given in the example are *not* the complete set of files available
         to customize; look in /etc/crossfire after enabling the server to see
@@ -131,9 +131,9 @@ in {
         exp_table = "";
         forbid = "";
         metaserver2 = "";
-        motd = (fileContents "${cfg.package}/etc/crossfire/motd");
-        news = (fileContents "${cfg.package}/etc/crossfire/news");
-        rules = (fileContents "${cfg.package}/etc/crossfire/rules");
+        motd = fileContents "${cfg.package}/etc/crossfire/motd";
+        news = fileContents "${cfg.package}/etc/crossfire/news";
+        rules = fileContents "${cfg.package}/etc/crossfire/rules";
         settings = "";
         stat_bonus = "";
       } // cfg.configFiles);
diff --git a/nixpkgs/nixos/modules/services/games/deliantra-server.nix b/nixpkgs/nixos/modules/services/games/deliantra-server.nix
index 17bdf2aef776..f39044eda7c7 100644
--- a/nixpkgs/nixos/modules/services/games/deliantra-server.nix
+++ b/nixpkgs/nixos/modules/services/games/deliantra-server.nix
@@ -41,7 +41,7 @@ in {
     stateDir = mkOption {
       type = types.str;
       default = "/var/lib/deliantra";
-      description = ''
+      description = lib.mdDoc ''
         Where to store runtime data (save files, persistent items, etc).
 
         If left at the default, this will be automatically created on server
diff --git a/nixpkgs/nixos/modules/services/games/factorio.nix b/nixpkgs/nixos/modules/services/games/factorio.nix
index 893afa97722e..9b15cac149d5 100644
--- a/nixpkgs/nixos/modules/services/games/factorio.nix
+++ b/nixpkgs/nixos/modules/services/games/factorio.nix
@@ -39,14 +39,14 @@ let
   } // cfg.extraSettings;
   serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings));
   serverAdminsFile = pkgs.writeText "server-adminlist.json" (builtins.toJSON cfg.admins);
-  modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods;
+  modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods cfg.mods-dat;
 in
 {
   options = {
     services.factorio = {
-      enable = mkEnableOption name;
+      enable = mkEnableOption (lib.mdDoc name);
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 34197;
         description = lib.mdDoc ''
           The port to which the service should bind.
@@ -136,6 +136,15 @@ in
           derivations via nixos-channel. Until then, this is for experts only.
         '';
       };
+      mods-dat = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          Mods settings can be changed by specifying a dat file, in the [mod
+          settings file
+          format](https://wiki.factorio.com/Mod_settings_file_format).
+        '';
+      };
       game-name = mkOption {
         type = types.nullOr types.str;
         default = "Factorio Game";
diff --git a/nixpkgs/nixos/modules/services/games/freeciv.nix b/nixpkgs/nixos/modules/services/games/freeciv.nix
index 02af9fda7afd..f33ea5c08a27 100644
--- a/nixpkgs/nixos/modules/services/games/freeciv.nix
+++ b/nixpkgs/nixos/modules/services/games/freeciv.nix
@@ -25,7 +25,7 @@ in
 {
   options = {
     services.freeciv = {
-      enable = mkEnableOption ''freeciv'';
+      enable = mkEnableOption (lib.mdDoc ''freeciv'');
       settings = mkOption {
         description = lib.mdDoc ''
           Parameters of freeciv-server.
@@ -38,7 +38,7 @@ in
             default = "none";
             description = lib.mdDoc "Announce game in LAN using given protocol.";
           };
-          options.auth = mkEnableOption "server authentication";
+          options.auth = mkEnableOption (lib.mdDoc "server authentication");
           options.Database = mkOption {
             type = types.nullOr types.str;
             apply = pkgs.writeText "auth.conf";
@@ -54,9 +54,9 @@ in
             default = 0;
             description = lib.mdDoc "Set debug log level.";
           };
-          options.exit-on-end = mkEnableOption "exit instead of restarting when a game ends.";
-          options.Guests = mkEnableOption "guests to login if auth is enabled";
-          options.Newusers = mkEnableOption "new users to login if auth is enabled";
+          options.exit-on-end = mkEnableOption (lib.mdDoc "exit instead of restarting when a game ends");
+          options.Guests = mkEnableOption (lib.mdDoc "guests to login if auth is enabled");
+          options.Newusers = mkEnableOption (lib.mdDoc "new users to login if auth is enabled");
           options.port = mkOption {
             type = types.port;
             default = 5556;
@@ -86,7 +86,7 @@ in
           };
         };
       };
-      openFirewall = mkEnableOption "opening the firewall for the port listening for clients";
+      openFirewall = mkEnableOption (lib.mdDoc "opening the firewall for the port listening for clients");
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/games/minetest-server.nix b/nixpkgs/nixos/modules/services/games/minetest-server.nix
index bc6ab7462d6b..578364ec542b 100644
--- a/nixpkgs/nixos/modules/services/games/minetest-server.nix
+++ b/nixpkgs/nixos/modules/services/games/minetest-server.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg   = config.services.minetest-server;
-  flag  = val: name: if val != null then "--${name} ${toString val} " else "";
+  flag  = val: name: optionalString (val != null) "--${name} ${toString val} ";
   flags = [
     (flag cfg.gameId "gameid")
     (flag cfg.world "world")
@@ -25,7 +25,7 @@ in
       gameId = mkOption {
         type        = types.nullOr types.str;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           Id of the game to use. To list available games run
           `minetestserver --gameid list`.
 
@@ -36,7 +36,7 @@ in
       world = mkOption {
         type        = types.nullOr types.path;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           Name of the world to use. To list available worlds run
           `minetestserver --world list`.
 
@@ -47,7 +47,7 @@ in
       configPath = mkOption {
         type        = types.nullOr types.path;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the config to use.
 
           If set to null, the config of the running user will be used:
@@ -62,7 +62,7 @@ in
           Path to logfile for logging.
 
           If set to null, logging will be output to stdout which means
-          all output will be catched by systemd.
+          all output will be caught by systemd.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/games/openarena.nix b/nixpkgs/nixos/modules/services/games/openarena.nix
index e38bc8f205a1..89e30d7c12af 100644
--- a/nixpkgs/nixos/modules/services/games/openarena.nix
+++ b/nixpkgs/nixos/modules/services/games/openarena.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.openarena = {
-      enable = mkEnableOption "OpenArena";
+      enable = mkEnableOption (lib.mdDoc "OpenArena");
 
       openPorts = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/games/quake3-server.nix b/nixpkgs/nixos/modules/services/games/quake3-server.nix
index 69fdbc50d87d..2d2148237da1 100644
--- a/nixpkgs/nixos/modules/services/games/quake3-server.nix
+++ b/nixpkgs/nixos/modules/services/games/quake3-server.nix
@@ -37,7 +37,7 @@ let
 in {
   options = {
     services.quake3-server = {
-      enable = mkEnableOption "Quake 3 dedicated server";
+      enable = mkEnableOption (lib.mdDoc "Quake 3 dedicated server");
 
       port = mkOption {
         type = types.port;
@@ -71,7 +71,7 @@ in {
       baseq3 = mkOption {
         type = types.either types.package types.path;
         default = defaultBaseq3;
-        defaultText = literalDocBook "Manually downloaded Quake 3 installation directory.";
+        defaultText = literalMD "Manually downloaded Quake 3 installation directory.";
         example = "/var/lib/q3ds";
         description = lib.mdDoc ''
           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/teeworlds.nix b/nixpkgs/nixos/modules/services/games/teeworlds.nix
index 6ddd0bee60cd..ffef440330c4 100644
--- a/nixpkgs/nixos/modules/services/games/teeworlds.nix
+++ b/nixpkgs/nixos/modules/services/games/teeworlds.nix
@@ -20,7 +20,7 @@ in
 {
   options = {
     services.teeworlds = {
-      enable = mkEnableOption "Teeworlds Server";
+      enable = mkEnableOption (lib.mdDoc "Teeworlds Server");
 
       openPorts = mkOption {
         type = types.bool;
@@ -70,7 +70,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8303;
         description = lib.mdDoc ''
           Port the server will listen on.
@@ -80,8 +80,8 @@ in
       extraOptions = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
-          Extra configuration lines for the <filename>teeworlds.cfg</filename>. See <link xlink:href="https://www.teeworlds.com/?page=docs&amp;wiki=server_settings">Teeworlds Documentation</link>.
+        description = lib.mdDoc ''
+          Extra configuration lines for the {file}`teeworlds.cfg`. See [Teeworlds Documentation](https://www.teeworlds.com/?page=docs&wiki=server_settings).
         '';
         example = [ "sv_map dm1" "sv_gametype dm" ];
       };
diff --git a/nixpkgs/nixos/modules/services/games/terraria.nix b/nixpkgs/nixos/modules/services/games/terraria.nix
index cd1bb7d819dc..ccdd779165b8 100644
--- a/nixpkgs/nixos/modules/services/games/terraria.nix
+++ b/nixpkgs/nixos/modules/services/games/terraria.nix
@@ -116,7 +116,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Wheter to open ports in the firewall";
+        description = lib.mdDoc "Whether to open ports in the firewall";
       };
 
       dataDir = mkOption {
@@ -131,6 +131,7 @@ in
   config = mkIf cfg.enable {
     users.users.terraria = {
       description = "Terraria server service user";
+      group       = "terraria";
       home        = cfg.dataDir;
       createHome  = true;
       uid         = config.ids.uids.terraria;
@@ -138,7 +139,6 @@ in
 
     users.groups.terraria = {
       gid = config.ids.gids.terraria;
-      members = [ "terraria" ];
     };
 
     systemd.services.terraria = {
diff --git a/nixpkgs/nixos/modules/services/hardware/acpid.nix b/nixpkgs/nixos/modules/services/hardware/acpid.nix
index fef2c14b9dce..821f4ef205fc 100644
--- a/nixpkgs/nixos/modules/services/hardware/acpid.nix
+++ b/nixpkgs/nixos/modules/services/hardware/acpid.nix
@@ -48,7 +48,7 @@ in
 
     services.acpid = {
 
-      enable = mkEnableOption "the ACPI daemon";
+      enable = mkEnableOption (lib.mdDoc "the ACPI daemon");
 
       logEvents = mkOption {
         type = types.bool;
@@ -72,12 +72,12 @@ in
           };
         });
 
-        description = ''
+        description = lib.mdDoc ''
           Event handlers.
 
-          <note><para>
-            Handler can be a single command.
-          </para></note>
+          ::: {.note}
+          Handler can be a single command.
+          :::
         '';
         default = {};
         example = {
diff --git a/nixpkgs/nixos/modules/services/hardware/actkbd.nix b/nixpkgs/nixos/modules/services/hardware/actkbd.nix
index 3ad2998e823d..1718d179bf5e 100644
--- a/nixpkgs/nixos/modules/services/hardware/actkbd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/actkbd.nix
@@ -57,13 +57,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable the <command>actkbd</command> key mapping daemon.
+        description = lib.mdDoc ''
+          Whether to enable the {command}`actkbd` key mapping daemon.
 
-          Turning this on will start an <command>actkbd</command>
+          Turning this on will start an {command}`actkbd`
           instance for every evdev input that has at least one key
           (which is okay even for systems with tiny memory footprint,
-          since actkbd normally uses &lt;100 bytes of memory per
+          since actkbd normally uses \<100 bytes of memory per
           instance).
 
           This allows binding keys globally without the need for e.g.
diff --git a/nixpkgs/nixos/modules/services/hardware/argonone.nix b/nixpkgs/nixos/modules/services/hardware/argonone.nix
index 61656237d6f5..e67c2625062e 100644
--- a/nixpkgs/nixos/modules/services/hardware/argonone.nix
+++ b/nixpkgs/nixos/modules/services/hardware/argonone.nix
@@ -5,11 +5,11 @@ let
 in
 {
   options.services.hardware.argonone = {
-    enable = lib.mkEnableOption "the driver for Argon One Raspberry Pi case fan and power button";
+    enable = lib.mkEnableOption (lib.mdDoc "the driver for Argon One Raspberry Pi case fan and power button");
     package = lib.mkOption {
       type = lib.types.package;
       default = pkgs.argononed;
-      defaultText = "pkgs.argononed";
+      defaultText = lib.literalExpression "pkgs.argononed";
       description = lib.mdDoc ''
         The package implementing the Argon One driver
       '';
diff --git a/nixpkgs/nixos/modules/services/hardware/asusd.nix b/nixpkgs/nixos/modules/services/hardware/asusd.nix
new file mode 100644
index 000000000000..ebbdea26c051
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/asusd.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.asusd;
+in
+{
+  options = {
+    services.asusd = {
+      enable = lib.mkEnableOption (lib.mdDoc "the asusd service for ASUS ROG laptops");
+
+      enableUserService = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Activate the asusd-user service.
+        '';
+      };
+
+      animeConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/anime.ron.
+          See https://asus-linux.org/asusctl/#anime-control.
+        '';
+      };
+
+      asusdConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd.ron.
+          See https://asus-linux.org/asusctl/.
+        '';
+      };
+
+      auraConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/aura.ron.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+
+      profileConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/profile.ron.
+          See https://asus-linux.org/asusctl/#profiles.
+        '';
+      };
+
+      fanCurvesConfig = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+          The content of /etc/asusd/fan_curves.ron.
+          See https://asus-linux.org/asusctl/#fan-curves.
+        '';
+      };
+
+      userLedModesConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd-user-ledmodes.ron.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.asusctl ];
+
+    environment.etc =
+      let
+        maybeConfig = name: cfg: lib.mkIf (cfg != null) {
+          source = pkgs.writeText name cfg;
+          mode = "0644";
+        };
+      in
+      {
+        "asusd/anime.ron" = maybeConfig "anime.ron" cfg.animeConfig;
+        "asusd/asusd.ron" = maybeConfig "asusd.ron" cfg.asusdConfig;
+        "asusd/aura.ron" = maybeConfig "aura.ron" cfg.auraConfig;
+        "asusd/profile.conf" = maybeConfig "profile.ron" cfg.profileConfig;
+        "asusd/fan_curves.ron" = maybeConfig "fan_curves.ron" cfg.fanCurvesConfig;
+        "asusd/asusd_user_ledmodes.ron" = maybeConfig "asusd_user_ledmodes.ron" cfg.userLedModesConfig;
+      };
+
+    services.dbus.enable = true;
+    systemd.packages = [ pkgs.asusctl ];
+    services.dbus.packages = [ pkgs.asusctl ];
+    services.udev.packages = [ pkgs.asusctl ];
+    services.supergfxd.enable = lib.mkDefault true;
+
+    systemd.user.services.asusd-user.enable = cfg.enableUserService;
+  };
+
+  meta.maintainers = pkgs.asusctl.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/auto-cpufreq.nix b/nixpkgs/nixos/modules/services/hardware/auto-cpufreq.nix
index f846476b30b5..fd2e03ef12f5 100644
--- a/nixpkgs/nixos/modules/services/hardware/auto-cpufreq.nix
+++ b/nixpkgs/nixos/modules/services/hardware/auto-cpufreq.nix
@@ -2,10 +2,26 @@
 with lib;
 let
   cfg = config.services.auto-cpufreq;
+  cfgFilename = "auto-cpufreq.conf";
+  cfgFile = format.generate cfgFilename cfg.settings;
+
+  format = pkgs.formats.ini {};
 in {
   options = {
     services.auto-cpufreq = {
-      enable = mkEnableOption "auto-cpufreq daemon";
+      enable = mkEnableOption (lib.mdDoc "auto-cpufreq daemon");
+
+      settings = mkOption {
+        description = lib.mdDoc ''
+          Configuration for `auto-cpufreq`.
+
+          See its [example configuration file] for supported settings.
+          [example configuration file]: https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto-cpufreq.conf-example
+          '';
+
+        default = {};
+        type = types.submodule { freeformType = format.type; };
+      };
     };
   };
 
@@ -18,6 +34,11 @@ in {
         # Workaround for https://github.com/NixOS/nixpkgs/issues/81138
         wantedBy = [ "multi-user.target" ];
         path = with pkgs; [ bash coreutils ];
+
+        serviceConfig.ExecStart = [
+          ""
+          "${lib.getExe pkgs.auto-cpufreq} --daemon --config ${cfgFile}"
+        ];
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
index a1e980dbec5e..2a58be51bb02 100644
--- a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
@@ -36,9 +36,9 @@ in
   options = {
 
     hardware.bluetooth = {
-      enable = mkEnableOption "support for Bluetooth";
+      enable = mkEnableOption (lib.mdDoc "support for Bluetooth");
 
-      hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation";
+      hsphfpd.enable = mkEnableOption (lib.mdDoc "support for hsphfpd[-prototype] implementation");
 
       powerOnBoot = mkOption {
         type = types.bool;
@@ -50,14 +50,8 @@ in
         type = types.package;
         default = pkgs.bluez;
         defaultText = literalExpression "pkgs.bluez";
-        example = literalExpression "pkgs.bluezFull";
-        description = ''
+        description = lib.mdDoc ''
           Which BlueZ package to use.
-
-          <note><para>
-            Use the <literal>pkgs.bluezFull</literal> package to enable all
-            bluez plugins.
-          </para></note>
         '';
       };
 
@@ -77,6 +71,29 @@ in
         };
         description = lib.mdDoc "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
       };
+
+      input = mkOption {
+        type = cfgFmt.type;
+        default = { };
+        example = {
+          General = {
+            IdleTimeout = 30;
+            ClassicBondedOnly = true;
+          };
+        };
+        description = lib.mdDoc "Set configuration for the input service (/etc/bluetooth/input.conf).";
+      };
+
+      network = mkOption {
+        type = cfgFmt.type;
+        default = { };
+        example = {
+          General = {
+            DisableSecurity = true;
+          };
+        };
+        description = lib.mdDoc "Set configuration for the network service (/etc/bluetooth/network.conf).";
+      };
     };
   };
 
@@ -86,6 +103,10 @@ in
     environment.systemPackages = [ package ]
       ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
 
+    environment.etc."bluetooth/input.conf".source =
+      cfgFmt.generate "input.conf" cfg.input;
+    environment.etc."bluetooth/network.conf".source =
+      cfgFmt.generate "network.conf" cfg.network;
     environment.etc."bluetooth/main.conf".source =
       cfgFmt.generate "main.conf" (recursiveUpdate defaults cfg.settings);
     services.udev.packages = [ package ];
diff --git a/nixpkgs/nixos/modules/services/hardware/ddccontrol.nix b/nixpkgs/nixos/modules/services/hardware/ddccontrol.nix
index f0b5a9c81960..0f1e8bf0d26c 100644
--- a/nixpkgs/nixos/modules/services/hardware/ddccontrol.nix
+++ b/nixpkgs/nixos/modules/services/hardware/ddccontrol.nix
@@ -13,7 +13,7 @@ in
 
   options = {
     services.ddccontrol = {
-      enable = lib.mkEnableOption "ddccontrol for controlling displays";
+      enable = lib.mkEnableOption (lib.mdDoc "ddccontrol for controlling displays");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/fancontrol.nix b/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
index 65c0c60ed3b7..993c37b2364f 100644
--- a/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
+++ b/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
@@ -9,7 +9,7 @@ let
 in
 {
   options.hardware.fancontrol = {
-    enable = mkEnableOption "software fan control (requires fancontrol.config)";
+    enable = mkEnableOption (lib.mdDoc "software fan control (requires fancontrol.config)");
 
     config = mkOption {
       type = types.lines;
@@ -42,6 +42,13 @@ in
         ExecStart = "${pkgs.lm_sensors}/sbin/fancontrol ${configFile}";
       };
     };
+
+    # On some systems, the fancontrol service does not resume properly after sleep because the pwm status of the fans
+    # is not reset properly. Restarting the service fixes this, in accordance with https://github.com/lm-sensors/lm-sensors/issues/172.
+    powerManagement.resumeCommands = ''
+      systemctl restart fancontrol.service
+    '';
+
   };
 
   meta.maintainers = [ maintainers.evils ];
diff --git a/nixpkgs/nixos/modules/services/hardware/fwupd.nix b/nixpkgs/nixos/modules/services/hardware/fwupd.nix
index eb5f6dcf8fd0..b8c2ac94845b 100644
--- a/nixpkgs/nixos/modules/services/hardware/fwupd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/fwupd.nix
@@ -7,19 +7,22 @@ with lib;
 let
   cfg = config.services.fwupd;
 
+  format = pkgs.formats.ini {
+    listToValue = l: lib.concatStringsSep ";" (map (s: generators.mkValueStringDefault {} s) l);
+    mkKeyValue = generators.mkKeyValueDefault {} "=";
+  };
+
   customEtc = {
     "fwupd/daemon.conf" = {
-      source = pkgs.writeText "daemon.conf" ''
-        [fwupd]
-        DisabledDevices=${lib.concatStringsSep ";" cfg.disabledDevices}
-        DisabledPlugins=${lib.concatStringsSep ";" cfg.disabledPlugins}
-      '';
+      source = format.generate "daemon.conf" {
+        fwupd = cfg.daemonSettings;
+      };
     };
-    "fwupd/uefi.conf" = {
-      source = pkgs.writeText "uefi.conf" ''
-        [uefi]
-        OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
-      '';
+
+    "fwupd/uefi_capsule.conf" = {
+      source = format.generate "uefi_capsule.conf" {
+        uefi_capsule = cfg.uefiCapsuleSettings;
+      };
     };
   };
 
@@ -33,18 +36,26 @@ let
       mkEtcFile = p: nameValuePair (mkName p) { source = p; };
     in listToAttrs (map mkEtcFile cfg.extraTrustedKeys);
 
-  # We cannot include the file in $out and rely on filesInstalledToEtc
-  # to install it because it would create a cyclic dependency between
-  # the outputs. We also need to enable the remote,
-  # which should not be done by default.
-  testRemote = if cfg.enableTestRemote then {
-    "fwupd/remotes.d/fwupd-tests.conf" = {
-      source = pkgs.runCommand "fwupd-tests-enabled.conf" {} ''
+  enableRemote = base: remote: {
+    "fwupd/remotes.d/${remote}.conf" = {
+      source = pkgs.runCommand "${remote}-enabled.conf" {} ''
         sed "s,^Enabled=false,Enabled=true," \
-        "${cfg.package.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
+        "${base}/etc/fwupd/remotes.d/${remote}.conf" > "$out"
       '';
     };
-  } else {};
+  };
+  remotes = (foldl'
+    (configFiles: remote: configFiles // (enableRemote cfg.package remote))
+    {}
+    cfg.extraRemotes
+  ) // (
+    # We cannot include the file in $out and rely on filesInstalledToEtc
+    # to install it because it would create a cyclic dependency between
+    # the outputs. We also need to enable the remote,
+    # which should not be done by default.
+    if cfg.enableTestRemote then (enableRemote cfg.package.installedTests "fwupd-tests") else {}
+  );
+
 in {
 
   ###### interface
@@ -59,30 +70,21 @@ in {
         '';
       };
 
-      disabledDevices = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
-        description = lib.mdDoc ''
-          Allow disabling specific devices by their GUID
-        '';
-      };
-
-      disabledPlugins = mkOption {
-        type = types.listOf types.str;
+      extraTrustedKeys = mkOption {
+        type = types.listOf types.path;
         default = [];
-        example = [ "udev" ];
+        example = literalExpression "[ /etc/nixos/fwupd/myfirmware.pem ]";
         description = lib.mdDoc ''
-          Allow disabling specific plugins
+          Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
         '';
       };
 
-      extraTrustedKeys = mkOption {
-        type = types.listOf types.path;
+      extraRemotes = mkOption {
+        type = with types; listOf str;
         default = [];
-        example = literalExpression "[ /etc/nixos/fwupd/myfirmware.pem ]";
+        example = [ "lvfs-testing" ];
         description = lib.mdDoc ''
-          Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
+          Enables extra remotes in fwupd. See `/etc/fwupd/remotes.d`.
         '';
       };
 
@@ -103,31 +105,88 @@ in {
           Which fwupd package to use.
         '';
       };
+
+      daemonSettings = mkOption {
+        type = types.submodule {
+          freeformType = format.type.nestedTypes.elemType;
+          options = {
+            DisabledDevices = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
+              description = lib.mdDoc ''
+                List of device GUIDs to be disabled.
+              '';
+            };
+
+            DisabledPlugins = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "udev" ];
+              description = lib.mdDoc ''
+                List of plugins to be disabled.
+              '';
+            };
+
+            EspLocation = mkOption {
+              type = types.path;
+              default = config.boot.loader.efi.efiSysMountPoint;
+              defaultText = lib.literalExpression "config.boot.loader.efi.efiSysMountPoint";
+              description = lib.mdDoc ''
+                The EFI system partition (ESP) path used if UDisks is not available
+                or if this partition is not mounted at /boot/efi, /boot, or /efi
+              '';
+            };
+          };
+        };
+        default = {};
+        description = lib.mdDoc ''
+          Configurations for the fwupd daemon.
+        '';
+      };
+
+      uefiCapsuleSettings = mkOption {
+        type = types.submodule {
+          freeformType = format.type.nestedTypes.elemType;
+        };
+        default = {};
+        description = lib.mdDoc ''
+          UEFI capsule configurations for the fwupd daemon.
+        '';
+      };
     };
   };
 
   imports = [
-    (mkRenamedOptionModule [ "services" "fwupd" "blacklistDevices"] [ "services" "fwupd" "disabledDevices" ])
-    (mkRenamedOptionModule [ "services" "fwupd" "blacklistPlugins"] [ "services" "fwupd" "disabledPlugins" ])
+    (mkRenamedOptionModule [ "services" "fwupd" "blacklistDevices"] [ "services" "fwupd" "daemonSettings" "DisabledDevices" ])
+    (mkRenamedOptionModule [ "services" "fwupd" "blacklistPlugins"] [ "services" "fwupd" "daemonSettings" "DisabledPlugins" ])
+    (mkRenamedOptionModule [ "services" "fwupd" "disabledDevices" ] [ "services" "fwupd" "daemonSettings" "DisabledDevices" ])
+    (mkRenamedOptionModule [ "services" "fwupd" "disabledPlugins" ] [ "services" "fwupd" "daemonSettings" "DisabledPlugins" ])
   ];
 
   ###### implementation
   config = mkIf cfg.enable {
     # Disable test related plug-ins implicitly so that users do not have to care about them.
-    services.fwupd.disabledPlugins = cfg.package.defaultDisabledPlugins;
+    services.fwupd.daemonSettings = {
+      DisabledPlugins = cfg.package.defaultDisabledPlugins;
+      EspLocation = config.boot.loader.efi.efiSysMountPoint;
+    };
 
     environment.systemPackages = [ cfg.package ];
 
     # customEtc overrides some files from the package
-    environment.etc = originalEtc // customEtc // extraTrustedKeys // testRemote;
+    environment.etc = originalEtc // customEtc // extraTrustedKeys // remotes;
 
     services.dbus.packages = [ cfg.package ];
 
     services.udev.packages = [ cfg.package ];
 
+    # required to update the firmware of disks
     services.udisks2.enable = true;
 
     systemd.packages = [ cfg.package ];
+
+    security.polkit.enable = true;
   };
 
   meta = {
diff --git a/nixpkgs/nixos/modules/services/hardware/irqbalance.nix b/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
index c79e0eb83ece..8ba0a73d895d 100644
--- a/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
+++ b/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
@@ -9,7 +9,7 @@ let
 
 in
 {
-  options.services.irqbalance.enable = mkEnableOption "irqbalance daemon";
+  options.services.irqbalance.enable = mkEnableOption (lib.mdDoc "irqbalance daemon");
 
   config = mkIf cfg.enable {
 
diff --git a/nixpkgs/nixos/modules/services/hardware/joycond.nix b/nixpkgs/nixos/modules/services/hardware/joycond.nix
index c3a71edaa2ff..1af18b3b63d3 100644
--- a/nixpkgs/nixos/modules/services/hardware/joycond.nix
+++ b/nixpkgs/nixos/modules/services/hardware/joycond.nix
@@ -9,12 +9,12 @@ with lib;
 
 {
   options.services.joycond = {
-    enable = mkEnableOption "support for Nintendo Pro Controllers and Joycons";
+    enable = mkEnableOption (lib.mdDoc "support for Nintendo Pro Controllers and Joycons");
 
     package = mkOption {
       type = types.package;
       default = pkgs.joycond;
-      defaultText = "pkgs.joycond";
+      defaultText = lib.literalExpression "pkgs.joycond";
       description = lib.mdDoc ''
         The joycond package to use.
       '';
diff --git a/nixpkgs/nixos/modules/services/hardware/kanata.nix b/nixpkgs/nixos/modules/services/hardware/kanata.nix
index ccba87531e66..7d544050130b 100644
--- a/nixpkgs/nixos/modules/services/hardware/kanata.nix
+++ b/nixpkgs/nixos/modules/services/hardware/kanata.nix
@@ -8,19 +8,9 @@ let
   keyboard = {
     options = {
       devices = mkOption {
-        type = types.addCheck (types.listOf types.str)
-          (devices: (length devices) > 0);
+        type = types.listOf types.str;
         example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
-        # TODO replace note with tip, which has not been implemented yet in
-        # nixos/lib/make-options-doc/mergeJSON.py
-        description = mdDoc ''
-          Paths to keyboard devices.
-
-          ::: {.note}
-          To avoid unnecessary triggers of the service unit, unplug devices in
-          the order of the list.
-          :::
-        '';
+        description = mdDoc "Paths to keyboard devices.";
       };
       config = mkOption {
         type = types.lines;
@@ -44,8 +34,10 @@ let
             cap (tap-hold 100 100 caps lctl))
         '';
         description = mdDoc ''
-          Configuration other than `defcfg`. See [example config
-          files](https://github.com/jtroo/kanata) for more information.
+          Configuration other than `defcfg`.
+
+          See [example config files](https://github.com/jtroo/kanata)
+          for more information.
         '';
       };
       extraDefCfg = mkOption {
@@ -53,8 +45,12 @@ let
         default = "";
         example = "danger-enable-cmd yes";
         description = mdDoc ''
-          Configuration of `defcfg` other than `linux-dev`. See [example
-          config files](https://github.com/jtroo/kanata) for more information.
+          Configuration of `defcfg` other than `linux-dev` (generated
+          from the devices option) and
+          `linux-continue-if-no-devs-found` (hardcoded to be yes).
+
+          See [example config files](https://github.com/jtroo/kanata)
+          for more information.
         '';
       };
       extraArgs = mkOption {
@@ -67,8 +63,7 @@ let
         default = null;
         example = 6666;
         description = mdDoc ''
-          Port to run the notification server on. `null` will not run the
-          server.
+          Port to run the TCP server on. `null` will not run the server.
         '';
       };
     };
@@ -76,28 +71,24 @@ let
 
   mkName = name: "kanata-${name}";
 
-  mkDevices = devices: concatStringsSep ":" devices;
+  mkDevices = devices:
+    optionalString ((length devices) > 0) "linux-dev ${concatStringsSep ":" devices}";
 
   mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
     (defcfg
       ${keyboard.extraDefCfg}
-      linux-dev ${mkDevices keyboard.devices})
+      ${mkDevices keyboard.devices}
+      linux-continue-if-no-devs-found yes)
 
     ${keyboard.config}
   '';
 
   mkService = name: keyboard: nameValuePair (mkName name) {
-    description = "kanata for ${mkDevices keyboard.devices}";
-
-    # Because path units are used to activate service units, which
-    # will start the old stopped services during "nixos-rebuild
-    # switch", stopIfChanged here is a workaround to make sure new
-    # services are running after "nixos-rebuild switch".
-    stopIfChanged = false;
-
+    wantedBy = [ "multi-user.target" ];
     serviceConfig = {
+      Type = "notify";
       ExecStart = ''
-        ${cfg.package}/bin/kanata \
+        ${getExe cfg.package} \
           --cfg ${mkConfig name keyboard} \
           --symlink-path ''${RUNTIME_DIRECTORY}/${name} \
           ${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
@@ -133,8 +124,7 @@ let
       ProtectKernelModules = true;
       ProtectKernelTunables = true;
       ProtectProc = "invisible";
-      RestrictAddressFamilies =
-        if (keyboard.port == null) then "none" else [ "AF_INET" ];
+      RestrictAddressFamilies = [ "AF_UNIX" ] ++ optional (keyboard.port != null) "AF_INET";
       RestrictNamespaces = true;
       RestrictRealtime = true;
       SystemCallArchitectures = [ "native" ];
@@ -146,42 +136,15 @@ let
       UMask = "0077";
     };
   };
-
-  mkPathName = i: name: "${mkName name}-${toString i}";
-
-  mkPath = name: n: i: device:
-    nameValuePair (mkPathName i name) {
-      description =
-        "${toString (i+1)}/${toString n} kanata trigger for ${name}, watching ${device}";
-      wantedBy = optional (i == 0) "multi-user.target";
-      pathConfig = {
-        PathExists = device;
-        # (ab)use systemd.path to construct a trigger chain so that the
-        # service unit is only started when all paths exist
-        # however, manual of systemd.path says Unit's suffix is not ".path"
-        Unit =
-          if (i + 1) == n
-          then "${mkName name}.service"
-          else "${mkPathName (i + 1) name}.path";
-      };
-      unitConfig.StopPropagatedFrom = optional (i > 0) "${mkName name}.service";
-    };
-
-  mkPaths = name: keyboard:
-    let
-      n = length keyboard.devices;
-    in
-    imap0 (mkPath name n) keyboard.devices
-  ;
 in
 {
   options.services.kanata = {
-    enable = mkEnableOption "kanata";
+    enable = mkEnableOption (mdDoc "kanata");
     package = mkOption {
       type = types.package;
       default = pkgs.kanata;
-      defaultText = lib.literalExpression "pkgs.kanata";
-      example = lib.literalExpression "pkgs.kanata-with-cmd";
+      defaultText = literalExpression "pkgs.kanata";
+      example = literalExpression "pkgs.kanata-with-cmd";
       description = mdDoc ''
         The kanata package to use.
 
@@ -198,18 +161,11 @@ in
     };
   };
 
-  config = lib.mkIf cfg.enable {
+  config = mkIf cfg.enable {
     hardware.uinput.enable = true;
 
-    systemd = {
-      paths = trivial.pipe cfg.keyboards [
-        (mapAttrsToList mkPaths)
-        concatLists
-        listToAttrs
-      ];
-      services = mapAttrs' mkService cfg.keyboards;
-    };
+    systemd.services = mapAttrs' mkService cfg.keyboards;
   };
 
-  meta.maintainers = with lib.maintainers; [ linj ];
+  meta.maintainers = with maintainers; [ linj ];
 }
diff --git a/nixpkgs/nixos/modules/services/hardware/keyd.nix b/nixpkgs/nixos/modules/services/hardware/keyd.nix
new file mode 100644
index 000000000000..d17b0e4303ef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/keyd.nix
@@ -0,0 +1,126 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.keyd;
+  settingsFormat = pkgs.formats.ini { };
+in
+{
+  options = {
+    services.keyd = {
+      enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");
+
+      ids = mkOption {
+        type = types.listOf types.string;
+        default = [ "*" ];
+        example = [ "*" "-0123:0456" ];
+        description = lib.mdDoc ''
+          Device identifiers, as shown by {manpage}`keyd(1)`.
+        '';
+      };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = {
+          main = {
+            capslock = "overload(control, esc)";
+            rightalt = "layer(rightalt)";
+          };
+
+          rightalt = {
+            j = "down";
+            k = "up";
+            h = "left";
+            l = "right";
+          };
+        };
+        description = lib.mdDoc ''
+          Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`.
+          See <https://github.com/rvaiya/keyd> how to configure.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf"
+      {
+        ids = ''
+          [ids]
+          ${concatStringsSep "\n" cfg.ids}
+        '';
+        passAsFile = [ "ids" ];
+      } ''
+      cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out
+    '';
+
+    hardware.uinput.enable = lib.mkDefault true;
+
+    systemd.services.keyd = {
+      description = "Keyd remapping daemon";
+      documentation = [ "man:keyd(1)" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      restartTriggers = [
+        config.environment.etc."keyd/default.conf".source
+      ];
+
+      # this is configurable in 2.4.2, later versions seem to remove this option.
+      # post-2.4.2 may need to set makeFlags in the derivation:
+      #
+      #     makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];
+      environment.KEYD_SOCKET = "/run/keyd/keyd.sock";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.keyd}/bin/keyd";
+        Restart = "always";
+
+        # TODO investigate why it doesn't work propeprly with DynamicUser
+        # See issue: https://github.com/NixOS/nixpkgs/issues/226346
+        # DynamicUser = true;
+        SupplementaryGroups = [
+          config.users.groups.input.name
+          config.users.groups.uinput.name
+        ];
+
+        RuntimeDirectory = "keyd";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DeviceAllow = [
+          "char-input rw"
+          "/dev/uinput rw"
+        ];
+        ProtectClock = true;
+        PrivateNetwork = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        PrivateUsers = true;
+        PrivateMounts = true;
+        PrivateTmp = true;
+        RestrictNamespaces = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        LockPersonality = true;
+        ProtectProc = "invisible";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        RestrictSUIDSGID = true;
+        IPAddressDeny = [ "any" ];
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProcSubset = "pid";
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/lcd.nix b/nixpkgs/nixos/modules/services/hardware/lcd.nix
index c817225c1f21..8d682d137f44 100644
--- a/nixpkgs/nixos/modules/services/hardware/lcd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/lcd.nix
@@ -61,20 +61,20 @@ in with lib; {
         usbPermissions = mkOption {
           type = bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Set group-write permissions on a USB device.
 
             A USB connected LCD panel will most likely require having its
             permissions modified for lcdd to write to it. Enabling this option
             sets group-write permissions on the device identified by
-            <option>services.hardware.lcd.usbVid</option> and
-            <option>services.hardware.lcd.usbPid</option>. In order to find the
-            values, you can run the <command>lsusb</command> command. Example
+            {option}`services.hardware.lcd.usbVid` and
+            {option}`services.hardware.lcd.usbPid`. In order to find the
+            values, you can run the {command}`lsusb` command. Example
             output:
 
-            <literal>
+            ```
             Bus 005 Device 002: ID 0403:c630 Future Technology Devices International, Ltd lcd2usb interface
-            </literal>
+            ```
 
             In this case the vendor id is 0403 and the product id is c630.
           '';
diff --git a/nixpkgs/nixos/modules/services/hardware/lirc.nix b/nixpkgs/nixos/modules/services/hardware/lirc.nix
index dfdd768c3544..5b1a8d10c729 100644
--- a/nixpkgs/nixos/modules/services/hardware/lirc.nix
+++ b/nixpkgs/nixos/modules/services/hardware/lirc.nix
@@ -11,7 +11,7 @@ in {
   options = {
     services.lirc = {
 
-      enable = mkEnableOption "LIRC daemon";
+      enable = mkEnableOption (lib.mdDoc "LIRC daemon");
 
       options = mkOption {
         type = types.lines;
@@ -19,7 +19,7 @@ in {
           [lircd]
           nodaemon = False
         '';
-        description = lib.mdDoc "LIRC default options descriped in man:lircd(8) ({file}`lirc_options.conf`)";
+        description = lib.mdDoc "LIRC default options described in man:lircd(8) ({file}`lirc_options.conf`)";
       };
 
       configs = mkOption {
diff --git a/nixpkgs/nixos/modules/services/hardware/openrgb.nix b/nixpkgs/nixos/modules/services/hardware/openrgb.nix
new file mode 100644
index 000000000000..310615ecc539
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/openrgb.nix
@@ -0,0 +1,53 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.hardware.openrgb;
+in {
+  options.services.hardware.openrgb = {
+    enable = mkEnableOption (lib.mdDoc "OpenRGB server");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.openrgb;
+      defaultText = literalMD "pkgs.openrgb";
+      description = lib.mdDoc "Set version of openrgb package to use.";
+    };
+
+    motherboard = mkOption {
+      type = types.nullOr (types.enum [ "amd" "intel" ]);
+      default = null;
+      description = lib.mdDoc "CPU family of motherboard. Allows for addition motherboard i2c support.";
+    };
+
+    server.port = mkOption {
+      type = types.port;
+      default = 6742;
+      description = lib.mdDoc "Set server port of openrgb.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+
+    boot.kernelModules = [ "i2c-dev" ]
+     ++ lib.optionals (cfg.motherboard == "amd") [ "i2c-piix4" ]
+     ++ lib.optionals (cfg.motherboard == "intel") [ "i2c-i801" ];
+
+    systemd.services.openrgb = {
+      description = "OpenRGB server daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        StateDirectory = "OpenRGB";
+        WorkingDirectory = "/var/lib/OpenRGB";
+        ExecStart = "${cfg.package}/bin/openrgb --server --server-port ${toString cfg.server.port}";
+        Restart = "always";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ jonringer ];
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/pcscd.nix b/nixpkgs/nixos/modules/services/hardware/pcscd.nix
index 22e4ea498497..a09c64645c48 100644
--- a/nixpkgs/nixos/modules/services/hardware/pcscd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/pcscd.nix
@@ -5,6 +5,10 @@ with lib;
 let
   cfgFile = pkgs.writeText "reader.conf" config.services.pcscd.readerConfig;
 
+  package = if config.security.polkit.enable
+              then pkgs.pcscliteWithPolkit
+              else pkgs.pcsclite;
+
   pluginEnv = pkgs.buildEnv {
     name = "pcscd-plugins";
     paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
@@ -16,7 +20,7 @@ in
   ###### interface
 
   options.services.pcscd = {
-    enable = mkEnableOption "PCSC-Lite daemon";
+    enable = mkEnableOption (lib.mdDoc "PCSC-Lite daemon");
 
     plugins = mkOption {
       type = types.listOf types.package;
@@ -49,8 +53,8 @@ in
 
     environment.etc."reader.conf".source = cfgFile;
 
-    environment.systemPackages = [ pkgs.pcsclite ];
-    systemd.packages = [ (getBin pkgs.pcsclite) ];
+    environment.systemPackages = [ package ];
+    systemd.packages = [ (getBin package) ];
 
     systemd.sockets.pcscd.wantedBy = [ "sockets.target" ];
 
@@ -66,7 +70,7 @@ in
       # around it, we force the path to the cfgFile.
       #
       # https://github.com/NixOS/nixpkgs/issues/121088
-      serviceConfig.ExecStart = [ "" "${getBin pkgs.pcsclite}/bin/pcscd -f -x -c ${cfgFile}" ];
+      serviceConfig.ExecStart = [ "" "${getBin package}/bin/pcscd -f -x -c ${cfgFile}" ];
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix b/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix
index 7048a56cb7fe..a1334684b7d5 100644
--- a/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix
+++ b/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix
@@ -10,7 +10,7 @@ in
 {
   options.hardware.rasdaemon = {
 
-    enable = mkEnableOption "RAS logging daemon";
+    enable = mkEnableOption (lib.mdDoc "RAS logging daemon");
 
     record = mkOption {
       type = types.bool;
@@ -76,7 +76,7 @@ in
       example = [ "i7core_edac" ];
     };
 
-    testing = mkEnableOption "error injection infrastructure";
+    testing = mkEnableOption (lib.mdDoc "error injection infrastructure");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/hardware/ratbagd.nix b/nixpkgs/nixos/modules/services/hardware/ratbagd.nix
index 01a8276750f2..c939d5e40a24 100644
--- a/nixpkgs/nixos/modules/services/hardware/ratbagd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/ratbagd.nix
@@ -10,7 +10,7 @@ in
 
   options = {
     services.ratbagd = {
-      enable = mkEnableOption "ratbagd for configuring gaming mice";
+      enable = mkEnableOption (lib.mdDoc "ratbagd for configuring gaming mice");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/sane.nix b/nixpkgs/nixos/modules/services/hardware/sane.nix
index aaf19c1cc0ac..2cac2e8e8bb4 100644
--- a/nixpkgs/nixos/modules/services/hardware/sane.nix
+++ b/nixpkgs/nixos/modules/services/hardware/sane.nix
@@ -28,8 +28,8 @@ let
   };
 
   env = {
-    SANE_CONFIG_DIR = config.hardware.sane.configDir;
-    LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
+    SANE_CONFIG_DIR = "/etc/sane-config";
+    LD_LIBRARY_PATH = [ "/etc/sane-libs" ];
   };
 
   backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends;
@@ -48,12 +48,12 @@ in
     hardware.sane.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable support for SANE scanners.
 
-        <note><para>
-          Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
-        </para></note>
+        ::: {.note}
+        Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
+        :::
       '';
     };
 
@@ -66,14 +66,16 @@ in
     hardware.sane.extraBackends = mkOption {
       type = types.listOf types.path;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Packages providing extra SANE backends to enable.
 
-        <note><para>
-          The example contains the package for HP scanners.
-        </para></note>
+        ::: {.note}
+        The example contains the package for HP scanners, and the package for
+        Apple AirScan and Microsoft WSD support (supports many
+        vendors/devices).
+        :::
       '';
-      example = literalExpression "[ pkgs.hplipWithPlugin ]";
+      example = literalExpression "[ pkgs.hplipWithPlugin pkgs.sane-airscan ]";
     };
 
     hardware.sane.disabledDefaultBackends = mkOption {
@@ -89,7 +91,7 @@ in
     hardware.sane.configDir = mkOption {
       type = types.str;
       internal = true;
-      description = "The value of SANE_CONFIG_DIR.";
+      description = lib.mdDoc "The value of SANE_CONFIG_DIR.";
     };
 
     hardware.sane.netConf = mkOption {
@@ -124,13 +126,22 @@ in
       '';
     };
 
+    hardware.sane.openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Open ports needed for discovery of scanners on the local network, e.g.
+        needed for Canon scanners (BJNP protocol).
+      '';
+    };
+
     services.saned.enable = mkOption {
       type = types.bool;
       default = false;
       description = lib.mdDoc ''
         Enable saned network daemon for remote connection to scanners.
 
-        saned would be runned from `scanner` user; to allow
+        saned would be run from `scanner` user; to allow
         access to hardware that doesn't have `scanner` group
         you should add needed groups to this user.
       '';
@@ -156,9 +167,12 @@ in
 
       environment.systemPackages = backends;
       environment.sessionVariables = env;
+      environment.etc."sane-config".source = config.hardware.sane.configDir;
+      environment.etc."sane-libs".source = "${saneConfig}/lib/sane";
       services.udev.packages = backends;
 
       users.groups.scanner.gid = config.ids.gids.scanner;
+      networking.firewall.allowedUDPPorts = mkIf config.hardware.sane.openFirewall [ 8612 ];
     })
 
     (mkIf config.services.saned.enable {
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
index f01446c411ee..e737a4ce20de 100644
--- a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
@@ -68,8 +68,8 @@ in
   options = {
 
     hardware.sane.brscan4.enable =
-      mkEnableOption "Brother's brscan4 scan backend" // {
-      description = ''
+      mkEnableOption (lib.mdDoc "Brother's brscan4 scan backend") // {
+      description = lib.mdDoc ''
         When enabled, will automatically register the "brscan4" sane
         backend and bring configuration files to their expected location.
       '';
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
index 9d083a615a2c..f76ab701c5b9 100644
--- a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
@@ -33,7 +33,8 @@ in
 
 stdenv.mkDerivation {
 
-  name = "brscan4-etc-files-0.4.3-3";
+  pname = "brscan4-etc-files";
+  version = "0.4.3-3";
   src = "${brscan4}/opt/brother/scanner/brscan4";
 
   nativeBuildInputs = [ brscan4 ];
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
index 506cb8167eac..d29e0f542f55 100644
--- a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
@@ -68,7 +68,7 @@ in
   options = {
 
     hardware.sane.brscan5.enable =
-      mkEnableOption "the Brother brscan5 sane backend";
+      mkEnableOption (lib.mdDoc "the Brother brscan5 sane backend");
 
     hardware.sane.brscan5.netDevices = mkOption {
       default = {};
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
index d71a17f5ea6b..5b05694abc01 100644
--- a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
@@ -6,8 +6,8 @@ with lib;
   options = {
 
     hardware.sane.dsseries.enable =
-      mkEnableOption "Brother DSSeries scan backend" // {
-      description = ''
+      mkEnableOption (lib.mdDoc "Brother DSSeries scan backend") // {
+      description = lib.mdDoc ''
         When enabled, will automatically register the "dsseries" SANE backend.
 
         This supports the Brother DSmobile scanner series, including the
diff --git a/nixpkgs/nixos/modules/services/hardware/spacenavd.nix b/nixpkgs/nixos/modules/services/hardware/spacenavd.nix
index 69ca6f102efe..36f132439377 100644
--- a/nixpkgs/nixos/modules/services/hardware/spacenavd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/spacenavd.nix
@@ -8,7 +8,7 @@ in {
 
   options = {
     hardware.spacenavd = {
-      enable = mkEnableOption "spacenavd to support 3DConnexion devices";
+      enable = mkEnableOption (lib.mdDoc "spacenavd to support 3DConnexion devices");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/supergfxd.nix b/nixpkgs/nixos/modules/services/hardware/supergfxd.nix
new file mode 100644
index 000000000000..5ea05ac27716
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/supergfxd.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.supergfxd;
+  json = pkgs.formats.json { };
+in
+{
+  options = {
+    services.supergfxd = {
+      enable = lib.mkEnableOption (lib.mdDoc "Enable the supergfxd service");
+
+      settings = lib.mkOption {
+        type = lib.types.nullOr json.type;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/supergfxd.conf.
+          See https://gitlab.com/asus-linux/supergfxctl/#config-options-etcsupergfxdconf.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.supergfxctl ];
+
+    environment.etc."supergfxd.conf" = lib.mkIf (cfg.settings != null) {
+      source = json.generate "supergfxd.conf" cfg.settings;
+      mode = "0644";
+    };
+
+    services.dbus.enable = true;
+
+    systemd.packages = [ pkgs.supergfxctl ];
+    systemd.services.supergfxd.wantedBy = [ "multi-user.target" ];
+    systemd.services.supergfxd.path = [ pkgs.kmod ];
+
+    services.dbus.packages = [ pkgs.supergfxctl ];
+    services.udev.packages = [ pkgs.supergfxctl ];
+  };
+
+  meta.maintainers = pkgs.supergfxctl.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/thermald.nix b/nixpkgs/nixos/modules/services/hardware/thermald.nix
index b433f46f2865..6b694ede5885 100644
--- a/nixpkgs/nixos/modules/services/hardware/thermald.nix
+++ b/nixpkgs/nixos/modules/services/hardware/thermald.nix
@@ -9,7 +9,7 @@ in
   ###### interface
   options = {
     services.thermald = {
-      enable = mkEnableOption "thermald, the temperature management daemon";
+      enable = mkEnableOption (lib.mdDoc "thermald, the temperature management daemon");
 
       debug = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/hardware/thinkfan.nix b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
index 86dabe71a4fa..8fa7b456f20e 100644
--- a/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
@@ -43,24 +43,26 @@ let
       };
       query = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The query string used to match one or more ${name}s: can be
           a fullpath to the temperature file (single ${name}) or a fullpath
           to a driver directory (multiple ${name}s).
 
-          <note><para>
-            When multiple ${name}s match, the query can be restricted using the
-            <option>name</option> or <option>indices</option> options.
-          </para></note>
+          ::: {.note}
+          When multiple ${name}s match, the query can be restricted using the
+          {option}`name` or {option}`indices` options.
+          :::
         '';
       };
       indices = mkOption {
         type = with types; nullOr (listOf ints.unsigned);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A list of ${name}s to pick in case multiple ${name}s match the query.
 
-          <note><para>Indices start from 0.</para></note>
+          ::: {.note}
+          Indices start from 0.
+          :::
         '';
       };
     } // optionalAttrs (name == "sensor") {
@@ -81,18 +83,18 @@ let
     // { "${type}" = query; };
 
   syntaxNote = name: ''
-    <note><para>
-      This section slightly departs from the thinkfan.conf syntax.
-      The type and path must be specified like this:
-      <literal>
-        type = "tpacpi";
-        query = "/proc/acpi/ibm/${name}";
-      </literal>
-      instead of a single declaration like:
-      <literal>
-        - tpacpi: /proc/acpi/ibm/${name}
-      </literal>
-    </para></note>
+    ::: {.note}
+    This section slightly departs from the thinkfan.conf syntax.
+    The type and path must be specified like this:
+    ```
+      type = "tpacpi";
+      query = "/proc/acpi/ibm/${name}";
+    ```
+    instead of a single declaration like:
+    ```
+      - tpacpi: /proc/acpi/ibm/${name}
+    ```
+    :::
   '';
 
 in {
@@ -104,13 +106,13 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable thinkfan, a fan control program.
 
-          <note><para>
-            This module targets IBM/Lenovo thinkpads by default, for
-            other hardware you will have configure it more carefully.
-          </para></note>
+          ::: {.note}
+          This module targets IBM/Lenovo thinkpads by default, for
+          other hardware you will have configure it more carefully.
+          :::
         '';
         relatedPackages = [ "thinkfan" ];
       };
@@ -131,9 +133,11 @@ in {
             query = "/proc/acpi/ibm/thermal";
           }
         ];
-        description = ''
+        description = lib.mdDoc ''
           List of temperature sensors thinkfan will monitor.
-        '' + syntaxNote "thermal";
+
+          ${syntaxNote "thermal"}
+        '';
       };
 
       fans = mkOption {
@@ -143,9 +147,11 @@ in {
             query = "/proc/acpi/ibm/fan";
           }
         ];
-        description = ''
+        description = lib.mdDoc ''
           List of fans thinkfan will control.
-        '' + syntaxNote "fan";
+
+          ${syntaxNote "fan"}
+        '';
       };
 
       levels = mkOption {
diff --git a/nixpkgs/nixos/modules/services/hardware/throttled.nix b/nixpkgs/nixos/modules/services/hardware/throttled.nix
index 559b29627cb8..afca24d976e1 100644
--- a/nixpkgs/nixos/modules/services/hardware/throttled.nix
+++ b/nixpkgs/nixos/modules/services/hardware/throttled.nix
@@ -7,7 +7,7 @@ let
 in {
   options = {
     services.throttled = {
-      enable = mkEnableOption "fix for Intel CPU throttling";
+      enable = mkEnableOption (lib.mdDoc "fix for Intel CPU throttling");
 
       extraConfig = mkOption {
         type = types.str;
@@ -20,12 +20,12 @@ in {
   config = mkIf cfg.enable {
     systemd.packages = [ pkgs.throttled ];
     # The upstream package has this in Install, but that's not enough, see the NixOS manual
-    systemd.services.lenovo_fix.wantedBy = [ "multi-user.target" ];
+    systemd.services.throttled.wantedBy = [ "multi-user.target" ];
 
-    environment.etc."lenovo_fix.conf".source =
+    environment.etc."throttled.conf".source =
       if cfg.extraConfig != ""
-      then pkgs.writeText "lenovo_fix.conf" cfg.extraConfig
-      else "${pkgs.throttled}/etc/lenovo_fix.conf";
+      then pkgs.writeText "throttled.conf" cfg.extraConfig
+      else "${pkgs.throttled}/etc/throttled.conf";
 
     # Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs.
     # See https://github.com/erpalma/throttled/issues/215
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.md b/nixpkgs/nixos/modules/services/hardware/trezord.md
new file mode 100644
index 000000000000..58c244a44bc1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/trezord.md
@@ -0,0 +1,17 @@
+# Trezor {#trezor}
+
+Trezor is an open-source cryptocurrency hardware wallet and security token
+allowing secure storage of private keys.
+
+It offers advanced features such U2F two-factor authorization, SSH login
+through
+[Trezor SSH agent](https://wiki.trezor.io/Apps:SSH_agent),
+[GPG](https://wiki.trezor.io/GPG) and a
+[password manager](https://wiki.trezor.io/Trezor_Password_Manager).
+For more information, guides and documentation, see <https://wiki.trezor.io>.
+
+To enable Trezor support, add the following to your {file}`configuration.nix`:
+
+    services.trezord.enable = true;
+
+This will add all necessary udev rules and start Trezor Bridge.
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.nix b/nixpkgs/nixos/modules/services/hardware/trezord.nix
index 70c1fd09860e..b2217fc97124 100644
--- a/nixpkgs/nixos/modules/services/hardware/trezord.nix
+++ b/nixpkgs/nixos/modules/services/hardware/trezord.nix
@@ -8,7 +8,7 @@ in {
   ### docs
 
   meta = {
-    doc = ./trezord.xml;
+    doc = ./trezord.md;
   };
 
   ### interface
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.xml b/nixpkgs/nixos/modules/services/hardware/trezord.xml
deleted file mode 100644
index 972d409d9d0e..000000000000
--- a/nixpkgs/nixos/modules/services/hardware/trezord.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="trezor">
- <title>Trezor</title>
- <para>
-  Trezor is an open-source cryptocurrency hardware wallet and security token
-  allowing secure storage of private keys.
- </para>
- <para>
-  It offers advanced features such U2F two-factor authorization, SSH login
-  through
-  <link xlink:href="https://wiki.trezor.io/Apps:SSH_agent">Trezor SSH agent</link>,
-  <link xlink:href="https://wiki.trezor.io/GPG">GPG</link> and a
-  <link xlink:href="https://wiki.trezor.io/Trezor_Password_Manager">password manager</link>.
-  For more information, guides and documentation, see <link xlink:href="https://wiki.trezor.io"/>.
- </para>
- <para>
-  To enable Trezor support, add the following to your <filename>configuration.nix</filename>:
-<programlisting>
-<xref linkend="opt-services.trezord.enable"/> = true;
-</programlisting>
-  This will add all necessary udev rules and start Trezor Bridge.
- </para>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/hardware/udev.nix b/nixpkgs/nixos/modules/services/hardware/udev.nix
index 1723cb508485..94406b60b29c 100644
--- a/nixpkgs/nixos/modules/services/hardware/udev.nix
+++ b/nixpkgs/nixos/modules/services/hardware/udev.nix
@@ -16,16 +16,6 @@ let
   '';
 
 
-  # networkd link files are used early by udev to set up interfaces early.
-  # This must be done in stage 1 to avoid race conditions between udev and
-  # network daemons.
-  # TODO move this into the initrd-network module when it exists
-  initrdLinkUnits = pkgs.runCommand "initrd-link-units" {} ''
-    mkdir -p $out
-    ln -s ${udev}/lib/systemd/network/*.link $out/
-    ${lib.concatMapStringsSep "\n" (file: "ln -s ${file} $out/") (lib.mapAttrsToList (n: v: "${v.unit}/${n}") (lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units))}
-  '';
-
   extraUdevRules = pkgs.writeTextFile {
     name = "extra-udev-rules";
     text = cfg.extraRules;
@@ -46,6 +36,11 @@ let
     SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
   '';
 
+  nixosInitrdRules = ''
+    # Mark dm devices as db_persist so that they are kept active after switching root
+    SUBSYSTEM=="block", KERNEL=="dm-[0-9]*", ACTION=="add|change", OPTIONS+="db_persist"
+  '';
+
   # Perform substitutions in all udev rules files.
   udevRulesFor = { name, udevPackages, udevPath, udev, systemd, binPackages, initrdBin ? null }: pkgs.runCommand name
     { preferLocalBuild = true;
@@ -165,16 +160,16 @@ let
 
       echo "Generating hwdb database..."
       # hwdb --update doesn't return error code even on errors!
-      res="$(${pkgs.buildPackages.udev}/bin/udevadm hwdb --update --root=$(pwd) 2>&1)"
+      res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)"
       echo "$res"
       [ -z "$(echo "$res" | egrep '^Error')" ]
       mv etc/udev/hwdb.bin $out
     '';
 
-  compressFirmware = if config.boot.kernelPackages.kernelAtLeast "5.3" then
-    pkgs.compressFirmwareXz
+  compressFirmware = firmware: if (config.boot.kernelPackages.kernelAtLeast "5.3" && (firmware.compressFirmware or true)) then
+    pkgs.compressFirmwareXz firmware
   else
-    id;
+    id firmware;
 
   # Udev has a 512-character limit for ENV{PATH}, so create a symlink
   # tree to work around this.
@@ -192,7 +187,6 @@ in
   ###### interface
 
   options = {
-
     boot.hardwareScan = mkOption {
       type = types.bool;
       default = true;
@@ -205,6 +199,9 @@ in
     };
 
     services.udev = {
+      enable = mkEnableOption (lib.mdDoc "udev") // {
+        default = true;
+      };
 
       packages = mkOption {
         type = types.listOf types.path;
@@ -222,8 +219,8 @@ in
       path = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
-          Packages added to the <envar>PATH</envar> environment variable when
+        description = lib.mdDoc ''
+          Packages added to the {env}`PATH` environment variable when
           executing programs from Udev rules.
         '';
       };
@@ -300,13 +297,13 @@ in
         type = types.listOf types.path;
         default = [];
         visible = false;
-        description = ''
-          <emphasis>This will only be used when systemd is used in stage 1.</emphasis>
+        description = lib.mdDoc ''
+          *This will only be used when systemd is used in stage 1.*
 
-          List of packages containing <command>udev</command> rules that will be copied to stage 1.
+          List of packages containing {command}`udev` rules that will be copied to stage 1.
           All files found in
-          <filename>«pkg»/etc/udev/rules.d</filename> and
-          <filename>«pkg»/lib/udev/rules.d</filename>
+          {file}`«pkg»/etc/udev/rules.d` and
+          {file}`«pkg»/lib/udev/rules.d`
           will be included.
         '';
       };
@@ -315,8 +312,8 @@ in
         type = types.listOf types.path;
         default = [];
         visible = false;
-        description = ''
-          <emphasis>This will only be used when systemd is used in stage 1.</emphasis>
+        description = lib.mdDoc ''
+          *This will only be used when systemd is used in stage 1.*
 
           Packages to search for binaries that are referenced by the udev rules in stage 1.
           This list always contains /bin of the initrd.
@@ -345,7 +342,7 @@ in
 
   ###### implementation
 
-  config = mkIf (!config.boot.isContainer) {
+  config = mkIf cfg.enable {
 
     services.udev.extraRules = nixosRules;
 
@@ -362,8 +359,10 @@ in
         EOF
       '';
 
+    boot.initrd.services.udev.rules = nixosInitrdRules;
+
     boot.initrd.systemd.additionalUpstreamUnits = [
-      # TODO: "initrd-udevadm-cleanup-db.service" is commented out because of https://github.com/systemd/systemd/issues/12953
+      "initrd-udevadm-cleanup-db.service"
       "systemd-udevd-control.socket"
       "systemd-udevd-kernel.socket"
       "systemd-udevd.service"
@@ -389,7 +388,6 @@ in
         systemd = config.boot.initrd.systemd.package;
         binPackages = config.boot.initrd.services.udev.binPackages ++ [ config.boot.initrd.systemd.contents."/bin".source ];
       };
-      "/etc/systemd/network".source = initrdLinkUnits;
     };
     # Insert initrd rules
     boot.initrd.services.udev.packages = [
diff --git a/nixpkgs/nixos/modules/services/hardware/udisks2.nix b/nixpkgs/nixos/modules/services/hardware/udisks2.nix
index 988e975d7e66..c53dbf477742 100644
--- a/nixpkgs/nixos/modules/services/hardware/udisks2.nix
+++ b/nixpkgs/nixos/modules/services/hardware/udisks2.nix
@@ -1,10 +1,9 @@
 # Udisks daemon.
-
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
+  cfg = config.services.udisks2;
   settingsFormat = pkgs.formats.ini {
     listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
   };
@@ -19,7 +18,17 @@ in
 
     services.udisks2 = {
 
-      enable = mkEnableOption "udisks2, a DBus service that allows applications to query and manipulate storage devices.";
+      enable = mkEnableOption (mdDoc "udisks2, a DBus service that allows applications to query and manipulate storage devices");
+
+      mountOnMedia = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc ''
+          When enabled, instructs udisks2 to mount removable drives under `/media/` directory, instead of the
+          default, ACL-controlled `/run/media/$USER/`. Since `/media/` is not mounted as tmpfs by default, it
+          requires cleanup to get rid of stale mountpoints; enabling this option will take care of this at boot.
+        '';
+      };
 
       settings = mkOption rec {
         type = types.attrsOf settingsFormat.type;
@@ -44,7 +53,7 @@ in
           };
         };
         '';
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Options passed to udisksd.
           See [here](http://manpages.ubuntu.com/manpages/latest/en/man5/udisks2.conf.5.html) and
           drive configuration in [here](http://manpages.ubuntu.com/manpages/latest/en/man8/udisks.8.html) for supported options.
@@ -62,16 +71,26 @@ in
 
     environment.systemPackages = [ pkgs.udisks2 ];
 
-    environment.etc = mapAttrs' (name: value: nameValuePair "udisks2/${name}" { source = value; } ) configFiles;
+    environment.etc = (mapAttrs' (name: value: nameValuePair "udisks2/${name}" { source = value; } ) configFiles) // {
+      # We need to make sure /etc/libblockdev/conf.d is populated to avoid
+      # warnings
+      "libblockdev/conf.d/00-default.cfg".source = "${pkgs.libblockdev}/etc/libblockdev/conf.d/00-default.cfg";
+      "libblockdev/conf.d/10-lvm-dbus.cfg".source = "${pkgs.libblockdev}/etc/libblockdev/conf.d/10-lvm-dbus.cfg";
+    };
 
     security.polkit.enable = true;
 
     services.dbus.packages = [ pkgs.udisks2 ];
 
-    systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ];
+    systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ]
+      ++ optional cfg.mountOnMedia "D! /media 0755 root root -";
 
     services.udev.packages = [ pkgs.udisks2 ];
 
+    services.udev.extraRules = optionalString cfg.mountOnMedia ''
+      ENV{ID_FS_USAGE}=="filesystem", ENV{UDISKS_FILESYSTEM_SHARED}="1"
+    '';
+
     systemd.packages = [ pkgs.udisks2 ];
   };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/undervolt.nix b/nixpkgs/nixos/modules/services/hardware/undervolt.nix
index 2bf37b411260..944777475401 100644
--- a/nixpkgs/nixos/modules/services/hardware/undervolt.nix
+++ b/nixpkgs/nixos/modules/services/hardware/undervolt.nix
@@ -5,8 +5,8 @@ let
   cfg = config.services.undervolt;
 
   mkPLimit = limit: window:
-    if (isNull limit && isNull window) then null
-    else assert asserts.assertMsg (!isNull limit && !isNull window) "Both power limit and window must be set";
+    if (limit == null && window == null) then null
+    else assert asserts.assertMsg (limit != null && window != null) "Both power limit and window must be set";
       "${toString limit} ${toString window}";
   cliArgs = lib.cli.toGNUCommandLine {} {
     inherit (cfg)
@@ -33,11 +33,11 @@ let
 in
 {
   options.services.undervolt = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
        Undervolting service for Intel CPUs.
 
        Warning: This service is not endorsed by Intel and may permanently damage your hardware. Use at your own risk!
-    '';
+    '');
 
     verbose = mkOption {
       type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/hardware/upower.nix b/nixpkgs/nixos/modules/services/hardware/upower.nix
index 54208158b1ab..aacc8a63dbeb 100644
--- a/nixpkgs/nixos/modules/services/hardware/upower.nix
+++ b/nixpkgs/nixos/modules/services/hardware/upower.nix
@@ -39,7 +39,7 @@ in
       enableWattsUpPro = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the Watts Up Pro device.
 
           The Watts Up Pro contains a generic FTDI USB device without a specific
@@ -49,10 +49,8 @@ in
 
           The generic FTDI device is known to also be used on:
 
-          <itemizedlist>
-            <listitem><para>Sparkfun FT232 breakout board</para></listitem>
-            <listitem><para>Parallax Propeller</para></listitem>
-          </itemizedlist>
+          - Sparkfun FT232 breakout board
+          - Parallax Propeller
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix b/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
index b4c954906dd3..9466ea26995b 100644
--- a/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
@@ -13,6 +13,7 @@ in
 
 {
   options.services.usbmuxd = {
+
     enable = mkOption {
       type = types.bool;
       default = false;
@@ -39,6 +40,15 @@ in
         The group usbmuxd should use to run after startup.
       '';
     };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.usbmuxd;
+      defaultText = literalExpression "pkgs.usbmuxd";
+      description = lib.mdDoc "Which package to use for the usbmuxd daemon.";
+      relatedPackages = [ "usbmuxd" "usbmuxd2" ];
+    };
+
   };
 
   config = mkIf cfg.enable {
@@ -68,7 +78,7 @@ in
         # Trigger the udev rule manually. This doesn't require replugging the
         # device when first enabling the option to get it to work
         ExecStartPre = "${pkgs.udev}/bin/udevadm trigger -s usb -a idVendor=${apple}";
-        ExecStart = "${pkgs.usbmuxd}/bin/usbmuxd -U ${cfg.user} -f";
+        ExecStart = "${cfg.package}/bin/usbmuxd -U ${cfg.user} -v";
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/usbrelayd.nix b/nixpkgs/nixos/modules/services/hardware/usbrelayd.nix
index 471657190bbc..01d3a5ba8bee 100644
--- a/nixpkgs/nixos/modules/services/hardware/usbrelayd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/usbrelayd.nix
@@ -5,7 +5,7 @@ let
 in
 {
   options.services.usbrelayd = with types; {
-    enable = mkEnableOption "USB Relay MQTT daemon";
+    enable = mkEnableOption (lib.mdDoc "USB Relay MQTT daemon");
 
     broker = mkOption {
       type = str;
@@ -34,10 +34,6 @@ in
 
     services.udev.packages = [ pkgs.usbrelayd ];
     systemd.packages = [ pkgs.usbrelayd ];
-    users.users.usbrelay = {
-      isSystemUser = true;
-      group = "usbrelay";
-    };
     users.groups.usbrelay = { };
   };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/vdr.nix b/nixpkgs/nixos/modules/services/hardware/vdr.nix
index 4fc2905c0969..de63ed893b02 100644
--- a/nixpkgs/nixos/modules/services/hardware/vdr.nix
+++ b/nixpkgs/nixos/modules/services/hardware/vdr.nix
@@ -12,7 +12,7 @@ in {
   options = {
 
     services.vdr = {
-      enable = mkEnableOption "VDR. Please put config into ${libDir}";
+      enable = mkEnableOption (lib.mdDoc "VDR. Please put config into ${libDir}");
 
       package = mkOption {
         type = types.package;
@@ -34,7 +34,7 @@ in {
         description = lib.mdDoc "Additional command line arguments to pass to VDR.";
       };
 
-      enableLirc = mkEnableOption "LIRC";
+      enableLirc = mkEnableOption (lib.mdDoc "LIRC");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/home-automation/esphome.nix b/nixpkgs/nixos/modules/services/home-automation/esphome.nix
new file mode 100644
index 000000000000..d7dbb6f0b90e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/home-automation/esphome.nix
@@ -0,0 +1,136 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib)
+    literalExpression
+    maintainers
+    mkEnableOption
+    mkIf
+    mkOption
+    mdDoc
+    types
+    ;
+
+  cfg = config.services.esphome;
+
+  stateDir = "/var/lib/esphome";
+
+  esphomeParams =
+    if cfg.enableUnixSocket
+    then "--socket /run/esphome/esphome.sock"
+    else "--address ${cfg.address} --port ${toString cfg.port}";
+in
+{
+  meta.maintainers = with maintainers; [ oddlama ];
+
+  options.services.esphome = {
+    enable = mkEnableOption (mdDoc "esphome");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.esphome;
+      defaultText = literalExpression "pkgs.esphome";
+      description = mdDoc "The package to use for the esphome command.";
+    };
+
+    enableUnixSocket = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Listen on a unix socket `/run/esphome/esphome.sock` instead of the TCP port.";
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = mdDoc "esphome address";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 6052;
+      description = mdDoc "esphome port";
+    };
+
+    openFirewall = mkOption {
+      default = false;
+      type = types.bool;
+      description = mdDoc "Whether to open the firewall for the specified port.";
+    };
+
+    allowedDevices = mkOption {
+      default = ["char-ttyS" "char-ttyUSB"];
+      example = ["/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"];
+      description = lib.mdDoc ''
+        A list of device nodes to which {command}`esphome` has access to.
+        Refer to DeviceAllow in systemd.resource-control(5) for more information.
+        Beware that if a device is referred to by an absolute path instead of a device category,
+        it will only allow devices that already are plugged in when the service is started.
+      '';
+      type = types.listOf types.str;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall && !cfg.enableUnixSocket) [cfg.port];
+
+    systemd.services.esphome = {
+      description = "ESPHome dashboard";
+      after = ["network.target"];
+      wantedBy = ["multi-user.target"];
+      path = [cfg.package];
+
+      # platformio fails to determine the home directory when using DynamicUser
+      environment.PLATFORMIO_CORE_DIR = "${stateDir}/.platformio";
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}";
+        DynamicUser = true;
+        User = "esphome";
+        Group = "esphome";
+        WorkingDirectory = stateDir;
+        StateDirectory = "esphome";
+        StateDirectoryMode = "0750";
+        Restart = "on-failure";
+        RuntimeDirectory = mkIf cfg.enableUnixSocket "esphome";
+        RuntimeDirectoryMode = "0750";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        DevicePolicy = "closed";
+        DeviceAllow = map (d: "${d} rw") cfg.allowedDevices;
+        SupplementaryGroups = ["dialout"];
+        #NoNewPrivileges = true; # Implied by DynamicUser
+        PrivateUsers = true;
+        #PrivateTmp = true; # Implied by DynamicUser
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectSystem = "strict";
+        #RemoveIPC = true; # Implied by DynamicUser
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = false; # Required by platformio for chroot
+        RestrictRealtime = true;
+        #RestrictSUIDSGID = true; # Implied by DynamicUser
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "@mount" # Required by platformio for chroot
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/home-automation/evcc.nix b/nixpkgs/nixos/modules/services/home-automation/evcc.nix
new file mode 100644
index 000000000000..efa2cf244313
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/home-automation/evcc.nix
@@ -0,0 +1,96 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+
+with lib;
+
+let
+  cfg = config.services.evcc;
+
+  format = pkgs.formats.yaml {};
+  configFile = format.generate "evcc.yml" cfg.settings;
+
+  package = pkgs.evcc;
+in
+
+{
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  options.services.evcc = with types; {
+    enable = mkEnableOption (lib.mdDoc "EVCC, the extensible EV Charge Controller with PV integration");
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [];
+      description = lib.mdDoc ''
+        Extra arguments to pass to the evcc executable.
+      '';
+    };
+
+    settings = mkOption {
+      type = format.type;
+      description = lib.mdDoc ''
+        evcc configuration as a Nix attribute set.
+
+        Check for possible options in the sample [evcc.dist.yaml](https://github.com/andig/evcc/blob/${package.version}/evcc.dist.yaml].
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.evcc = {
+      after = [
+        "network-online.target"
+        "mosquitto.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      environment.HOME = "/var/lib/evcc";
+      path = with pkgs; [
+        glibc # requires getent
+      ];
+      serviceConfig = {
+        ExecStart = "${package}/bin/evcc --config ${configFile} ${escapeShellArgs cfg.extraArgs}";
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [
+          "char-ttyUSB"
+        ];
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups= true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        StateDirectory = "evcc";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+        User = "evcc";
+      };
+    };
+  };
+
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixpkgs/nixos/modules/services/home-automation/home-assistant.nix b/nixpkgs/nixos/modules/services/home-automation/home-assistant.nix
index 2b8128383631..abe0b93e412c 100644
--- a/nixpkgs/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixpkgs/nixos/modules/services/home-automation/home-assistant.nix
@@ -35,7 +35,10 @@ let
   #   ...
   # } ];
   usedPlatforms = config:
-    if isAttrs config then
+    # don't recurse into derivations possibly creating an infinite recursion
+    if isDerivation config then
+      [ ]
+    else if isAttrs config then
       optional (config ? platform) config.platform
       ++ concatMap usedPlatforms (attrValues config)
     else if isList config then
@@ -77,7 +80,7 @@ in {
   options.services.home-assistant = {
     # Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project.
     # https://github.com/home-assistant/architecture/blob/master/adr/0012-define-supported-installation-method.md#decision
-    enable = mkEnableOption "Home Assistant. Please note that this installation method is unsupported upstream";
+    enable = mkEnableOption (lib.mdDoc "Home Assistant. Please note that this installation method is unsupported upstream");
 
     configDir = mkOption {
       default = "/var/lib/hass";
@@ -126,10 +129,10 @@ in {
           psycopg2
         ];
       '';
-      description = ''
+      description = lib.mdDoc ''
         List of packages to add to propagatedBuildInputs.
 
-        A popular example is <package>python3Packages.psycopg2</package>
+        A popular example is `python3Packages.psycopg2`
         for PostgreSQL support in the recorder component.
       '';
     };
@@ -362,7 +365,7 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.openFirewall -> !isNull cfg.config;
+        assertion = cfg.openFirewall -> cfg.config != null;
         message = "openFirewall can only be used with a declarative config";
       }
     ];
@@ -409,13 +412,14 @@ in {
         (optionalString (cfg.config != null) copyConfig) +
         (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
       ;
+      environment.PYTHONPATH = package.pythonPath;
       serviceConfig = let
         # List of capabilities to equip home-assistant with, depending on configured components
         capabilities = lib.unique ([
           # Empty string first, so we will never accidentally have an empty capability bounding set
           # https://github.com/NixOS/nixpkgs/issues/120617#issuecomment-830685115
           ""
-        ] ++ lib.optionals (builtins.any useComponent [ "bluetooth" "bluetooth_le_tracker" "bluetooth_tracker" "eq3btsmart" "fjaraskupan" "govee_ble" "homekit_controller" "inkbird" "moat" "sensorpush" "switchbot" "xiaomi_ble" ]) [
+        ] ++ lib.optionals (builtins.any useComponent componentsUsingBluetooth) [
           # Required for interaction with hci devices and bluetooth sockets, identified by bluetooth-adapters dependency
           # https://www.home-assistant.io/integrations/bluetooth_le_tracker/#rootless-setup-on-core-installs
           "CAP_NET_ADMIN"
@@ -432,8 +436,45 @@ in {
         ]);
         componentsUsingBluetooth = [
           # Components that require the AF_BLUETOOTH address family
-          "bluetooth_tracker"
+          "august"
+          "august_ble"
+          "airthings_ble"
+          "aranet"
+          "bluemaestro"
+          "bluetooth"
+          "bluetooth_adapters"
           "bluetooth_le_tracker"
+          "bluetooth_tracker"
+          "bthome"
+          "default_config"
+          "eq3btsmart"
+          "eufylife_ble"
+          "esphome"
+          "fjaraskupan"
+          "govee_ble"
+          "homekit_controller"
+          "inkbird"
+          "keymitt_ble"
+          "led_ble"
+          "melnor"
+          "moat"
+          "mopeka"
+          "oralb"
+          "qingping"
+          "rapt_ble"
+          "ruuvi_gateway"
+          "ruuvitag_ble"
+          "sensirion_ble"
+          "sensorpro"
+          "sensorpush"
+          "shelly"
+          "snooz"
+          "switchbot"
+          "thermobeacon"
+          "thermopro"
+          "tilt_ble"
+          "xiaomi_ble"
+          "yalexs_ble"
         ];
         componentsUsingPing = [
           # Components that require the capset syscall for the ping wrapper
@@ -450,7 +491,6 @@ in {
           # mostly the ones using config flows already.
           "acer_projector"
           "alarmdecoder"
-          "arduino"
           "blackbird"
           "deconz"
           "dsmr"
@@ -464,12 +504,12 @@ in {
           "insteon"
           "kwb"
           "lacrosse"
-          "mhz19"
           "modbus"
           "modem_callerid"
           "mysensors"
           "nad"
           "numato"
+          "otbr"
           "rflink"
           "rfxtrx"
           "scsgate"
@@ -480,7 +520,6 @@ in {
           "usb"
           "velbus"
           "w800rf32"
-          "xbee"
           "zha"
           "zwave"
           "zwave_js"
diff --git a/nixpkgs/nixos/modules/services/home-automation/zigbee2mqtt.nix b/nixpkgs/nixos/modules/services/home-automation/zigbee2mqtt.nix
index 48474ab3face..796de3a491e4 100644
--- a/nixpkgs/nixos/modules/services/home-automation/zigbee2mqtt.nix
+++ b/nixpkgs/nixos/modules/services/home-automation/zigbee2mqtt.nix
@@ -18,7 +18,7 @@ in
   ];
 
   options.services.zigbee2mqtt = {
-    enable = mkEnableOption "enable zigbee2mqtt service";
+    enable = mkEnableOption (lib.mdDoc "zigbee2mqtt service");
 
     package = mkOption {
       description = lib.mdDoc "Zigbee2mqtt package to use";
@@ -119,9 +119,8 @@ in
         ];
         SystemCallArchitectures = "native";
         SystemCallFilter = [
-          "@system-service"
-          "~@privileged"
-          "~@resources"
+          "@system-service @pkey"
+          "~@privileged @resources"
         ];
         UMask = "0077";
       };
diff --git a/nixpkgs/nixos/modules/services/logging/awstats.nix b/nixpkgs/nixos/modules/services/logging/awstats.nix
index ad87c3bd907f..708775bfcf03 100644
--- a/nixpkgs/nixos/modules/services/logging/awstats.nix
+++ b/nixpkgs/nixos/modules/services/logging/awstats.nix
@@ -25,26 +25,26 @@ let
       logFile = mkOption {
         type = types.str;
         example = "/var/log/nginx/access.log";
-        description = ''
+        description = lib.mdDoc ''
           The log file to be scanned.
 
           For mail, set this to
-          <literal>
+          ```
           journalctl $OLD_CURSOR -u postfix.service | ''${pkgs.perl}/bin/perl ''${pkgs.awstats.out}/share/awstats/tools/maillogconvert.pl standard |
-          </literal>
+          ```
         '';
       };
 
       logFormat = mkOption {
         type = types.str;
         default = "1";
-        description = ''
+        description = lib.mdDoc ''
           The log format being used.
 
           For mail, set this to
-          <literal>
+          ```
           %time2 %email %email_r %host %host_r %method %url %code %bytesd
-          </literal>
+          ```
         '';
       };
 
@@ -69,7 +69,7 @@ let
       };
 
       webService = {
-        enable = mkEnableOption "awstats web service";
+        enable = mkEnableOption (lib.mdDoc "awstats web service");
 
         hostname = mkOption {
           type = types.str;
@@ -95,7 +95,7 @@ in
   ];
 
   options.services.awstats = {
-    enable = mkEnableOption "awstats";
+    enable = mkEnableOption (lib.mdDoc "awstats");
 
     dataDir = mkOption {
       type = types.path;
diff --git a/nixpkgs/nixos/modules/services/logging/filebeat.nix b/nixpkgs/nixos/modules/services/logging/filebeat.nix
index 3dc9df372ac5..5b5e7fd5ae89 100644
--- a/nixpkgs/nixos/modules/services/logging/filebeat.nix
+++ b/nixpkgs/nixos/modules/services/logging/filebeat.nix
@@ -18,7 +18,7 @@ in
 
     services.filebeat = {
 
-      enable = mkEnableOption "filebeat";
+      enable = mkEnableOption (lib.mdDoc "filebeat");
 
       package = mkOption {
         type = types.package;
@@ -159,27 +159,27 @@ in
                 type = types.listOf json.type;
                 default = [];
                 internal = true;
-                description = ''
+                description = lib.mdDoc ''
                   Inputs specify how Filebeat locates and processes
-                  input data. Use <xref linkend="opt-services.filebeat.inputs"/> instead.
+                  input data. Use [](#opt-services.filebeat.inputs) instead.
 
-                  See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html"/>.
+                  See <https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html>.
                 '';
               };
               modules = mkOption {
                 type = types.listOf json.type;
                 default = [];
                 internal = true;
-                description = ''
+                description = lib.mdDoc ''
                   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.
+                  Use [](#opt-services.filebeat.modules) instead.
 
-                  See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html"/>.
+                  See <https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html>.
                 '';
               };
             };
diff --git a/nixpkgs/nixos/modules/services/logging/fluentd.nix b/nixpkgs/nixos/modules/services/logging/fluentd.nix
index fe9f4b07e162..7764aafb2d1a 100644
--- a/nixpkgs/nixos/modules/services/logging/fluentd.nix
+++ b/nixpkgs/nixos/modules/services/logging/fluentd.nix
@@ -12,11 +12,7 @@ in {
   options = {
 
     services.fluentd = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether to enable fluentd.";
-      };
+      enable = mkEnableOption (lib.mdDoc "fluentd");
 
       config = mkOption {
         type = types.lines;
diff --git a/nixpkgs/nixos/modules/services/logging/graylog.nix b/nixpkgs/nixos/modules/services/logging/graylog.nix
index 9f7160b3e873..1eb51c50ff79 100644
--- a/nixpkgs/nixos/modules/services/logging/graylog.nix
+++ b/nixpkgs/nixos/modules/services/logging/graylog.nix
@@ -33,12 +33,12 @@ in
 
     services.graylog = {
 
-      enable = mkEnableOption "Graylog";
+      enable = mkEnableOption (lib.mdDoc "Graylog");
 
       package = mkOption {
         type = types.package;
-        default = pkgs.graylog;
-        defaultText = literalExpression "pkgs.graylog";
+        default = if versionOlder config.system.stateVersion "23.05" then pkgs.graylog-3_3 else pkgs.graylog-5_0;
+        defaultText = literalExpression (if versionOlder config.system.stateVersion "23.05" then "pkgs.graylog-3_3" else "pkgs.graylog-5_0");
         description = lib.mdDoc "Graylog package to use.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/logging/heartbeat.nix b/nixpkgs/nixos/modules/services/logging/heartbeat.nix
index 72fbf41739df..a9ae11ec66e6 100644
--- a/nixpkgs/nixos/modules/services/logging/heartbeat.nix
+++ b/nixpkgs/nixos/modules/services/logging/heartbeat.nix
@@ -18,7 +18,7 @@ in
 
     services.heartbeat = {
 
-      enable = mkEnableOption "heartbeat";
+      enable = mkEnableOption (lib.mdDoc "heartbeat");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/logging/journalbeat.nix b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
index a38283ae1e41..e761380552de 100644
--- a/nixpkgs/nixos/modules/services/logging/journalbeat.nix
+++ b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
@@ -18,7 +18,7 @@ in
 
     services.journalbeat = {
 
-      enable = mkEnableOption "journalbeat";
+      enable = mkEnableOption (lib.mdDoc "journalbeat");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/logging/journalwatch.nix b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
index a315da3ea0ee..55e2d600ee4f 100644
--- a/nixpkgs/nixos/modules/services/logging/journalwatch.nix
+++ b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
@@ -239,7 +239,7 @@ in {
         Type = "oneshot";
         # requires a relative directory name to create beneath /var/lib
         StateDirectory = user;
-        StateDirectoryMode = 0750;
+        StateDirectoryMode = "0750";
         ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
         # lowest CPU and IO priority, but both still in best-effort class to prevent starvation
         Nice=19;
diff --git a/nixpkgs/nixos/modules/services/logging/logcheck.nix b/nixpkgs/nixos/modules/services/logging/logcheck.nix
index b1279f0fe586..8a277cea6e46 100644
--- a/nixpkgs/nixos/modules/services/logging/logcheck.nix
+++ b/nixpkgs/nixos/modules/services/logging/logcheck.nix
@@ -109,13 +109,7 @@ in
 {
   options = {
     services.logcheck = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = lib.mdDoc ''
-          Enable the logcheck cron job.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "logcheck cron job");
 
       user = mkOption {
         default = "logcheck";
diff --git a/nixpkgs/nixos/modules/services/logging/logrotate.nix b/nixpkgs/nixos/modules/services/logging/logrotate.nix
index 53230cc51e59..342ac5ec6e04 100644
--- a/nixpkgs/nixos/modules/services/logging/logrotate.nix
+++ b/nixpkgs/nixos/modules/services/logging/logrotate.nix
@@ -5,93 +5,9 @@ with lib;
 let
   cfg = config.services.logrotate;
 
-  # deprecated legacy compat settings
-  # these options will be removed before 22.11 in the following PR:
-  # https://github.com/NixOS/nixpkgs/pull/164169
-  pathOpts = { name, ... }: {
-    options = {
-      enable = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable log rotation for this path. This can be used to explicitly disable
-          logging that has been configured by NixOS.
-        '';
-      };
-
-      name = mkOption {
-        type = types.str;
-        internal = true;
-      };
-
-      path = mkOption {
-        type = with types; either str (listOf str);
-        default = name;
-        defaultText = "attribute name";
-        description = lib.mdDoc ''
-          The path to log files to be rotated.
-          Spaces are allowed and normal shell quoting rules apply,
-          with ', ", and \ characters supported.
-        '';
-      };
-
-      user = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc ''
-          The user account to use for rotation.
-        '';
-      };
-
-      group = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc ''
-          The group to use for rotation.
-        '';
-      };
-
-      frequency = mkOption {
-        type = types.enum [ "hourly" "daily" "weekly" "monthly" "yearly" ];
-        default = "daily";
-        description = lib.mdDoc ''
-          How often to rotate the logs.
-        '';
-      };
-
-      keep = mkOption {
-        type = types.int;
-        default = 20;
-        description = lib.mdDoc ''
-          How many rotations to keep.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = lib.mdDoc ''
-          Extra logrotate config options for this path. Refer to
-          <https://linux.die.net/man/8/logrotate> for details.
-        '';
-      };
-
-      priority = mkOption {
-        type = types.int;
-        default = 1000;
-        description = ''
-          Order of this logrotate block in relation to the others. The semantics are
-          the same as with `lib.mkOrder`. Smaller values have a greater priority.
-        '';
-      };
-    };
-
-    config.name = name;
-  };
-
   generateLine = n: v:
     if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null
-    else if builtins.elem n [ "extraConfig" "frequency" ] then "${v}\n"
+    else if builtins.elem n [ "frequency" ] then "${v}\n"
     else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ]
          then "${n}\n    ${v}\n  endscript\n"
     else if isInt v then "${n} ${toString v}\n"
@@ -110,25 +26,6 @@ let
         ${generateSection 2 settings}}
     '';
 
-  # below two mapPaths are compat functions
-  mapPathOptToSetting = n: v:
-    if n == "keep" then nameValuePair "rotate" v
-    else if n == "path" then nameValuePair "files" v
-    else nameValuePair n v;
-
-  mapPathsToSettings = path: pathOpts:
-    nameValuePair path (
-      filterAttrs (n: v: ! builtins.elem n [ "user" "group" "name" ] && v != "") (
-        (mapAttrs' mapPathOptToSetting pathOpts) //
-        {
-          su =
-            if pathOpts.user != null
-            then "${pathOpts.user} ${pathOpts.group}"
-            else null;
-        }
-      )
-    );
-
   settings = sortProperties (attrValues (filterAttrs (_: settings: settings.enable) (
     foldAttrs recursiveUpdate { } [
       {
@@ -139,15 +36,7 @@ let
           frequency = "weekly";
           rotate = 4;
         };
-        # compat section
-        extraConfig = {
-          enable = (cfg.extraConfig != "");
-          global = true;
-          extraConfig = cfg.extraConfig;
-          priority = 101;
-        };
       }
-      (mapAttrs' mapPathsToSettings cfg.paths)
       cfg.settings
       { header = { global = true; priority = 100; }; }
     ]
@@ -194,18 +83,19 @@ let
   };
 
   mailOption =
-    if foldr (n: a: a || (n.mail or false) != false) false (attrValues cfg.settings)
-    then "--mail=${pkgs.mailutils}/bin/mail"
-    else "";
+    optionalString (foldr (n: a: a || (n.mail or false) != false) false (attrValues cfg.settings))
+    "--mail=${pkgs.mailutils}/bin/mail";
 in
 {
   imports = [
-    (mkRenamedOptionModule [ "services" "logrotate" "config" ] [ "services" "logrotate" "extraConfig" ])
+    (mkRemovedOptionModule [ "services" "logrotate" "config" ] "Modify services.logrotate.settings.header instead")
+    (mkRemovedOptionModule [ "services" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.header instead")
+    (mkRemovedOptionModule [ "services" "logrotate" "paths" ] "Add attributes to services.logrotate.settings instead")
   ];
 
   options = {
     services.logrotate = {
-      enable = mkEnableOption "the logrotate systemd service" // {
+      enable = mkEnableOption (lib.mdDoc "the logrotate systemd service") // {
         default = foldr (n: a: a || n.enable) false (attrValues cfg.settings);
         defaultText = literalExpression "cfg.settings != {}";
       };
@@ -218,11 +108,30 @@ in
           or settings common to all further files settings.
           Refer to <https://linux.die.net/man/8/logrotate> for details.
         '';
+        example = literalExpression ''
+          {
+            # global options
+            header = {
+              dateext = true;
+            };
+            # example custom files
+            "/var/log/mylog.log" = {
+              frequency = "daily";
+              rotate = 3;
+            };
+            "multiple paths" = {
+               files = [
+                "/var/log/first*.log"
+                "/var/log/second.log"
+              ];
+            };
+          };
+          '';
         type = types.attrsOf (types.submodule ({ name, ... }: {
           freeformType = with types; attrsOf (nullOr (oneOf [ int bool str ]));
 
           options = {
-            enable = mkEnableOption "setting individual kill switch" // {
+            enable = mkEnableOption (lib.mdDoc "setting individual kill switch") // {
               default = true;
             };
 
@@ -253,14 +162,14 @@ in
               default = null;
               description = lib.mdDoc ''
                 How often to rotate the logs. Defaults to previously set global setting,
-                which itself defauts to weekly.
+                which itself defaults to weekly.
               '';
             };
 
             priority = mkOption {
               type = types.int;
               default = 1000;
-              description = ''
+              description = lib.mdDoc ''
                 Order of this logrotate block in relation to the others. The semantics are
                 the same as with `lib.mkOrder`. Smaller values are inserted first.
               '';
@@ -277,7 +186,7 @@ in
           A configuration file automatically generated by NixOS.
         '';
         description = lib.mdDoc ''
-          Override the configuration file used by MySQL. By default,
+          Override the configuration file used by logrotate. By default,
           NixOS generates one automatically from [](#opt-services.logrotate.settings).
         '';
         example = literalExpression ''
@@ -311,76 +220,10 @@ in
           in this case you can disable the failing check with this option.
         '';
       };
-
-      # deprecated legacy compat settings
-      paths = mkOption {
-        type = with types; attrsOf (submodule pathOpts);
-        default = { };
-        description = ''
-          Attribute set of paths to rotate. The order each block appears in the generated configuration file
-          can be controlled by the <link linkend="opt-services.logrotate.paths._name_.priority">priority</link> option
-          using the same semantics as `lib.mkOrder`. Smaller values have a greater priority.
-          This setting has been deprecated in favor of <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
-        '';
-        example = literalExpression ''
-          {
-            httpd = {
-              path = "/var/log/httpd/*.log";
-              user = config.services.httpd.user;
-              group = config.services.httpd.group;
-              keep = 7;
-            };
-
-            myapp = {
-              path = "/var/log/myapp/*.log";
-              user = "myuser";
-              group = "mygroup";
-              frequency = "weekly";
-              keep = 5;
-              priority = 1;
-            };
-          }
-        '';
-      };
-
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        description = lib.mdDoc ''
-          Extra contents to append to the logrotate configuration file. Refer to
-          <https://linux.die.net/man/8/logrotate> for details.
-          This setting has been deprecated in favor of
-          [logrotate settings](#opt-services.logrotate.settings).
-        '';
-      };
     };
   };
 
   config = mkIf cfg.enable {
-    assertions =
-      mapAttrsToList
-        (name: pathOpts:
-          {
-            assertion = (pathOpts.user != null) == (pathOpts.group != null);
-            message = ''
-              If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
-            '';
-          })
-        cfg.paths;
-
-    warnings =
-      (mapAttrsToList
-        (name: pathOpts: ''
-          Using config.services.logrotate.paths.${name} is deprecated and will become unsupported in a future release.
-          Please use services.logrotate.settings instead.
-        '')
-        cfg.paths
-      ) ++
-      (optional (cfg.extraConfig != "") ''
-        Using config.services.logrotate.extraConfig is deprecated and will become unsupported in a future release.
-        Please use services.logrotate.settings with globals=true instead.
-      '');
-
     systemd.services.logrotate = {
       description = "Logrotate Service";
       startAt = "hourly";
diff --git a/nixpkgs/nixos/modules/services/logging/promtail.nix b/nixpkgs/nixos/modules/services/logging/promtail.nix
index bdf98322fa4f..9db82fd42b28 100644
--- a/nixpkgs/nixos/modules/services/logging/promtail.nix
+++ b/nixpkgs/nixos/modules/services/logging/promtail.nix
@@ -12,7 +12,7 @@ let
   positionsFile = cfg.configuration.positions.filename;
 in {
   options.services.promtail = with types; {
-    enable = mkEnableOption "the Promtail ingresser";
+    enable = mkEnableOption (lib.mdDoc "the Promtail ingresser");
 
 
     configuration = mkOption {
diff --git a/nixpkgs/nixos/modules/services/logging/rsyslogd.nix b/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
index 21d6482d9ffd..207d416c1a88 100644
--- a/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
+++ b/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
@@ -48,10 +48,10 @@ in
       defaultConfig = mkOption {
         type = types.lines;
         default = defaultConf;
-        description = ''
-          The default <filename>syslog.conf</filename> file configures a
+        description = lib.mdDoc ''
+          The default {file}`syslog.conf` file configures a
           fairly standard setup of log files, which can be extended by
-          means of <varname>extraConfig</varname>.
+          means of {var}`extraConfig`.
         '';
       };
 
@@ -59,9 +59,9 @@ in
         type = types.lines;
         default = "";
         example = "news.* -/var/log/news";
-        description = ''
-          Additional text appended to <filename>syslog.conf</filename>,
-          i.e. the contents of <varname>defaultConfig</varname>.
+        description = lib.mdDoc ''
+          Additional text appended to {file}`syslog.conf`,
+          i.e. the contents of {var}`defaultConfig`.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/logging/syslogd.nix b/nixpkgs/nixos/modules/services/logging/syslogd.nix
index a51bf08e5d25..553973e255f7 100644
--- a/nixpkgs/nixos/modules/services/logging/syslogd.nix
+++ b/nixpkgs/nixos/modules/services/logging/syslogd.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.syslogd;
 
   syslogConf = pkgs.writeText "syslog.conf" ''
-    ${if (cfg.tty != "") then "kern.warning;*.err;authpriv.none /dev/${cfg.tty}" else ""}
+    ${optionalString (cfg.tty != "") "kern.warning;*.err;authpriv.none /dev/${cfg.tty}"}
     ${cfg.defaultConfig}
     ${cfg.extraConfig}
   '';
@@ -57,10 +57,10 @@ in
       defaultConfig = mkOption {
         type = types.lines;
         default = defaultConf;
-        description = ''
-          The default <filename>syslog.conf</filename> file configures a
+        description = lib.mdDoc ''
+          The default {file}`syslog.conf` file configures a
           fairly standard setup of log files, which can be extended by
-          means of <varname>extraConfig</varname>.
+          means of {var}`extraConfig`.
         '';
       };
 
@@ -76,9 +76,9 @@ in
         type = types.lines;
         default = "";
         example = "news.* -/var/log/news";
-        description = ''
-          Additional text appended to <filename>syslog.conf</filename>,
-          i.e. the contents of <varname>defaultConfig</varname>.
+        description = lib.mdDoc ''
+          Additional text appended to {file}`syslog.conf`,
+          i.e. the contents of {var}`defaultConfig`.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/logging/ulogd.nix b/nixpkgs/nixos/modules/services/logging/ulogd.nix
new file mode 100644
index 000000000000..065032b531c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/ulogd.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.ulogd;
+  settingsFormat = pkgs.formats.ini { };
+  settingsFile = settingsFormat.generate "ulogd.conf" cfg.settings;
+in {
+  options = {
+    services.ulogd = {
+      enable = mkEnableOption (lib.mdDoc "ulogd");
+
+      settings = mkOption {
+        example = {
+          global.stack = "stack=log1:NFLOG,base1:BASE,pcap1:PCAP";
+          log1.group = 2;
+          pcap1 = {
+            file = "/var/log/ulogd.pcap";
+            sync = 1;
+          };
+        };
+        type = settingsFormat.type;
+        default = { };
+        description = lib.mdDoc "Configuration for ulogd. See {file}`/share/doc/ulogd/` in `pkgs.ulogd.doc`.";
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ 1 3 5 7 8 ];
+        default = 5;
+        description = lib.mdDoc "Log level (1 = debug, 3 = info, 5 = notice, 7 = error, 8 = fatal)";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.ulogd = {
+      description = "Ulogd Daemon";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-pre.target" ];
+      before = [ "network-pre.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.ulogd}/bin/ulogd -c ${settingsFile} --verbose --loglevel ${toString cfg.logLevel}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/vector.nix b/nixpkgs/nixos/modules/services/logging/vector.nix
index 93d8550c31b2..f2edeabfc06f 100644
--- a/nixpkgs/nixos/modules/services/logging/vector.nix
+++ b/nixpkgs/nixos/modules/services/logging/vector.nix
@@ -6,7 +6,9 @@ let cfg = config.services.vector;
 in
 {
   options.services.vector = {
-    enable = mkEnableOption "Vector";
+    enable = mkEnableOption (lib.mdDoc "Vector");
+
+    package = mkPackageOptionMD pkgs "vector" { };
 
     journaldAccess = mkOption {
       type = types.bool;
@@ -26,13 +28,9 @@ in
   };
 
   config = mkIf cfg.enable {
+    # for cli usage
+    environment.systemPackages = [ pkgs.vector ];
 
-    users.groups.vector = { };
-    users.users.vector = {
-      description = "Vector service user";
-      group = "vector";
-      isSystemUser = true;
-    };
     systemd.services.vector = {
       description = "Vector event and log aggregator";
       wantedBy = [ "multi-user.target" ];
@@ -43,15 +41,16 @@ in
           format = pkgs.formats.toml { };
           conf = format.generate "vector.toml" cfg.settings;
           validateConfig = file:
-            pkgs.runCommand "validate-vector-conf" { } ''
-              ${pkgs.vector}/bin/vector validate --no-environment "${file}"
+          pkgs.runCommand "validate-vector-conf" {
+            nativeBuildInputs = [ pkgs.vector ];
+          } ''
+              vector validate --no-environment "${file}"
               ln -s "${file}" "$out"
             '';
         in
         {
-          ExecStart = "${pkgs.vector}/bin/vector --config ${validateConfig conf}";
-          User = "vector";
-          Group = "vector";
+          ExecStart = "${getExe cfg.package} --config ${validateConfig conf}";
+          DynamicUser = true;
           Restart = "no";
           StateDirectory = "vector";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixpkgs/nixos/modules/services/mail/davmail.nix b/nixpkgs/nixos/modules/services/mail/davmail.nix
index a01d8758c0eb..483f591a7268 100644
--- a/nixpkgs/nixos/modules/services/mail/davmail.nix
+++ b/nixpkgs/nixos/modules/services/mail/davmail.nix
@@ -25,7 +25,7 @@ in
 
   {
     options.services.davmail = {
-      enable = mkEnableOption "davmail, an MS Exchange gateway";
+      enable = mkEnableOption (lib.mdDoc "davmail, an MS Exchange gateway");
 
       url = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix b/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
index aa465891db21..6f9cbc4e9d4d 100644
--- a/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
+++ b/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
@@ -45,7 +45,7 @@ in
         type = types.str;
         example = "selector1";
         description =
-          ''
+          lib.mdDoc ''
             The selector to use for DKIM key identification.
 
             For example, if 'selector1' is used here, then for each domain
diff --git a/nixpkgs/nixos/modules/services/mail/dovecot.nix b/nixpkgs/nixos/modules/services/mail/dovecot.nix
index 4caf8dbfd2b0..21bafd859c3c 100644
--- a/nixpkgs/nixos/modules/services/mail/dovecot.nix
+++ b/nixpkgs/nixos/modules/services/mail/dovecot.nix
@@ -169,13 +169,13 @@ in
   ];
 
   options.services.dovecot2 = {
-    enable = mkEnableOption "the dovecot 2.x POP3/IMAP server";
+    enable = mkEnableOption (lib.mdDoc "the dovecot 2.x POP3/IMAP server");
 
-    enablePop3 = mkEnableOption "starting the POP3 listener (when Dovecot is enabled).";
+    enablePop3 = mkEnableOption (lib.mdDoc "starting the POP3 listener (when Dovecot is enabled)");
 
-    enableImap = mkEnableOption "starting the IMAP listener (when Dovecot is enabled)." // { default = true; };
+    enableImap = mkEnableOption (lib.mdDoc "starting the IMAP listener (when Dovecot is enabled)") // { default = true; };
 
-    enableLmtp = mkEnableOption "starting the LMTP listener (when Dovecot is enabled).";
+    enableLmtp = mkEnableOption (lib.mdDoc "starting the LMTP listener (when Dovecot is enabled)");
 
     protocols = mkOption {
       type = types.listOf types.str;
@@ -267,9 +267,9 @@ in
       description = lib.mdDoc "Default group to store mail for virtual users.";
     };
 
-    createMailUser = mkEnableOption ''automatically creating the user
-      given in <option>services.dovecot.user</option> and the group
-      given in <option>services.dovecot.group</option>.'' // { default = true; };
+    createMailUser = mkEnableOption (lib.mdDoc ''automatically creating the user
+      given in {option}`services.dovecot.user` and the group
+      given in {option}`services.dovecot.group`.'') // { default = true; };
 
     modules = mkOption {
       type = types.listOf types.package;
@@ -300,9 +300,9 @@ in
       description = lib.mdDoc "Path to the server's private key.";
     };
 
-    enablePAM = mkEnableOption "creating a own Dovecot PAM service and configure PAM user logins." // { default = true; };
+    enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins") // { default = true; };
 
-    enableDHE = mkEnableOption "enable ssl_dh and generation of primes for the key exchange." // { default = true; };
+    enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange") // { default = true; };
 
     sieveScripts = mkOption {
       type = types.attrsOf types.path;
@@ -310,7 +310,7 @@ in
       description = lib.mdDoc "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
     };
 
-    showPAMFailure = mkEnableOption "showing the PAM failure message on authentication error (useful for OTPW).";
+    showPAMFailure = mkEnableOption (lib.mdDoc "showing the PAM failure message on authentication error (useful for OTPW)");
 
     mailboxes = mkOption {
       type = with types; coercedTo
@@ -326,7 +326,7 @@ in
       description = lib.mdDoc "Configure mailboxes and auto create or subscribe them.";
     };
 
-    enableQuota = mkEnableOption "the dovecot quota service.";
+    enableQuota = mkEnableOption (lib.mdDoc "the dovecot quota service");
 
     quotaPort = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/mail/exim.nix b/nixpkgs/nixos/modules/services/mail/exim.nix
index cd0da4fc5098..1d1258913b67 100644
--- a/nixpkgs/nixos/modules/services/mail/exim.nix
+++ b/nixpkgs/nixos/modules/services/mail/exim.nix
@@ -116,8 +116,9 @@ in
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."exim.conf".source ];
       serviceConfig = {
-        ExecStart   = "${cfg.package}/bin/exim -bdf -q${cfg.queueRunnerInterval}";
-        ExecReload  = "${coreutils}/bin/kill -HUP $MAINPID";
+        ExecStart   = "!${cfg.package}/bin/exim -bdf -q${cfg.queueRunnerInterval}";
+        ExecReload  = "!${coreutils}/bin/kill -HUP $MAINPID";
+        User        = cfg.user;
       };
       preStart = ''
         if ! test -d ${cfg.spoolDir}; then
diff --git a/nixpkgs/nixos/modules/services/mail/goeland.nix b/nixpkgs/nixos/modules/services/mail/goeland.nix
new file mode 100644
index 000000000000..13092a65ed90
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/goeland.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.goeland;
+  tomlFormat = pkgs.formats.toml { };
+in
+{
+  options.services.goeland = {
+    enable = mkEnableOption (mdDoc "goeland");
+
+    settings = mkOption {
+      description = mdDoc ''
+        Configuration of goeland.
+        See the [example config file](https://github.com/slurdge/goeland/blob/master/cmd/asset/config.default.toml) for the available options.
+      '';
+      default = { };
+      type = tomlFormat.type;
+    };
+    schedule = mkOption {
+      type = types.str;
+      default = "12h";
+      example = "Mon, 00:00:00";
+      description = mdDoc "How often to run goeland, in systemd time format.";
+    };
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var/lib/goeland";
+      description = mdDoc ''
+        The data directory for goeland where the database will reside if using the unseen filter.
+        If left as the default value this directory will automatically be created before the goeland
+        server starts, otherwise you are responsible for ensuring the directory exists with
+        appropriate ownership and permissions.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.goeland.settings.database = "${cfg.stateDir}/goeland.db";
+
+    systemd.services.goeland = {
+      serviceConfig = let confFile = tomlFormat.generate "config.toml" cfg.settings; in mkMerge [
+        {
+          ExecStart = "${pkgs.goeland}/bin/goeland run -c ${confFile}";
+          User = "goeland";
+          Group = "goeland";
+        }
+        (mkIf (cfg.stateDir == "/var/lib/goeland") {
+          StateDirectory = "goeland";
+          StateDirectoryMode = "0750";
+        })
+      ];
+      startAt = cfg.schedule;
+    };
+
+    users.users.goeland = {
+      description = "goeland user";
+      group = "goeland";
+      isSystemUser = true;
+    };
+    users.groups.goeland = { };
+
+    warnings = optionals (hasAttr "password" cfg.settings.email) [
+      ''
+        It is not recommended to set the "services.goeland.settings.email.password"
+        option as it will be in cleartext in the Nix store.
+        Please use "services.goeland.settings.email.password_file" instead.
+      ''
+    ];
+  };
+
+  meta.maintainers = with maintainers; [ sweenu ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/listmonk.nix b/nixpkgs/nixos/modules/services/mail/listmonk.nix
new file mode 100644
index 000000000000..251362fdd89d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/listmonk.nix
@@ -0,0 +1,222 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.listmonk;
+  tomlFormat = pkgs.formats.toml { };
+  cfgFile = tomlFormat.generate "listmonk.toml" cfg.settings;
+  # Escaping is done according to https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
+  setDatabaseOption = key: value:
+    "UPDATE settings SET value = '${
+      lib.replaceStrings [ "'" ] [ "''" ] (builtins.toJSON value)
+    }' WHERE key = '${key}';";
+  updateDatabaseConfigSQL = pkgs.writeText "update-database-config.sql"
+    (concatStringsSep "\n" (mapAttrsToList setDatabaseOption
+      (if (cfg.database.settings != null) then
+        cfg.database.settings
+      else
+        { })));
+  updateDatabaseConfigScript =
+    pkgs.writeShellScriptBin "update-database-config.sh" ''
+      ${if cfg.database.mutableSettings then ''
+        if [ ! -f /var/lib/listmonk/.db_settings_initialized ]; then
+          ${pkgs.postgresql}/bin/psql -d listmonk -f ${updateDatabaseConfigSQL} ;
+          touch /var/lib/listmonk/.db_settings_initialized
+        fi
+      '' else
+        "${pkgs.postgresql}/bin/psql -d listmonk -f ${updateDatabaseConfigSQL}"}
+    '';
+
+  databaseSettingsOpts = with types; {
+    freeformType =
+      oneOf [ (listOf str) (listOf (attrsOf anything)) str int bool ];
+
+    options = {
+      "app.notify_emails" = mkOption {
+        type = listOf str;
+        default = [ ];
+        description = lib.mdDoc "Administrator emails for system notifications";
+      };
+
+      "privacy.exportable" = mkOption {
+        type = listOf str;
+        default = [ "profile" "subscriptions" "campaign_views" "link_clicks" ];
+        description = lib.mdDoc
+          "List of fields which can be exported through an automatic export request";
+      };
+
+      "privacy.domain_blocklist" = mkOption {
+        type = listOf str;
+        default = [ ];
+        description = lib.mdDoc
+          "E-mail addresses with these domains are disallowed from subscribing.";
+      };
+
+      smtp = mkOption {
+        type = listOf (submodule {
+          freeformType = with types; attrsOf (oneOf [ str int bool ]);
+
+          options = {
+            enabled = mkEnableOption (lib.mdDoc "this SMTP server for listmonk");
+            host = mkOption {
+              type = types.str;
+              description = lib.mdDoc "Hostname for the SMTP server";
+            };
+            port = mkOption {
+              type = types.port;
+              description = lib.mdDoc "Port for the SMTP server";
+            };
+            max_conns = mkOption {
+              type = types.int;
+              description = lib.mdDoc
+                "Maximum number of simultaneous connections, defaults to 1";
+              default = 1;
+            };
+            tls_type = mkOption {
+              type = types.enum [ "none" "STARTTLS" "TLS" ];
+              description =
+                lib.mdDoc "Type of TLS authentication with the SMTP server";
+            };
+          };
+        });
+
+        description = lib.mdDoc "List of outgoing SMTP servers";
+      };
+
+      # TODO: refine this type based on the smtp one.
+      "bounce.mailboxes" = mkOption {
+        type = listOf
+          (submodule { freeformType = with types; oneOf [ str int bool ]; });
+        default = [ ];
+        description = lib.mdDoc "List of bounce mailboxes";
+      };
+
+      messengers = mkOption {
+        type = listOf str;
+        default = [ ];
+        description = lib.mdDoc
+          "List of messengers, see: <https://github.com/knadh/listmonk/blob/master/models/settings.go#L64-L74> for options.";
+      };
+    };
+  };
+in {
+  ###### interface
+  options = {
+    services.listmonk = {
+      enable = mkEnableOption
+        (lib.mdDoc "Listmonk, this module assumes a reverse proxy to be set");
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc
+            "Create the PostgreSQL database and database user locally.";
+        };
+
+        settings = mkOption {
+          default = null;
+          type = with types; nullOr (submodule databaseSettingsOpts);
+          description = lib.mdDoc
+            "Dynamic settings in the PostgreSQL database, set by a SQL script, see <https://github.com/knadh/listmonk/blob/master/schema.sql#L177-L230> for details.";
+        };
+        mutableSettings = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Database settings will be reset to the value set in this module if this is not enabled.
+            Enable this if you want to persist changes you have done in the application.
+          '';
+        };
+      };
+      package = mkPackageOptionMD pkgs "listmonk" {};
+      settings = mkOption {
+        type = types.submodule { freeformType = tomlFormat.type; };
+        description = lib.mdDoc ''
+          Static settings set in the config.toml, see <https://github.com/knadh/listmonk/blob/master/config.toml.sample> for details.
+          You can set secrets using the secretFile option with environment variables following <https://listmonk.app/docs/configuration/#environment-variables>.
+        '';
+      };
+      secretFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = lib.mdDoc
+          "A file containing secrets as environment variables. See <https://listmonk.app/docs/configuration/#environment-variables> for details on supported values.";
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    # Default parameters from https://github.com/knadh/listmonk/blob/master/config.toml.sample
+    services.listmonk.settings."app".address = mkDefault "localhost:9000";
+    services.listmonk.settings."db" = mkMerge [
+      ({
+        max_open = mkDefault 25;
+        max_idle = mkDefault 25;
+        max_lifetime = mkDefault "300s";
+      })
+      (mkIf cfg.database.createLocally {
+        host = mkDefault "/run/postgresql";
+        port = mkDefault 5432;
+        user = mkDefault "listmonk";
+        database = mkDefault "listmonk";
+      })
+    ];
+
+    services.postgresql = mkIf cfg.database.createLocally {
+      enable = true;
+
+      ensureUsers = [{
+        name = "listmonk";
+        ensurePermissions = { "DATABASE listmonk" = "ALL PRIVILEGES"; };
+      }];
+
+      ensureDatabases = [ "listmonk" ];
+    };
+
+    systemd.services.listmonk = {
+      description = "Listmonk - newsletter and mailing list manager";
+      after = [ "network.target" ]
+        ++ optional cfg.database.createLocally "postgresql.service";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "exec";
+        EnvironmentFile = mkIf (cfg.secretFile != null) [ cfg.secretFile ];
+        ExecStartPre = [
+          # StateDirectory cannot be used when DynamicUser = true is set this way.
+          # Indeed, it will try to create all the folders and realize one of them already exist.
+          # Therefore, we have to create it ourselves.
+          ''${pkgs.coreutils}/bin/mkdir -p "''${STATE_DIRECTORY}/listmonk/uploads"''
+          "${cfg.package}/bin/listmonk --config ${cfgFile} --idempotent --install --upgrade --yes"
+          "${updateDatabaseConfigScript}/bin/update-database-config.sh"
+        ];
+        ExecStart = "${cfg.package}/bin/listmonk --config ${cfgFile}";
+
+        Restart = "on-failure";
+
+        StateDirectory = [ "listmonk" ];
+
+        User = "listmonk";
+        Group = "listmonk";
+        DynamicUser = true;
+        NoNewPrivileges = true;
+        CapabilityBoundingSet = "";
+        SystemCallArchitecture = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+        ProtectDevices = true;
+        ProtectControlGroups = true;
+        ProtectKernelTunables = true;
+        ProtectHome = true;
+        DeviceAllow = false;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        UMask = "0027";
+        MemoryDenyWriteExecute = true;
+        LockPersonality = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        ProtectKernelModules = true;
+        PrivateUsers = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/maddy.nix b/nixpkgs/nixos/modules/services/mail/maddy.nix
index 2f9abd3ed1f0..3b4a517fb859 100644
--- a/nixpkgs/nixos/modules/services/mail/maddy.nix
+++ b/nixpkgs/nixos/modules/services/mail/maddy.nix
@@ -13,8 +13,6 @@ let
     # 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
@@ -35,6 +33,7 @@ let
       }
       optional_step file /etc/maddy/aliases
     }
+
     msgpipeline local_routing {
       destination postmaster $(local_domains) {
         modify {
@@ -139,33 +138,33 @@ in {
   options = {
     services.maddy = {
 
-      enable = mkEnableOption "Maddy, a free an open source mail server";
+      enable = mkEnableOption (lib.mdDoc "Maddy, a free an open source mail server");
 
       user = mkOption {
         default = "maddy";
         type = with types; uniq string;
-        description = ''
+        description = lib.mdDoc ''
           User account under which maddy runs.
 
-          <note><para>
+          ::: {.note}
           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 = ''
+        description = lib.mdDoc ''
           Group account under which maddy runs.
 
-          <note><para>
+          ::: {.note}
           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>
+          :::
         '';
       };
 
@@ -203,17 +202,88 @@ in {
       config = mkOption {
         type = with types; nullOr lines;
         default = defaultConfig;
-        description = ''
+        description = lib.mdDoc ''
           Server configuration, see
-          <link xlink:href="https://maddy.email">https://maddy.email</link> for
+          [https://maddy.email](https://maddy.email) for
           more information. The default configuration of this module will setup
-          minimal maddy instance for mail transfer without TLS encryption.
-          <note><para>
+          minimal Maddy instance for mail transfer without TLS encryption.
+
+          ::: {.note}
           This should not be used in a production environment.
-          </para></note>
+          :::
         '';
       };
 
+      tls = {
+        loader = mkOption {
+          type = with types; nullOr (enum [ "off" "file" "acme" ]);
+          default = "off";
+          description = lib.mdDoc ''
+            TLS certificates are obtained by modules called "certificate
+            loaders".
+
+            The `file` loader module reads certificates from files specified by
+            the `certificates` option.
+
+            Alternatively the `acme` module can be used to automatically obtain
+            certificates using the ACME protocol.
+
+            Module configuration is done via the `tls.extraConfig` option.
+
+            Secrets such as API keys or passwords should not be supplied in
+            plaintext. Instead the `secrets` option can be used to read secrets
+            at runtime as environment variables. Secrets can be referenced with
+            `{env:VAR}`.
+          '';
+        };
+
+        certificates = mkOption {
+          type = with types; listOf (submodule {
+            options = {
+              keyPath = mkOption {
+                type = types.path;
+                example = "/etc/ssl/mx1.example.org.key";
+                description = lib.mdDoc ''
+                  Path to the private key used for TLS.
+                '';
+              };
+              certPath = mkOption {
+                type = types.path;
+                example = "/etc/ssl/mx1.example.org.crt";
+                description = lib.mdDoc ''
+                  Path to the certificate used for TLS.
+                '';
+              };
+            };
+          });
+          default = [];
+          example = lib.literalExpression ''
+            [{
+              keyPath = "/etc/ssl/mx1.example.org.key";
+              certPath = "/etc/ssl/mx1.example.org.crt";
+            }]
+          '';
+          description = lib.mdDoc ''
+            A list of attribute sets containing paths to TLS certificates and
+            keys. Maddy will use SNI if multiple pairs are selected.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = with types; nullOr lines;
+          description = lib.mdDoc ''
+            Arguments for the specified certificate loader.
+
+            In case the `tls` loader is set, the defaults are considered secure
+            and there is no need to change anything in most cases.
+            For available options see [upstream manual](https://maddy.email/reference/tls/).
+
+            For ACME configuration, see [following page](https://maddy.email/reference/tls-acme).
+          '';
+          default = "";
+        };
+      };
+
       openFirewall = mkOption {
         type = types.bool;
         default = false;
@@ -222,22 +292,126 @@ in {
         '';
       };
 
+      ensureAccounts = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = lib.mdDoc ''
+          List of IMAP accounts which get automatically created. Note that for
+          a complete setup, user credentials for these accounts are required
+          and can be created using the `ensureCredentials` option.
+          This option does not delete accounts which are not (anymore) listed.
+        '';
+        example = [
+          "user1@localhost"
+          "user2@localhost"
+        ];
+      };
+
+      ensureCredentials = mkOption {
+        default = {};
+        description = lib.mdDoc ''
+          List of user accounts which get automatically created if they don't
+          exist yet. Note that for a complete setup, corresponding mail boxes
+          have to get created using the `ensureAccounts` option.
+          This option does not delete accounts which are not (anymore) listed.
+        '';
+        example = {
+          "user1@localhost".passwordFile = /secrets/user1-localhost;
+          "user2@localhost".passwordFile = /secrets/user2-localhost;
+        };
+        type = types.attrsOf (types.submodule {
+          options = {
+            passwordFile = mkOption {
+              type = types.path;
+              example = "/path/to/file";
+              default = null;
+              description = lib.mdDoc ''
+                Specifies the path to a file containing the
+                clear text password for the user.
+              '';
+            };
+          };
+        });
+      };
+
+      secrets = lib.mkOption {
+        type = with types; listOf path;
+        description = lib.mdDoc ''
+          A list of files containing the various secrets. Should be in the format
+          expected by systemd's `EnvironmentFile` directory. Secrets can be
+          referenced in the format `{env:VAR}`.
+        '';
+        default = [ ];
+      };
+
     };
   };
 
   config = mkIf cfg.enable {
 
+    assertions = [
+      {
+        assertion = cfg.tls.loader == "file" -> cfg.tls.certificates != [];
+        message = ''
+          If Maddy is configured to use TLS, tls.certificates with attribute sets
+          of certPath and keyPath must be provided.
+          Read more about obtaining TLS certificates here:
+          https://maddy.email/tutorials/setting-up/#tls-certificates
+        '';
+      }
+      {
+        assertion = cfg.tls.loader == "acme" -> cfg.tls.extraConfig != "";
+        message = ''
+          If Maddy is configured to obtain TLS certificates using the ACME
+          loader, extra configuration options must be supplied via
+          tls.extraConfig option.
+          See upstream documentation for more details:
+          https://maddy.email/reference/tls-acme
+        '';
+      }
+    ];
+
     systemd = {
+
       packages = [ pkgs.maddy ];
-      services.maddy = {
-        serviceConfig = {
-          User = cfg.user;
-          Group = cfg.group;
-          StateDirectory = [ "maddy" ];
+      services = {
+        maddy = {
+          serviceConfig = {
+            User = cfg.user;
+            Group = cfg.group;
+            StateDirectory = [ "maddy" ];
+            EnvironmentFile = cfg.secrets;
+          };
+          restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
+          wantedBy = [ "multi-user.target" ];
         };
-        restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
-        wantedBy = [ "multi-user.target" ];
+        maddy-ensure-accounts = {
+          script = ''
+            ${optionalString (cfg.ensureAccounts != []) ''
+              ${concatMapStrings (account: ''
+                if ! ${pkgs.maddy}/bin/maddyctl imap-acct list | grep "${account}"; then
+                  ${pkgs.maddy}/bin/maddyctl imap-acct create ${account}
+                fi
+              '') cfg.ensureAccounts}
+            ''}
+            ${optionalString (cfg.ensureCredentials != {}) ''
+              ${concatStringsSep "\n" (mapAttrsToList (name: cfg: ''
+                if ! ${pkgs.maddy}/bin/maddyctl creds list | grep "${name}"; then
+                  ${pkgs.maddy}/bin/maddyctl creds create --password $(cat ${escapeShellArg cfg.passwordFile}) ${name}
+                fi
+              '') cfg.ensureCredentials)}
+            ''}
+          '';
+          serviceConfig = {
+            Type = "oneshot";
+            User= "maddy";
+          };
+          after = [ "maddy.service" ];
+          wantedBy = [ "multi-user.target" ];
+        };
+
       };
+
     };
 
     environment.etc."maddy/maddy.conf" = {
@@ -246,6 +420,23 @@ in {
         $(primary_domain) = ${cfg.primaryDomain}
         $(local_domains) = ${toString cfg.localDomains}
         hostname ${cfg.hostname}
+
+        ${if (cfg.tls.loader == "file") then ''
+          tls file ${concatStringsSep " " (
+            map (x: x.certPath + " " + x.keyPath
+          ) cfg.tls.certificates)} ${optionalString (cfg.tls.extraConfig != "") ''
+            { ${cfg.tls.extraConfig} }
+          ''}
+        '' else if (cfg.tls.loader == "acme") then ''
+          tls {
+            loader acme {
+              ${cfg.tls.extraConfig}
+            }
+          }
+        '' else if (cfg.tls.loader == "off") then ''
+          tls off
+        '' else ""}
+
         ${cfg.config}
       '';
     };
diff --git a/nixpkgs/nixos/modules/services/mail/mail.nix b/nixpkgs/nixos/modules/services/mail/mail.nix
index fcc7ff6db91b..8e1424595b51 100644
--- a/nixpkgs/nixos/modules/services/mail/mail.nix
+++ b/nixpkgs/nixos/modules/services/mail/mail.nix
@@ -14,7 +14,7 @@ with lib;
         type = types.nullOr options.security.wrappers.type.nestedTypes.elemType;
         default = null;
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           Configuration for the sendmail setuid wapper.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/mail/mailcatcher.nix b/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
index 01f3a9776bbb..d0f4550c1926 100644
--- a/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
+++ b/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
@@ -11,7 +11,7 @@ in
   options = {
 
     services.mailcatcher = {
-      enable = mkEnableOption "MailCatcher";
+      enable = mkEnableOption (lib.mdDoc "MailCatcher");
 
       http.ip = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/mail/mailhog.nix b/nixpkgs/nixos/modules/services/mail/mailhog.nix
index defc58b80681..7ae62de291ba 100644
--- a/nixpkgs/nixos/modules/services/mail/mailhog.nix
+++ b/nixpkgs/nixos/modules/services/mail/mailhog.nix
@@ -27,7 +27,7 @@ in
   options = {
 
     services.mailhog = {
-      enable = mkEnableOption "MailHog";
+      enable = mkEnableOption (lib.mdDoc "MailHog");
 
       storage = mkOption {
         type = types.enum [ "maildir" "memory" ];
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.md b/nixpkgs/nixos/modules/services/mail/mailman.md
new file mode 100644
index 000000000000..55b61f8a2582
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailman.md
@@ -0,0 +1,82 @@
+# Mailman {#module-services-mailman}
+
+[Mailman](https://www.list.org) is free
+software for managing electronic mail discussion and e-newsletter
+lists. Mailman and its web interface can be configured using the
+corresponding NixOS module. Note that this service is best used with
+an existing, securely configured Postfix setup, as it does not automatically configure this.
+
+## Basic usage with Postfix {#module-services-mailman-basic-usage}
+
+For a basic configuration with Postfix as the MTA, the following settings are suggested:
+```
+{ config, ... }: {
+  services.postfix = {
+    enable = true;
+    relayDomains = ["hash:/var/lib/mailman/data/postfix_domains"];
+    sslCert = config.security.acme.certs."lists.example.org".directory + "/full.pem";
+    sslKey = config.security.acme.certs."lists.example.org".directory + "/key.pem";
+    config = {
+      transport_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
+      local_recipient_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
+    };
+  };
+  services.mailman = {
+    enable = true;
+    serve.enable = true;
+    hyperkitty.enable = true;
+    webHosts = ["lists.example.org"];
+    siteOwner = "mailman@example.org";
+  };
+  services.nginx.virtualHosts."lists.example.org".enableACME = true;
+  networking.firewall.allowedTCPPorts = [ 25 80 443 ];
+}
+```
+
+DNS records will also be required:
+
+  - `AAAA` and `A` records pointing to the host in question, in order for browsers to be able to discover the address of the web server;
+  - An `MX` record pointing to a domain name at which the host is reachable, in order for other mail servers to be able to deliver emails to the mailing lists it hosts.
+
+After this has been done and appropriate DNS records have been
+set up, the Postorius mailing list manager and the Hyperkitty
+archive browser will be available at
+https://lists.example.org/. Note that this setup is not
+sufficient to deliver emails to most email providers nor to
+avoid spam -- a number of additional measures for authenticating
+incoming and outgoing mails, such as SPF, DMARC and DKIM are
+necessary, but outside the scope of the Mailman module.
+
+## Using with other MTAs {#module-services-mailman-other-mtas}
+
+Mailman also supports other MTA, though with a little bit more configuration. For example, to use Mailman with Exim, you can use the following settings:
+```
+{ config, ... }: {
+  services = {
+    mailman = {
+      enable = true;
+      siteOwner = "mailman@example.org";
+      enablePostfix = false;
+      settings.mta = {
+        incoming = "mailman.mta.exim4.LMTP";
+        outgoing = "mailman.mta.deliver.deliver";
+        lmtp_host = "localhost";
+        lmtp_port = "8024";
+        smtp_host = "localhost";
+        smtp_port = "25";
+        configuration = "python:mailman.config.exim4";
+      };
+    };
+    exim = {
+      enable = true;
+      # You can configure Exim in a separate file to reduce configuration.nix clutter
+      config = builtins.readFile ./exim.conf;
+    };
+  };
+}
+```
+
+The exim config needs some special additions to work with Mailman. Currently
+NixOS can't manage Exim config with such granularity. Please refer to
+[Mailman documentation](https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html)
+for more info on configuring Mailman for working with Exim.
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.nix b/nixpkgs/nixos/modules/services/mail/mailman.nix
index 61b2b664d944..3707738154e7 100644
--- a/nixpkgs/nixos/modules/services/mail/mailman.nix
+++ b/nixpkgs/nixos/modules/services/mail/mailman.nix
@@ -92,7 +92,7 @@ in {
       };
 
       ldap = {
-        enable = mkEnableOption "LDAP auth";
+        enable = mkEnableOption (lib.mdDoc "LDAP auth");
         serverUri = mkOption {
           type = types.str;
           example = "ldaps://ldap.host";
@@ -111,7 +111,7 @@ in {
           type = types.str;
           example = "/run/secrets/ldap-bind";
           description = lib.mdDoc ''
-            Path to the file containing the bind password of the servie account
+            Path to the file containing the bind password of the service account
             defined by [](#opt-services.mailman.ldap.bindDn).
           '';
         };
@@ -260,7 +260,7 @@ in {
       };
 
       serve = {
-        enable = mkEnableOption "Automatic nginx and uwsgi setup for mailman-web";
+        enable = mkEnableOption (lib.mdDoc "Automatic nginx and uwsgi setup for mailman-web");
 
         virtualRoot = mkOption {
           default = "/";
@@ -279,7 +279,7 @@ in {
       };
 
       hyperkitty = {
-        enable = mkEnableOption "the Hyperkitty archiver for Mailman";
+        enable = mkEnableOption (lib.mdDoc "the Hyperkitty archiver for Mailman");
 
         baseUrl = mkOption {
           type = types.str;
@@ -442,7 +442,7 @@ in {
       virtualHosts = lib.genAttrs cfg.webHosts (webHost: {
         locations = {
           ${cfg.serve.virtualRoot}.extraConfig = "uwsgi_pass unix:/run/mailman-web.socket;";
-          "${cfg.serve.virtualRoot}/static/".alias = webSettings.STATIC_ROOT + "/";
+          "${removeSuffix "/" cfg.serve.virtualRoot}/static/".alias = webSettings.STATIC_ROOT + "/";
         };
       });
     };
@@ -569,10 +569,14 @@ in {
           type = "normal";
           plugins = ["python3"];
           home = webEnv;
-          manage-script-name = true;
-          mount = "${cfg.serve.virtualRoot}=mailman_web.wsgi:application";
           http = "127.0.0.1:18507";
-        };
+        }
+        // (if cfg.serve.virtualRoot == "/"
+          then { module = "mailman_web.wsgi:application"; }
+          else {
+            mount = "${cfg.serve.virtualRoot}=mailman_web.wsgi:application";
+            manage-script-name = true;
+          });
         uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig);
       in {
         wantedBy = ["multi-user.target"];
@@ -637,7 +641,7 @@ in {
 
   meta = {
     maintainers = with lib.maintainers; [ lheckemann qyliss ma27 ];
-    doc = ./mailman.xml;
+    doc = ./mailman.md;
   };
 
 }
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.xml b/nixpkgs/nixos/modules/services/mail/mailman.xml
deleted file mode 100644
index 27247fb064f2..000000000000
--- a/nixpkgs/nixos/modules/services/mail/mailman.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-mailman">
-  <title>Mailman</title>
-  <para>
-    <link xlink:href="https://www.list.org">Mailman</link> is free
-    software for managing electronic mail discussion and e-newsletter
-    lists. Mailman and its web interface can be configured using the
-    corresponding NixOS module. Note that this service is best used with
-    an existing, securely configured Postfix setup, as it does not automatically configure this.
-  </para>
-
-  <section xml:id="module-services-mailman-basic-usage">
-    <title>Basic usage with Postfix</title>
-    <para>
-      For a basic configuration with Postfix as the MTA, the following settings are suggested:
-      <programlisting>{ config, ... }: {
-  services.postfix = {
-    enable = true;
-    relayDomains = ["hash:/var/lib/mailman/data/postfix_domains"];
-    sslCert = config.security.acme.certs."lists.example.org".directory + "/full.pem";
-    sslKey = config.security.acme.certs."lists.example.org".directory + "/key.pem";
-    config = {
-      transport_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
-      local_recipient_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
-    };
-  };
-  services.mailman = {
-    <link linkend="opt-services.mailman.enable">enable</link> = true;
-    <link linkend="opt-services.mailman.serve.enable">serve.enable</link> = true;
-    <link linkend="opt-services.mailman.hyperkitty.enable">hyperkitty.enable</link> = true;
-    <link linkend="opt-services.mailman.webHosts">webHosts</link> = ["lists.example.org"];
-    <link linkend="opt-services.mailman.siteOwner">siteOwner</link> = "mailman@example.org";
-  };
-  <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">services.nginx.virtualHosts."lists.example.org".enableACME</link> = true;
-  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 25 80 443 ];
-}</programlisting>
-    </para>
-    <para>
-      DNS records will also be required:
-      <itemizedlist>
-        <listitem><para><literal>AAAA</literal> and <literal>A</literal> records pointing to the host in question, in order for browsers to be able to discover the address of the web server;</para></listitem>
-        <listitem><para>An <literal>MX</literal> record pointing to a domain name at which the host is reachable, in order for other mail servers to be able to deliver emails to the mailing lists it hosts.</para></listitem>
-      </itemizedlist>
-    </para>
-    <para>
-      After this has been done and appropriate DNS records have been
-      set up, the Postorius mailing list manager and the Hyperkitty
-      archive browser will be available at
-      https://lists.example.org/. Note that this setup is not
-      sufficient to deliver emails to most email providers nor to
-      avoid spam -- a number of additional measures for authenticating
-      incoming and outgoing mails, such as SPF, DMARC and DKIM are
-      necessary, but outside the scope of the Mailman module.
-    </para>
-  </section>
-  <section xml:id="module-services-mailman-other-mtas">
-    <title>Using with other MTAs</title>
-    <para>
-      Mailman also supports other MTA, though with a little bit more configuration. For example, to use Mailman with Exim, you can use the following settings:
-      <programlisting>{ config, ... }: {
-  services = {
-    mailman = {
-      enable = true;
-      siteOwner = "mailman@example.org";
-      <link linkend="opt-services.mailman.enablePostfix">enablePostfix</link> = false;
-      settings.mta = {
-        incoming = "mailman.mta.exim4.LMTP";
-        outgoing = "mailman.mta.deliver.deliver";
-        lmtp_host = "localhost";
-        lmtp_port = "8024";
-        smtp_host = "localhost";
-        smtp_port = "25";
-        configuration = "python:mailman.config.exim4";
-      };
-    };
-    exim = {
-      enable = true;
-      # You can configure Exim in a separate file to reduce configuration.nix clutter
-      config = builtins.readFile ./exim.conf;
-    };
-  };
-}</programlisting>
-    </para>
-    <para>
-      The exim config needs some special additions to work with Mailman. Currently
-      NixOS can't manage Exim config with such granularity. Please refer to
-      <link xlink:href="https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html">Mailman documentation</link>
-      for more info on configuring Mailman for working with Exim.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/mail/nullmailer.nix b/nixpkgs/nixos/modules/services/mail/nullmailer.nix
index 336c76c98508..7c72229efb24 100644
--- a/nixpkgs/nixos/modules/services/mail/nullmailer.nix
+++ b/nixpkgs/nixos/modules/services/mail/nullmailer.nix
@@ -212,6 +212,9 @@ with lib;
 
     systemd.tmpfiles.rules = [
       "d /var/spool/nullmailer - ${cfg.user} - - -"
+      "d /var/spool/nullmailer/failed 750 ${cfg.user} - - -"
+      "d /var/spool/nullmailer/queue 750 ${cfg.user} - - -"
+      "d /var/spool/nullmailer/tmp 750 ${cfg.user} - - -"
     ];
 
     systemd.services.nullmailer = {
@@ -220,7 +223,6 @@ with lib;
       after = [ "network.target" ];
 
       preStart = ''
-        mkdir -p /var/spool/nullmailer/{queue,tmp,failed}
         rm -f /var/spool/nullmailer/trigger && mkfifo -m 660 /var/spool/nullmailer/trigger
       '';
 
diff --git a/nixpkgs/nixos/modules/services/mail/offlineimap.nix b/nixpkgs/nixos/modules/services/mail/offlineimap.nix
index 17c09df8f92a..64fa09e83612 100644
--- a/nixpkgs/nixos/modules/services/mail/offlineimap.nix
+++ b/nixpkgs/nixos/modules/services/mail/offlineimap.nix
@@ -7,18 +7,18 @@ let
 in {
 
   options.services.offlineimap = {
-    enable = mkEnableOption "OfflineIMAP, a software to dispose your mailbox(es) as a local Maildir(s)";
+    enable = mkEnableOption (lib.mdDoc "OfflineIMAP, a software to dispose your mailbox(es) as a local Maildir(s)");
 
     install = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install a user service for Offlineimap. Once
         the service is started, emails will be fetched automatically.
 
         The service must be manually started for each user with
         "systemctl --user start offlineimap" or globally through
-        <varname>services.offlineimap.enable</varname>.
+        {var}`services.offlineimap.enable`.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
index d46447a480a5..237f36945e4b 100644
--- a/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
+++ b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
@@ -22,10 +22,10 @@ with lib;
       };
 
       secretsFile = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The secret data used to encode the SRS address.
           to generate, use a command like:
-          <literal>for n in $(seq 5); do dd if=/dev/urandom count=1 bs=1024 status=none | sha256sum | sed 's/  -$//' | sed 's/^/          /'; done</literal>
+          `for n in $(seq 5); do dd if=/dev/urandom count=1 bs=1024 status=none | sha256sum | sed 's/  -$//' | sed 's/^/          /'; done`
         '';
         type = types.path;
         default = "/var/lib/pfix-srsd/secrets";
diff --git a/nixpkgs/nixos/modules/services/mail/postfix.nix b/nixpkgs/nixos/modules/services/mail/postfix.nix
index 75ef09dbc6ee..23c47aaca7e2 100644
--- a/nixpkgs/nixos/modules/services/mail/postfix.nix
+++ b/nixpkgs/nixos/modules/services/mail/postfix.nix
@@ -70,7 +70,7 @@ let
       privileged = mkOption {
         type = types.bool;
         example = true;
-        description = "";
+        description = lib.mdDoc "";
       };
 
       chroot = mkOption {
@@ -140,8 +140,8 @@ let
         type = types.listOf types.str;
         default = [];
         internal = true;
-        description = ''
-          The raw configuration line for the <filename>master.cf</filename>.
+        description = lib.mdDoc ''
+          The raw configuration line for the {file}`master.cf`.
         '';
       };
     };
@@ -234,12 +234,12 @@ let
 
   headerChecks = concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks) + cfg.extraHeaderChecks;
 
-  aliases = let seperator = if cfg.aliasMapType == "hash" then ":" else ""; in
+  aliases = let separator = optionalString (cfg.aliasMapType == "hash") ":"; in
     optionalString (cfg.postmasterAlias != "") ''
-      postmaster${seperator} ${cfg.postmasterAlias}
+      postmaster${separator} ${cfg.postmasterAlias}
     ''
     + optionalString (cfg.rootAlias != "") ''
-      root${seperator} ${cfg.rootAlias}
+      root${separator} ${cfg.rootAlias}
     ''
     + cfg.extraAliases
   ;
@@ -355,125 +355,125 @@ in
       setgidGroup = mkOption {
         type = types.str;
         default = "postdrop";
-        description = "
+        description = lib.mdDoc ''
           How to call postfix setgid group (for postdrop). Should
           be uniquely used group.
-        ";
+        '';
       };
 
       networks = mkOption {
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = ["192.168.0.1/24"];
-        description = "
+        description = lib.mdDoc ''
           Net masks for trusted - allowed to relay mail to third parties -
           hosts. Leave empty to use mynetworks_style configuration or use
           default (localhost-only).
-        ";
+        '';
       };
 
       networksStyle = mkOption {
         type = types.str;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Name of standard way of trusted network specification to use,
           leave blank if you specify it explicitly or if you want to use
           default (localhost-only).
-        ";
+        '';
       };
 
       hostname = mkOption {
         type = types.str;
         default = "";
-        description ="
+        description = lib.mdDoc ''
           Hostname to use. Leave blank to use just the hostname of machine.
           It should be FQDN.
-        ";
+        '';
       };
 
       domain = mkOption {
         type = types.str;
         default = "";
-        description ="
+        description = lib.mdDoc ''
           Domain to use. Leave blank to use hostname minus first component.
-        ";
+        '';
       };
 
       origin = mkOption {
         type = types.str;
         default = "";
-        description ="
+        description = lib.mdDoc ''
           Origin to use in outgoing e-mail. Leave blank to use hostname.
-        ";
+        '';
       };
 
       destination = mkOption {
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = ["localhost"];
-        description = "
+        description = lib.mdDoc ''
           Full (!) list of domains we deliver locally. Leave blank for
           acceptable Postfix default.
-        ";
+        '';
       };
 
       relayDomains = mkOption {
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = ["localdomain"];
-        description = "
+        description = lib.mdDoc ''
           List of domains we agree to relay to. Default is empty.
-        ";
+        '';
       };
 
       relayHost = mkOption {
         type = types.str;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Mail relay for outbound mail.
-        ";
+        '';
       };
 
       relayPort = mkOption {
         type = types.int;
         default = 25;
-        description = "
+        description = lib.mdDoc ''
           SMTP port for relay mail relay.
-        ";
+        '';
       };
 
       lookupMX = mkOption {
         type = types.bool;
         default = false;
-        description = "
+        description = lib.mdDoc ''
           Whether relay specified is just domain whose MX must be used.
-        ";
+        '';
       };
 
       postmasterAlias = mkOption {
         type = types.str;
         default = "root";
-        description = "
+        description = lib.mdDoc ''
           Who should receive postmaster e-mail. Multiple values can be added by
           separating values with comma.
-        ";
+        '';
       };
 
       rootAlias = mkOption {
         type = types.str;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Who should receive root e-mail. Blank for no redirection.
           Multiple values can be added by separating values with comma.
-        ";
+        '';
       };
 
       extraAliases = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
-        ";
+        '';
       };
 
       aliasMapType = mkOption {
@@ -497,9 +497,9 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the main.cf configuration file.
-        ";
+        '';
       };
 
       tlsTrustedAuthorities = mkOption {
@@ -527,9 +527,9 @@ in
         type = types.str;
         default = "";
         example = "+";
-        description = "
+        description = lib.mdDoc ''
           Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
-        ";
+        '';
       };
 
       canonical = mkOption {
@@ -543,9 +543,9 @@ in
       virtual = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Entries for the virtual alias map, cf. man-page virtual(5).
-        ";
+        '';
       };
 
       virtualMapType = mkOption {
@@ -572,9 +572,9 @@ in
       transport = mkOption {
         default = "";
         type = types.lines;
-        description = "
+        description = lib.mdDoc ''
           Entries for the transport map, cf. man-page transport(8).
-        ";
+        '';
       };
 
       dnsBlacklists = mkOption {
@@ -809,7 +809,7 @@ in
       // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
                                                            then "${cfg.relayHost}:${toString cfg.relayPort}"
                                                            else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
-      // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
+      // optionalAttrs (!config.networking.enableIPv6) { inet_protocols = mkDefault "ipv4"; }
       // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
       // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
       // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
diff --git a/nixpkgs/nixos/modules/services/mail/postgrey.nix b/nixpkgs/nixos/modules/services/mail/postgrey.nix
index 301bc69e1cac..fdfa08946ddf 100644
--- a/nixpkgs/nixos/modules/services/mail/postgrey.nix
+++ b/nixpkgs/nixos/modules/services/mail/postgrey.nix
@@ -15,12 +15,12 @@ with lib; let
         type = nullOr str;
         default = null;
         example = "127.0.0.1";
-        description = "The address to bind to. Localhost if null";
+        description = lib.mdDoc "The address to bind to. Localhost if null";
       };
       port = mkOption {
         type = natural';
         default = 10030;
-        description = "Tcp port to bind to";
+        description = lib.mdDoc "Tcp port to bind to";
       };
     };
   };
@@ -30,13 +30,13 @@ with lib; let
       path = mkOption {
         type = path;
         default = "/run/postgrey.sock";
-        description = "Path of the unix socket";
+        description = lib.mdDoc "Path of the unix socket";
       };
 
       mode = mkOption {
         type = str;
         default = "0777";
-        description = "Mode of the unix socket";
+        description = lib.mdDoc "Mode of the unix socket";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/mail/public-inbox.nix b/nixpkgs/nixos/modules/services/mail/public-inbox.nix
index 9af3914ebdc1..3380f0da181d 100644
--- a/nixpkgs/nixos/modules/services/mail/public-inbox.nix
+++ b/nixpkgs/nixos/modules/services/mail/public-inbox.nix
@@ -6,8 +6,6 @@ let
   cfg = config.services.public-inbox;
   stateDir = "/var/lib/public-inbox";
 
-  manref = name: vol: "<citerefentry><refentrytitle>${name}</refentrytitle><manvolnum>${toString vol}</manvolnum></citerefentry>";
-
   gitIni = pkgs.formats.gitIni { listsAsDuplicateKeys = true; };
   iniAtom = elemAt gitIni.type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped/*either*/.functor.wrapped 0;
 
@@ -18,7 +16,7 @@ let
     args = mkOption {
       type = with types; listOf str;
       default = [];
-      description = "Command-line arguments to pass to ${manref "public-inbox-${proto}d" 1}.";
+      description = lib.mdDoc "Command-line arguments to pass to {manpage}`public-inbox-${proto}d(1)`.";
     };
     port = mkOption {
       type = with types; nullOr (either str port);
@@ -34,13 +32,13 @@ let
       type = with types; nullOr str;
       default = null;
       example = "/path/to/fullchain.pem";
-      description = "Path to TLS certificate to use for connections to ${manref "public-inbox-${proto}d" 1}.";
+      description = lib.mdDoc "Path to TLS certificate to use for connections to {manpage}`public-inbox-${proto}d(1)`.";
     };
     key = mkOption {
       type = with types; nullOr str;
       default = null;
       example = "/path/to/key.pem";
-      description = "Path to TLS key to use for connections to ${manref "public-inbox-${proto}d" 1}.";
+      description = lib.mdDoc "Path to TLS key to use for connections to {manpage}`public-inbox-${proto}d(1)`.";
     };
   };
 
@@ -145,7 +143,7 @@ in
 
 {
   options.services.public-inbox = {
-    enable = mkEnableOption "the public-inbox mail archiver";
+    enable = mkEnableOption (lib.mdDoc "the public-inbox mail archiver");
     package = mkOption {
       type = types.package;
       default = pkgs.public-inbox;
@@ -204,15 +202,15 @@ in
         options.watch = mkOption {
           type = with types; listOf str;
           default = [];
-          description = "Paths for ${manref "public-inbox-watch" 1} to monitor for new mail.";
+          description = lib.mdDoc "Paths for {manpage}`public-inbox-watch(1)` to monitor for new mail.";
           example = [ "maildir:/path/to/test.example.com.git" ];
         };
         options.watchheader = mkOption {
           type = with types; nullOr str;
           default = null;
           example = "List-Id:<test@example.com>";
-          description = ''
-            If specified, ${manref "public-inbox-watch" 1} will only process
+          description = lib.mdDoc ''
+            If specified, {manpage}`public-inbox-watch(1)` will only process
             mail containing a matching header.
           '';
         };
@@ -226,10 +224,10 @@ in
       }));
     };
     imap = {
-      enable = mkEnableOption "the public-inbox IMAP server";
+      enable = mkEnableOption (lib.mdDoc "the public-inbox IMAP server");
     } // publicInboxDaemonOptions "imap" 993;
     http = {
-      enable = mkEnableOption "the public-inbox HTTP server";
+      enable = mkEnableOption (lib.mdDoc "the public-inbox HTTP server");
       mounts = mkOption {
         type = with types; listOf str;
         default = [ "/" ];
@@ -255,16 +253,16 @@ in
       };
     };
     mda = {
-      enable = mkEnableOption "the public-inbox Mail Delivery Agent";
+      enable = mkEnableOption (lib.mdDoc "the public-inbox Mail Delivery Agent");
       args = mkOption {
         type = with types; listOf str;
         default = [];
-        description = "Command-line arguments to pass to ${manref "public-inbox-mda" 1}.";
+        description = lib.mdDoc "Command-line arguments to pass to {manpage}`public-inbox-mda(1)`.";
       };
     };
-    postfix.enable = mkEnableOption "the integration into Postfix";
+    postfix.enable = mkEnableOption (lib.mdDoc "the integration into Postfix");
     nntp = {
-      enable = mkEnableOption "the public-inbox NNTP server";
+      enable = mkEnableOption (lib.mdDoc "the public-inbox NNTP server");
     } // publicInboxDaemonOptions "nntp" 563;
     spamAssassinRules = mkOption {
       type = with types; nullOr path;
@@ -283,18 +281,33 @@ in
           default = {};
           description = lib.mdDoc "public inboxes";
           type = types.submodule {
-            freeformType = with types; /*inbox name*/attrsOf (/*inbox option name*/attrsOf /*inbox option value*/iniAtom);
+            # Support both global options like `services.public-inbox.settings.publicinbox.imapserver`
+            # and inbox specific options like `services.public-inbox.settings.publicinbox.foo.address`.
+            freeformType = with types; attrsOf (oneOf [ iniAtom (attrsOf iniAtom) ]);
+
             options.css = mkOption {
               type = with types; listOf str;
               default = [];
               description = lib.mdDoc "The local path name of a CSS file for the PSGI web interface.";
             };
+            options.imapserver = mkOption {
+              type = with types; listOf str;
+              default = [];
+              example = [ "imap.public-inbox.org" ];
+              description = lib.mdDoc "IMAP URLs to this public-inbox instance";
+            };
             options.nntpserver = mkOption {
               type = with types; listOf str;
               default = [];
               example = [ "nntp://news.public-inbox.org" "nntps://news.public-inbox.org" ];
               description = lib.mdDoc "NNTP URLs to this public-inbox instance";
             };
+            options.pop3server = mkOption {
+              type = with types; listOf str;
+              default = [];
+              example = [ "pop.public-inbox.org" ];
+              description = lib.mdDoc "POP3 URLs to this public-inbox instance";
+            };
             options.sourceinfo = mkOption {
               type = with types; nullOr str;
               default = null;
@@ -314,16 +327,16 @@ in
         options.publicinboxmda.spamcheck = mkOption {
           type = with types; enum [ "spamc" "none" ];
           default = "none";
-          description = ''
-            If set to spamc, ${manref "public-inbox-watch" 1} will filter spam
+          description = lib.mdDoc ''
+            If set to spamc, {manpage}`public-inbox-watch(1)` will filter spam
             using SpamAssassin.
           '';
         };
         options.publicinboxwatch.spamcheck = mkOption {
           type = with types; enum [ "spamc" "none" ];
           default = "none";
-          description = ''
-            If set to spamc, ${manref "public-inbox-watch" 1} will filter spam
+          description = lib.mdDoc ''
+            If set to spamc, {manpage}`public-inbox-watch(1)` will filter spam
             using SpamAssassin.
           '';
         };
@@ -354,7 +367,7 @@ in
         };
       };
     };
-    openFirewall = mkEnableOption "opening the firewall when using a port option";
+    openFirewall = mkEnableOption (lib.mdDoc "opening the firewall when using a port option");
   };
   config = mkIf cfg.enable {
     assertions = [
diff --git a/nixpkgs/nixos/modules/services/mail/roundcube.nix b/nixpkgs/nixos/modules/services/mail/roundcube.nix
index d8adf53e48a9..22a4e3c451ab 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.php80.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
+  phpWithPspell = pkgs.php81.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
 in
 {
   options.services.roundcube = {
@@ -39,7 +39,7 @@ in
       '';
 
       description = lib.mdDoc ''
-        The package which contains roundcube's sources. Can be overriden to create
+        The package which contains roundcube's sources. Can be overridden to create
         an environment which contains roundcube and third-party plugins.
       '';
     };
@@ -70,7 +70,12 @@ in
       };
       passwordFile = mkOption {
         type = types.str;
-        description = lib.mdDoc "Password file for the postgresql connection. Must be readable by user `nginx`. Ignored if `database.host` is set to `localhost`, as peer authentication will be used.";
+        description = lib.mdDoc ''
+          Password file for the postgresql connection.
+          Must be formatted according to PostgreSQL .pgpass standard (see https://www.postgresql.org/docs/current/libpq-pgpass.html)
+          but only one line, no comments and readable by user `nginx`.
+          Ignored if `database.host` is set to `localhost`, as peer authentication will be used.
+        '';
       };
       dbname = mkOption {
         type = types.str;
@@ -92,7 +97,7 @@ in
       default = [];
       example = literalExpression "with pkgs.aspellDicts; [ en fr de ]";
       description = lib.mdDoc ''
-        List of aspell dictionnaries for spell checking. If empty, spell checking is disabled.
+        List of aspell dictionaries for spell checking. If empty, spell checking is disabled.
       '';
     };
 
@@ -123,7 +128,13 @@ in
     environment.etc."roundcube/config.inc.php".text = ''
       <?php
 
-      ${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"}
+      ${lib.optionalString (!localDB) ''
+        $password = file('${cfg.database.passwordFile}')[0];
+        $password = preg_split('~\\\\.(*SKIP)(*FAIL)|\:~s', $password);
+        $password = end($password);
+        $password = str_replace("\\:", ":", $password);
+        $password = str_replace("\\\\", "\\", $password);
+      ''}
 
       $config = array();
       $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
@@ -132,6 +143,8 @@ in
       $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
       $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
       $config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
+      # Roundcube uses PHP-FPM which has `PrivateTmp = true;`
+      $config['temp_dir'] = '/tmp';
       $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"};
       # by default, spellchecking uses a third-party cloud services
       $config['spellcheck_engine'] = 'pspell';
@@ -150,9 +163,13 @@ in
             root = cfg.package;
             index = "index.php";
             extraConfig = ''
-              location ~* \.php$ {
+              location ~* \.php(/|$) {
                 fastcgi_split_path_info ^(.+\.php)(/.+)$;
                 fastcgi_pass unix:${fpm.socket};
+
+                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+                fastcgi_param PATH_INFO       $fastcgi_path_info;
+
                 include ${config.services.nginx.package}/conf/fastcgi_params;
                 include ${pkgs.nginx}/conf/fastcgi.conf;
               }
@@ -217,6 +234,7 @@ in
         path = [ config.services.postgresql.package ];
       })
       {
+        after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         script = let
           psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}";
diff --git a/nixpkgs/nixos/modules/services/mail/rspamd.nix b/nixpkgs/nixos/modules/services/mail/rspamd.nix
index ed4d7a504412..f9be9024dd4f 100644
--- a/nixpkgs/nixos/modules/services/mail/rspamd.nix
+++ b/nixpkgs/nixos/modules/services/mail/rspamd.nix
@@ -13,24 +13,24 @@ let
       socket = mkOption {
         type = types.str;
         example = "localhost:11333";
-        description = ''
+        description = lib.mdDoc ''
           Socket for this worker to listen on in a format acceptable by rspamd.
         '';
       };
       mode = mkOption {
         type = types.str;
         default = "0644";
-        description = "Mode to set on unix socket";
+        description = lib.mdDoc "Mode to set on unix socket";
       };
       owner = mkOption {
         type = types.str;
         default = "${cfg.user}";
-        description = "Owner to set on unix socket";
+        description = lib.mdDoc "Owner to set on unix socket";
       };
       group = mkOption {
         type = types.str;
         default = "${cfg.group}";
-        description = "Group to set on unix socket";
+        description = lib.mdDoc "Group to set on unix socket";
       };
       rawEntry = mkOption {
         type = types.str;
@@ -227,7 +227,7 @@ in
 
     services.rspamd = {
 
-      enable = mkEnableOption "rspamd, the Rapid spam filtering system";
+      enable = mkEnableOption (lib.mdDoc "rspamd, the Rapid spam filtering system");
 
       debug = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/mail/rss2email.nix b/nixpkgs/nixos/modules/services/mail/rss2email.nix
index faef0fb1544d..bd5cfd437838 100644
--- a/nixpkgs/nixos/modules/services/mail/rss2email.nix
+++ b/nixpkgs/nixos/modules/services/mail/rss2email.nix
@@ -112,7 +112,6 @@ in {
     in
     {
       preStart = ''
-        cp -f ${conf} /var/lib/rss2email/conf.cfg
         if [ ! -f /var/lib/rss2email/db.json ]; then
           echo '{"version":2,"feeds":[]}' > /var/lib/rss2email/db.json
         fi
@@ -121,7 +120,7 @@ in {
       serviceConfig = {
         StateDirectory = "rss2email";
         ExecStart =
-          "${pkgs.rss2email}/bin/r2e -c /var/lib/rss2email/conf.cfg -d /var/lib/rss2email/db.json run";
+          "${pkgs.rss2email}/bin/r2e -c ${conf} -d /var/lib/rss2email/db.json run";
         User = "rss2email";
       };
     };
diff --git a/nixpkgs/nixos/modules/services/mail/schleuder.nix b/nixpkgs/nixos/modules/services/mail/schleuder.nix
index 80b37ac129d0..2991418dd804 100644
--- a/nixpkgs/nixos/modules/services/mail/schleuder.nix
+++ b/nixpkgs/nixos/modules/services/mail/schleuder.nix
@@ -18,8 +18,8 @@ let
 in
 {
   options.services.schleuder = {
-    enable = lib.mkEnableOption "Schleuder secure remailer";
-    enablePostfix = lib.mkEnableOption "automatic postfix integration" // { default = true; };
+    enable = lib.mkEnableOption (lib.mdDoc "Schleuder secure remailer");
+    enablePostfix = lib.mkEnableOption (lib.mdDoc "automatic postfix integration") // { default = true; };
     lists = lib.mkOption {
       description = lib.mdDoc ''
         List of list addresses that should be handled by Schleuder.
diff --git a/nixpkgs/nixos/modules/services/mail/spamassassin.nix b/nixpkgs/nixos/modules/services/mail/spamassassin.nix
index 153e3c000845..49d1d9315985 100644
--- a/nixpkgs/nixos/modules/services/mail/spamassassin.nix
+++ b/nixpkgs/nixos/modules/services/mail/spamassassin.nix
@@ -12,7 +12,7 @@ in
   options = {
 
     services.spamassassin = {
-      enable = mkEnableOption "the SpamAssassin daemon";
+      enable = mkEnableOption (lib.mdDoc "the SpamAssassin daemon");
 
       debug = mkOption {
         type = types.bool;
@@ -22,23 +22,26 @@ in
 
       config = mkOption {
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           The SpamAssassin local.cf config
 
           If you are using this configuration:
-            add_header all Status _YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_
+
+              add_header all Status _YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_
 
           Then you can Use this sieve filter:
-            require ["fileinto", "reject", "envelope"];
 
-            if header :contains "X-Spam-Flag" "YES" {
-              fileinto "spam";
-            }
+              require ["fileinto", "reject", "envelope"];
+
+              if header :contains "X-Spam-Flag" "YES" {
+                fileinto "spam";
+              }
 
           Or this procmail filter:
-            :0:
-            * ^X-Spam-Flag: YES
-            /var/vpopmail/domains/lastlog.de/js/.maildir/.spam/new
+
+              :0:
+              * ^X-Spam-Flag: YES
+              /var/vpopmail/domains/lastlog.de/js/.maildir/.spam/new
 
           To filter your messages based on the additional mail headers added by spamassassin.
         '';
diff --git a/nixpkgs/nixos/modules/services/mail/sympa.nix b/nixpkgs/nixos/modules/services/mail/sympa.nix
index 0014701d5831..7a5047b2bea5 100644
--- a/nixpkgs/nixos/modules/services/mail/sympa.nix
+++ b/nixpkgs/nixos/modules/services/mail/sympa.nix
@@ -80,7 +80,7 @@ in
   ###### interface
   options.services.sympa = with types; {
 
-    enable = mkEnableOption "Sympa mailing list manager";
+    enable = mkEnableOption (lib.mdDoc "Sympa mailing list manager");
 
     lang = mkOption {
       type = str;
@@ -239,10 +239,10 @@ in
       server = mkOption {
         type = enum [ "nginx" "none" ];
         default = "nginx";
-        description = ''
+        description = lib.mdDoc ''
           The webserver used for the Sympa web interface. Set it to `none` if you want to configure it yourself.
           Further nginx configuration can be done by adapting
-          <option>services.nginx.virtualHosts.«name»</option>.
+          {option}`services.nginx.virtualHosts.«name»`.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/mail/zeyple.nix b/nixpkgs/nixos/modules/services/mail/zeyple.nix
new file mode 100644
index 000000000000..e7f9ddd92dc2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/zeyple.nix
@@ -0,0 +1,125 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.zeyple;
+  ini = pkgs.formats.ini { };
+
+  gpgHome = pkgs.runCommand "zeyple-gpg-home" { } ''
+    mkdir -p $out
+    for file in ${lib.concatStringsSep " " cfg.keys}; do
+      ${config.programs.gnupg.package}/bin/gpg --homedir="$out" --import "$file"
+    done
+
+    # Remove socket files
+    rm -f $out/S.*
+  '';
+in {
+  options.services.zeyple = {
+    enable = mkEnableOption (lib.mdDoc "Zeyple, an utility program to automatically encrypt outgoing emails with GPG");
+
+    user = mkOption {
+      type = types.str;
+      default = "zeyple";
+      description = lib.mdDoc ''
+        User to run Zeyple as.
+
+        ::: {.note}
+        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.
+        :::
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "zeyple";
+      description = lib.mdDoc ''
+        Group to use to run Zeyple.
+
+        ::: {.note}
+        If left as the default value this group will automatically be created
+        on system activation, otherwise the sysadmin is responsible for
+        ensuring the user exists.
+        :::
+      '';
+    };
+
+    settings = mkOption {
+      type = ini.type;
+      default = { };
+      description = lib.mdDoc ''
+        Zeyple configuration. refer to
+        <https://github.com/infertux/zeyple/blob/master/zeyple/zeyple.conf.example>
+        for details on supported values.
+      '';
+    };
+
+    keys = mkOption {
+      type = with types; listOf path;
+      description = lib.mdDoc "List of public key files that will be imported by gpg.";
+    };
+
+    rotateLogs = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc "Whether to enable rotation of log files.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = optionalAttrs (cfg.group == "zeyple") { "${cfg.group}" = { }; };
+    users.users = optionalAttrs (cfg.user == "zeyple") {
+      "${cfg.user}" = {
+        isSystemUser = true;
+        group = cfg.group;
+      };
+    };
+
+    services.zeyple.settings = {
+      zeyple = mapAttrs (name: mkDefault) {
+        log_file = "/var/log/zeyple/zeyple.log";
+        force_encrypt = true;
+      };
+
+      gpg = mapAttrs (name: mkDefault) { home = "${gpgHome}"; };
+
+      relay = mapAttrs (name: mkDefault) {
+        host = "localhost";
+        port = 10026;
+      };
+    };
+
+    environment.etc."zeyple.conf".source = ini.generate "zeyple.conf" cfg.settings;
+
+    systemd.tmpfiles.rules = [ "f '${cfg.settings.zeyple.log_file}' 0600 ${cfg.user} ${cfg.group} - -" ];
+    services.logrotate = mkIf cfg.rotateLogs {
+      enable = true;
+      settings.zeyple = {
+        files = cfg.settings.zeyple.log_file;
+        frequency = "weekly";
+        rotate = 5;
+        compress = true;
+        copytruncate = true;
+      };
+    };
+
+    services.postfix.extraMasterConf = ''
+      zeyple    unix  -       n       n       -       -       pipe
+        user=${cfg.user} argv=${pkgs.zeyple}/bin/zeyple ''${recipient}
+
+      localhost:${toString cfg.settings.relay.port} inet  n       -       n       -       10      smtpd
+        -o content_filter=
+        -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
+        -o smtpd_helo_restrictions=
+        -o smtpd_client_restrictions=
+        -o smtpd_sender_restrictions=
+        -o smtpd_recipient_restrictions=permit_mynetworks,reject
+        -o mynetworks=127.0.0.0/8,[::1]/128
+        -o smtpd_authorized_xforward_hosts=127.0.0.0/8,[::1]/128
+    '';
+
+    services.postfix.extraConfig = "content_filter = zeyple";
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/matrix/appservice-discord.nix b/nixpkgs/nixos/modules/services/matrix/appservice-discord.nix
index c72a2123a923..f579c2529c0a 100644
--- a/nixpkgs/nixos/modules/services/matrix/appservice-discord.nix
+++ b/nixpkgs/nixos/modules/services/matrix/appservice-discord.nix
@@ -5,7 +5,6 @@ with lib;
 let
   dataDir = "/var/lib/matrix-appservice-discord";
   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
@@ -14,7 +13,16 @@ let
 in {
   options = {
     services.matrix-appservice-discord = {
-      enable = mkEnableOption "a bridge between Matrix and Discord";
+      enable = mkEnableOption (lib.mdDoc "a bridge between Matrix and Discord");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.matrix-appservice-discord;
+        defaultText = literalExpression "pkgs.matrix-appservice-discord";
+        description = lib.mdDoc ''
+          Which package of matrix-appservice-discord to use.
+        '';
+      };
 
       settings = mkOption rec {
         # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
@@ -114,7 +122,7 @@ in {
 
       preStart = ''
         if [ ! -f '${registrationFile}' ]; then
-          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+          ${cfg.package}/bin/matrix-appservice-discord \
             --generate-registration \
             --url=${escapeShellArg cfg.url} \
             ${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \
@@ -135,13 +143,13 @@ in {
 
         DynamicUser = true;
         PrivateTmp = true;
-        WorkingDirectory = appDir;
+        WorkingDirectory = "${cfg.package}/${cfg.package.passthru.nodeAppDir}";
         StateDirectory = baseNameOf dataDir;
-        UMask = 0027;
+        UMask = "0027";
         EnvironmentFile = cfg.environmentFile;
 
         ExecStart = ''
-          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+          ${cfg.package}/bin/matrix-appservice-discord \
             --file='${registrationFile}' \
             --config='${settingsFile}' \
             --port='${toString cfg.port}'
diff --git a/nixpkgs/nixos/modules/services/matrix/appservice-irc.nix b/nixpkgs/nixos/modules/services/matrix/appservice-irc.nix
index b24edba96d93..388553d4182e 100644
--- a/nixpkgs/nixos/modules/services/matrix/appservice-irc.nix
+++ b/nixpkgs/nixos/modules/services/matrix/appservice-irc.nix
@@ -28,7 +28,7 @@ let
   registrationFile = "/var/lib/matrix-appservice-irc/registration.yml";
 in {
   options.services.matrix-appservice-irc = with types; {
-    enable = mkEnableOption "the Matrix/IRC bridge";
+    enable = mkEnableOption (lib.mdDoc "the Matrix/IRC bridge");
 
     port = mkOption {
       type = port;
diff --git a/nixpkgs/nixos/modules/services/matrix/conduit.nix b/nixpkgs/nixos/modules/services/matrix/conduit.nix
index 29040c385008..c8d89ed33f51 100644
--- a/nixpkgs/nixos/modules/services/matrix/conduit.nix
+++ b/nixpkgs/nixos/modules/services/matrix/conduit.nix
@@ -11,7 +11,7 @@ in
   {
     meta.maintainers = with maintainers; [ pstn piegames ];
     options.services.matrix-conduit = {
-      enable = mkEnableOption "matrix-conduit";
+      enable = mkEnableOption (lib.mdDoc "matrix-conduit");
 
       extraEnvironment = mkOption {
         type = types.attrsOf types.str;
@@ -23,8 +23,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.matrix-conduit;
-        defaultText = "pkgs.matrix-conduit";
-        example = "pkgs.matrix-conduit";
+        defaultText = lib.literalExpression "pkgs.matrix-conduit";
         description = lib.mdDoc ''
           Package of the conduit matrix server to use.
         '';
diff --git a/nixpkgs/nixos/modules/services/matrix/dendrite.nix b/nixpkgs/nixos/modules/services/matrix/dendrite.nix
index 7cf9fd9e75f3..244c15fbf7a9 100644
--- a/nixpkgs/nixos/modules/services/matrix/dendrite.nix
+++ b/nixpkgs/nixos/modules/services/matrix/dendrite.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.services.dendrite = {
-    enable = lib.mkEnableOption "matrix.org dendrite";
+    enable = lib.mkEnableOption (lib.mdDoc "matrix.org dendrite");
     httpPort = lib.mkOption {
       type = lib.types.nullOr lib.types.port;
       default = 8008;
@@ -26,51 +26,50 @@ in
       type = lib.types.nullOr lib.types.path;
       example = "/var/lib/dendrite/server.cert";
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The path to the TLS certificate.
 
-        <programlisting>
+        ```
           nix-shell -p dendrite --command "generate-keys --tls-cert server.crt --tls-key server.key"
-        </programlisting>
+        ```
       '';
     };
     tlsKey = lib.mkOption {
       type = lib.types.nullOr lib.types.path;
       example = "/var/lib/dendrite/server.key";
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The path to the TLS key.
 
-        <programlisting>
+        ```
           nix-shell -p dendrite --command "generate-keys --tls-cert server.crt --tls-key server.key"
-        </programlisting>
+        ```
       '';
     };
     environmentFile = lib.mkOption {
       type = lib.types.nullOr lib.types.path;
       example = "/var/lib/dendrite/registration_secret";
       default = null;
-      description = ''
-        Environment file as defined in <citerefentry>
-        <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)`.
         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. Currently only used
         for the registration secret to allow secure registration when
         client_api.registration_disabled is true.
 
-        <programlisting>
+        ```
           # snippet of dendrite-related config
           services.dendrite.settings.client_api.registration_shared_secret = "$REGISTRATION_SHARED_SECRET";
-        </programlisting>
+        ```
 
-        <programlisting>
+        ```
           # content of the environment file
           REGISTRATION_SHARED_SECRET=verysecretpassword
-        </programlisting>
+        ```
 
         Note that this file needs to be available on the host on which
-        <literal>dendrite</literal> is running.
+        `dendrite` is running.
       '';
     };
     loadCredential = lib.mkOption {
@@ -103,13 +102,13 @@ in
               lib.types.path
               (lib.types.strMatching "^\\$CREDENTIALS_DIRECTORY/.+");
             example = "$CREDENTIALS_DIRECTORY/private_key";
-            description = ''
+            description = lib.mdDoc ''
               The path to the signing private key file, used to sign
               requests and events.
 
-              <programlisting>
+              ```
                 nix-shell -p dendrite --command "generate-keys --private-key matrix_key.pem"
-              </programlisting>
+              ```
             '';
           };
           trusted_third_party_id_servers = lib.mkOption {
@@ -160,6 +159,15 @@ in
             '';
           };
         };
+        options.relay_api.database = {
+          connection_string = lib.mkOption {
+            type = lib.types.str;
+            default = "file:relayapi.db";
+            description = lib.mdDoc ''
+              Database for the Relay Server.
+            '';
+          };
+        };
         options.media_api = {
           database = {
             connection_string = lib.mkOption {
@@ -196,6 +204,25 @@ in
             '';
           };
         };
+        options.sync_api.search = {
+          enable = lib.mkEnableOption (lib.mdDoc "Dendrite's full-text search engine");
+          index_path = lib.mkOption {
+            type = lib.types.str;
+            default = "${workingDir}/searchindex";
+            description = lib.mdDoc ''
+              The path the search index will be created in.
+            '';
+          };
+          language = lib.mkOption {
+            type = lib.types.str;
+            default = "en";
+            description = lib.mdDoc ''
+              The language most likely to be used on the server - used when indexing, to
+              ensure the returned results match expectations. A full list of possible languages
+              can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
+            '';
+          };
+        };
         options.user_api = {
           account_database = {
             connection_string = lib.mkOption {
@@ -270,13 +297,13 @@ in
         LimitNOFILE = 65535;
         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
         LoadCredential = cfg.loadCredential;
-        ExecStartPre = ''
+        ExecStartPre = [''
           ${pkgs.envsubst}/bin/envsubst \
             -i ${configurationYaml} \
             -o /run/dendrite/dendrite.yaml
-        '';
+        ''];
         ExecStart = lib.strings.concatStringsSep " " ([
-          "${pkgs.dendrite}/bin/dendrite-monolith-server"
+          "${pkgs.dendrite}/bin/dendrite"
           "--config /run/dendrite/dendrite.yaml"
         ] ++ lib.optionals (cfg.httpPort != null) [
           "--http-bind-address :${builtins.toString cfg.httpPort}"
diff --git a/nixpkgs/nixos/modules/services/matrix/mautrix-facebook.nix b/nixpkgs/nixos/modules/services/matrix/mautrix-facebook.nix
index dfdf6e250215..bab6865496dd 100644
--- a/nixpkgs/nixos/modules/services/matrix/mautrix-facebook.nix
+++ b/nixpkgs/nixos/modules/services/matrix/mautrix-facebook.nix
@@ -17,7 +17,7 @@ let
 in {
   options = {
     services.mautrix-facebook = {
-      enable = mkEnableOption "Mautrix-Facebook, a Matrix-Facebook hybrid puppeting/relaybot bridge";
+      enable = mkEnableOption (lib.mdDoc "Mautrix-Facebook, a Matrix-Facebook hybrid puppeting/relaybot bridge");
 
       settings = mkOption rec {
         apply = recursiveUpdate default;
@@ -25,9 +25,11 @@ in {
         default = {
           homeserver = {
             address = "http://localhost:8008";
+            software = "standard";
           };
 
           appservice = rec {
+            id = "facebook";
             address = "http://${hostname}:${toString port}";
             hostname = "localhost";
             port = 29319;
@@ -44,6 +46,12 @@ in {
             encryption = {
               allow = true;
               default = true;
+
+              verification_levels = {
+                receive = "cross-signed-tofu";
+                send = "cross-signed-tofu";
+                share = "cross-signed-tofu";
+              };
             };
             username_template = "facebook_{userid}";
           };
@@ -89,7 +97,7 @@ in {
         type = types.nullOr types.path;
         default = null;
         description = lib.mdDoc ''
-          File containing environment variables to be passed to the mautrix-telegram service.
+          File containing environment variables to be passed to the mautrix-facebook service.
 
           Any config variable can be overridden by setting `MAUTRIX_FACEBOOK_SOME_KEY` to override the `some.key` variable.
         '';
@@ -116,6 +124,8 @@ in {
   };
 
   config = mkIf cfg.enable {
+    users.groups.mautrix-facebook = {};
+
     users.users.mautrix-facebook = {
       group = "mautrix-facebook";
       isSystemUser = true;
@@ -162,7 +172,7 @@ in {
 
     services.mautrix-facebook = {
       registrationData = {
-        id = "mautrix-facebook";
+        id = cfg.settings.appservice.id;
 
         namespaces = {
           users = [
diff --git a/nixpkgs/nixos/modules/services/matrix/mautrix-telegram.nix b/nixpkgs/nixos/modules/services/matrix/mautrix-telegram.nix
index 196100a531ad..b64cc71d9873 100644
--- a/nixpkgs/nixos/modules/services/matrix/mautrix-telegram.nix
+++ b/nixpkgs/nixos/modules/services/matrix/mautrix-telegram.nix
@@ -7,18 +7,22 @@ let
   registrationFile = "${dataDir}/telegram-registration.yaml";
   cfg = config.services.mautrix-telegram;
   settingsFormat = pkgs.formats.json {};
-  settingsFileUnsubstituted = settingsFormat.generate "mautrix-telegram-config-unsubstituted.json" cfg.settings;
-  settingsFile = "${dataDir}/config.json";
+  settingsFile =
+    settingsFormat.generate "mautrix-telegram-config.json" cfg.settings;
 
 in {
   options = {
     services.mautrix-telegram = {
-      enable = mkEnableOption "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge";
+      enable = mkEnableOption (lib.mdDoc "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge");
 
       settings = mkOption rec {
         apply = recursiveUpdate default;
         inherit (settingsFormat) type;
         default = {
+          homeserver = {
+            software = "standard";
+          };
+
           appservice = rec {
             database = "sqlite:///${dataDir}/mautrix-telegram.db";
             database_opts = {};
@@ -81,7 +85,7 @@ in {
         description = lib.mdDoc ''
           {file}`config.yaml` configuration as a Nix attribute set.
           Configuration options should match those described in
-          [example-config.yaml](https://github.com/tulir/mautrix-telegram/blob/master/example-config.yaml).
+          [example-config.yaml](https://github.com/mautrix/telegram/blob/master/mautrix_telegram/example-config.yaml).
 
           Secret tokens should be specified using {option}`environmentFile`
           instead of this world-readable attribute set.
@@ -93,12 +97,23 @@ in {
         default = null;
         description = lib.mdDoc ''
           File containing environment variables to be passed to the mautrix-telegram service,
-          in which secret tokens can be specified securely by defining values for
+          in which secret tokens can be specified securely by defining values for e.g.
           `MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN`,
           `MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN`,
           `MAUTRIX_TELEGRAM_TELEGRAM_API_ID`,
           `MAUTRIX_TELEGRAM_TELEGRAM_API_HASH` and optionally
           `MAUTRIX_TELEGRAM_TELEGRAM_BOT_TOKEN`.
+
+          These environment variables can also be used to set other options by
+          replacing hierarchy levels by `.`, converting the name to uppercase
+          and prepending `MAUTRIX_TELEGRAM_`.
+          For example, the first value above maps to
+          {option}`settings.appservice.as_token`.
+
+          The environment variable values can be prefixed with `json::` to have
+          them be parsed as JSON. For example, `login_shared_secret_map` can be
+          set as follows:
+          `MAUTRIX_TELEGRAM_BRIDGE_LOGIN_SHARED_SECRET_MAP=json::{"example.com":"secret"}`.
         '';
       };
 
@@ -122,19 +137,21 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
       after = [ "network-online.target" ] ++ cfg.serviceDependencies;
-      path = [ pkgs.lottieconverter ];
+      path = [ pkgs.lottieconverter pkgs.ffmpeg-full ];
+
+      # mautrix-telegram tries to generate a dotfile in the home directory of
+      # the running user if using a postgresql database:
+      #
+      #  File "python3.10/site-packages/asyncpg/connect_utils.py", line 257, in _dot_postgre>
+      #    return (pathlib.Path.home() / '.postgresql' / filename).resolve()
+      #  File "python3.10/pathlib.py", line 1000, in home
+      #    return cls("~").expanduser()
+      #  File "python3.10/pathlib.py", line 1440, in expanduser
+      #    raise RuntimeError("Could not determine home directory.")
+      # RuntimeError: Could not determine home directory.
+      environment.HOME = dataDir;
 
       preStart = ''
-        # Not all secrets can be passed as environment variable (yet)
-        # https://github.com/tulir/mautrix-telegram/issues/584
-        [ -f ${settingsFile} ] && rm -f ${settingsFile}
-        old_umask=$(umask)
-        umask 0177
-        ${pkgs.envsubst}/bin/envsubst \
-          -o ${settingsFile} \
-          -i ${settingsFileUnsubstituted}
-        umask $old_umask
-
         # generate the appservice's registration file if absent
         if [ ! -f '${registrationFile}' ]; then
           ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
@@ -162,7 +179,7 @@ in {
         PrivateTmp = true;
         WorkingDirectory = pkgs.mautrix-telegram; # necessary for the database migration scripts to be found
         StateDirectory = baseNameOf dataDir;
-        UMask = 0027;
+        UMask = "0027";
         EnvironmentFile = cfg.environmentFile;
 
         ExecStart = ''
@@ -170,8 +187,6 @@ in {
             --config='${settingsFile}'
         '';
       };
-
-      restartTriggers = [ settingsFileUnsubstituted ];
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/matrix/mjolnir.md b/nixpkgs/nixos/modules/services/matrix/mjolnir.md
new file mode 100644
index 000000000000..f6994eeb8fa5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/matrix/mjolnir.md
@@ -0,0 +1,110 @@
+# Mjolnir (Matrix Moderation Tool) {#module-services-mjolnir}
+
+This chapter will show you how to set up your own, self-hosted
+[Mjolnir](https://github.com/matrix-org/mjolnir) instance.
+
+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.
+
+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.
+
+See the [README](https://github.com/matrix-org/mjolnir#readme)
+page and the [Moderator's guide](https://github.com/matrix-org/mjolnir/blob/main/docs/moderators.md)
+for additional instructions on how to setup and use Mjolnir.
+
+For [additional settings](#opt-services.mjolnir.settings)
+see [the default configuration](https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml).
+
+## Mjolnir Setup {#module-services-mjolnir-setup}
+
+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 [services.mjolnir.managementRoom](#opt-services.mjolnir.managementRoom).
+
+Next, create a new user for Mjolnir on your homeserver, if not present already.
+
+The Mjolnir Matrix user expects to be free of any rate limiting.
+See [Synapse #6286](https://github.com/matrix-org/synapse/issues/6286)
+for an example on how to achieve this.
+
+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.
+
+Now invite the Mjolnir user to the management room.
+
+It is recommended to use [Pantalaimon](https://github.com/matrix-org/pantalaimon),
+so your management room can be encrypted. This also applies if you are looking to moderate an encrypted room.
+
+To enable the Pantalaimon E2E Proxy for mjolnir, enable
+[services.mjolnir.pantalaimon](#opt-services.mjolnir.pantalaimon.enable). This will
+autoconfigure a new Pantalaimon instance, which will connect to the homeserver
+set in [services.mjolnir.homeserverUrl](#opt-services.mjolnir.homeserverUrl) and Mjolnir itself
+will be configured to connect to the new Pantalaimon instance.
+
+```
+{
+  services.mjolnir = {
+    enable = true;
+    homeserverUrl = "https://matrix.domain.tld";
+    pantalaimon = {
+       enable = true;
+       username = "mjolnir";
+       passwordFile = "/run/secrets/mjolnir-password";
+    };
+    protectedRooms = [
+      "https://matrix.to/#/!xxx:domain.tld"
+    ];
+    managementRoom = "!yyy:domain.tld";
+  };
+}
+```
+
+### Element Matrix Services (EMS) {#module-services-mjolnir-setup-ems}
+
+If you are using a managed ["Element Matrix Services (EMS)"](https://ems.element.io/)
+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.
+
+## Synapse Antispam Module {#module-services-mjolnir-matrix-synapse-antispam}
+
+A Synapse module is also available to apply the same rulesets the bot
+uses across an entire homeserver.
+
+To use the Antispam Module, add `matrix-synapse-plugins.matrix-synapse-mjolnir-antispam`
+to the Synapse plugin list and enable the `mjolnir.Module` module.
+
+```
+{
+  services.matrix-synapse = {
+    plugins = with pkgs; [
+      matrix-synapse-plugins.matrix-synapse-mjolnir-antispam
+    ];
+    extraConfig = ''
+      modules:
+        - module: mjolnir.Module
+          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"
+    '';
+  };
+}
+```
diff --git a/nixpkgs/nixos/modules/services/matrix/mjolnir.nix b/nixpkgs/nixos/modules/services/matrix/mjolnir.nix
index abbbb4030e52..0824be663340 100644
--- a/nixpkgs/nixos/modules/services/matrix/mjolnir.nix
+++ b/nixpkgs/nixos/modules/services/matrix/mjolnir.nix
@@ -30,7 +30,7 @@ let
 
   # these config files will be merged one after the other to build the final config
   configFiles = [
-    "${pkgs.mjolnir}/share/mjolnir/config/default.yaml"
+    "${pkgs.mjolnir}/libexec/mjolnir/deps/mjolnir/config/default.yaml"
     moduleConfigFile
   ];
 
@@ -65,7 +65,7 @@ let
 in
 {
   options.services.mjolnir = {
-    enable = mkEnableOption "Mjolnir, a moderation tool for Matrix";
+    enable = mkEnableOption (lib.mdDoc "Mjolnir, a moderation tool for Matrix");
 
     homeserverUrl = mkOption {
       type = types.str;
@@ -95,10 +95,10 @@ in
       default = { };
       type = types.submodule {
         options = {
-          enable = mkEnableOption ''
+          enable = mkEnableOption (lib.mdDoc ''
             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;
@@ -200,7 +200,7 @@ in
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
-        ExecStart = ''${pkgs.mjolnir}/bin/mjolnir'';
+        ExecStart = ''${pkgs.mjolnir}/bin/mjolnir --mjolnir-config ./config/default.yaml'';
         ExecStartPre = [ generateConfig ];
         WorkingDirectory = cfg.dataPath;
         StateDirectory = "mjolnir";
@@ -236,7 +236,7 @@ in
   };
 
   meta = {
-    doc = ./mjolnir.xml;
+    doc = ./mjolnir.md;
     maintainers = with maintainers; [ jojosch ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/matrix/mjolnir.xml b/nixpkgs/nixos/modules/services/matrix/mjolnir.xml
deleted file mode 100644
index b07abe339791..000000000000
--- a/nixpkgs/nixos/modules/services/matrix/mjolnir.xml
+++ /dev/null
@@ -1,134 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="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.Module</literal> module.
-  </para>
-<programlisting>
-{
-  services.matrix-synapse = {
-    plugins = with pkgs; [
-      matrix-synapse-plugins.matrix-synapse-mjolnir-antispam
-    ];
-    extraConfig = ''
-      modules:
-        - module: mjolnir.Module
-          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/misc/mx-puppet-discord.nix b/nixpkgs/nixos/modules/services/matrix/mx-puppet-discord.nix
index 18b083b99ba3..36c9f8b122ea 100644
--- a/nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix
+++ b/nixpkgs/nixos/modules/services/matrix/mx-puppet-discord.nix
@@ -12,10 +12,10 @@ let
 in {
   options = {
     services.mx-puppet-discord = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         mx-puppet-discord is a discord puppeting bridge for matrix.
         It handles bridging private and group DMs, as well as Guilds (servers)
-      '';
+      '');
 
       settings = mkOption rec {
         apply = recursiveUpdate default;
@@ -107,7 +107,7 @@ in {
         PrivateTmp = true;
         WorkingDirectory = pkgs.mx-puppet-discord;
         StateDirectory = baseNameOf dataDir;
-        UMask = 0027;
+        UMask = "0027";
 
         ExecStart = ''
           ${pkgs.mx-puppet-discord}/bin/mx-puppet-discord \
diff --git a/nixpkgs/nixos/modules/services/matrix/synapse.md b/nixpkgs/nixos/modules/services/matrix/synapse.md
new file mode 100644
index 000000000000..cad91ebf58d5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/matrix/synapse.md
@@ -0,0 +1,213 @@
+# Matrix {#module-services-matrix}
+
+[Matrix](https://matrix.org/) is an open standard for
+interoperable, decentralised, real-time communication over IP. It can be used
+to power Instant Messaging, VoIP/WebRTC signalling, Internet of Things
+communication - or anywhere you need a standard HTTP API for publishing and
+subscribing to data whilst tracking the conversation history.
+
+This chapter will show you how to set up your own, self-hosted Matrix
+homeserver using the Synapse reference homeserver, and how to serve your own
+copy of the Element web client. See the
+[Try Matrix Now!](https://matrix.org/docs/projects/try-matrix-now.html)
+overview page for links to Element Apps for Android and iOS,
+desktop clients, as well as bridges to other networks and other projects
+around Matrix.
+
+## Synapse Homeserver {#module-services-matrix-synapse}
+
+[Synapse](https://github.com/matrix-org/synapse) is
+the reference homeserver implementation of Matrix from the core development
+team at matrix.org. The following configuration example will set up a
+synapse server for the `example.org` domain, served from
+the host `myhostname.example.org`. For more information,
+please refer to the
+[installation instructions of Synapse](https://matrix-org.github.io/synapse/latest/setup/installation.html) .
+```
+{ pkgs, lib, config, ... }:
+let
+  fqdn = "${config.networking.hostName}.${config.networking.domain}";
+  clientConfig."m.homeserver".base_url = "https://${fqdn}";
+  serverConfig."m.server" = "${fqdn}:443";
+  mkWellKnown = data: ''
+    add_header Content-Type application/json;
+    add_header Access-Control-Allow-Origin *;
+    return 200 '${builtins.toJSON data}';
+  '';
+in {
+  networking.hostName = "myhostname";
+  networking.domain = "example.org";
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+  services.postgresql.enable = true;
+  services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
+    CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+    CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+      TEMPLATE template0
+      LC_COLLATE = "C"
+      LC_CTYPE = "C";
+  '';
+
+  services.nginx = {
+    enable = true;
+    recommendedTlsSettings = true;
+    recommendedOptimisation = true;
+    recommendedGzipSettings = true;
+    recommendedProxySettings = true;
+    virtualHosts = {
+      # If the A and AAAA DNS records on example.org do not point on the same host as the
+      # records for myhostname.example.org, you can easily move the /.well-known
+      # virtualHost section of the code to the host that is serving example.org, while
+      # the rest stays on myhostname.example.org with no other changes required.
+      # This pattern also allows to seamlessly move the homeserver from
+      # myhostname.example.org to myotherhost.example.org by only changing the
+      # /.well-known redirection target.
+      "${config.networking.domain}" = {
+        enableACME = true;
+        forceSSL = true;
+        # This section is not needed if the server_name of matrix-synapse is equal to
+        # the domain (i.e. example.org from @foo:example.org) and the federation port
+        # is 8448.
+        # Further reference can be found in the docs about delegation under
+        # https://matrix-org.github.io/synapse/latest/delegate.html
+        locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
+        # This is usually needed for homeserver discovery (from e.g. other Matrix clients).
+        # Further reference can be found in the upstream docs at
+        # https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient
+        locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
+      };
+      "${fqdn}" = {
+        enableACME = true;
+        forceSSL = true;
+        # It's also possible to do a redirect here or something else, this vhost is not
+        # needed for Matrix. It's recommended though to *not put* element
+        # here, see also the section about Element.
+        locations."/".extraConfig = ''
+          return 404;
+        '';
+        # Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash
+        # *must not* be used here.
+        locations."/_matrix".proxyPass = "http://[::1]:8008";
+        # Forward requests for e.g. SSO and password-resets.
+        locations."/_synapse/client".proxyPass = "http://[::1]:8008";
+      };
+    };
+  };
+
+  services.matrix-synapse = {
+    enable = true;
+    settings.server_name = config.networking.domain;
+    settings.listeners = [
+      { port = 8008;
+        bind_addresses = [ "::1" ];
+        type = "http";
+        tls = false;
+        x_forwarded = true;
+        resources = [ {
+          names = [ "client" "federation" ];
+          compress = true;
+        } ];
+      }
+    ];
+  };
+}
+```
+
+## Registering Matrix users {#module-services-matrix-register-users}
+
+If you want to run a server with public registration by anybody, you can
+then enable `services.matrix-synapse.settings.enable_registration = true;`.
+Otherwise, or you can generate a registration secret with
+{command}`pwgen -s 64 1` and set it with
+[](#opt-services.matrix-synapse.settings.registration_shared_secret).
+To create a new user or admin, run the following after you have set the secret
+and have rebuilt NixOS:
+```ShellSession
+$ nix-shell -p matrix-synapse
+$ register_new_matrix_user -k your-registration-shared-secret http://localhost:8008
+New user localpart: your-username
+Password:
+Confirm password:
+Make admin [no]:
+Success!
+```
+In the example, this would create a user with the Matrix Identifier
+`@your-username:example.org`.
+
+::: {.warning}
+When using [](#opt-services.matrix-synapse.settings.registration_shared_secret), the secret
+will end up in the world-readable store. Instead it's recommended to deploy the secret
+in an additional file like this:
+
+  - Create a file with the following contents:
+
+    ```
+    registration_shared_secret: your-very-secret-secret
+    ```
+  - Deploy the file with a secret-manager such as
+    [{option}`deployment.keys`](https://nixops.readthedocs.io/en/latest/overview.html#managing-keys)
+    from {manpage}`nixops(1)` or [sops-nix](https://github.com/Mic92/sops-nix/) to
+    e.g. {file}`/run/secrets/matrix-shared-secret` and ensure that it's readable
+    by `matrix-synapse`.
+  - Include the file like this in your configuration:
+
+    ```
+    {
+      services.matrix-synapse.extraConfigFiles = [
+        "/run/secrets/matrix-shared-secret"
+      ];
+    }
+    ```
+:::
+
+::: {.note}
+It's also possible to user alternative authentication mechanism such as
+[LDAP (via `matrix-synapse-ldap3`)](https://github.com/matrix-org/matrix-synapse-ldap3)
+or [OpenID](https://matrix-org.github.io/synapse/latest/openid.html).
+:::
+
+## Element (formerly known as Riot) Web Client {#module-services-matrix-element-web}
+
+[Element Web](https://github.com/vector-im/riot-web/) is
+the reference web client for Matrix and developed by the core team at
+matrix.org. Element was formerly known as Riot.im, see the
+[Element introductory blog post](https://element.io/blog/welcome-to-element/)
+for more information. The following snippet can be optionally added to the code before
+to complete the synapse installation with a web client served at
+`https://element.myhostname.example.org` and
+`https://element.example.org`. Alternatively, you can use the hosted
+copy at <https://app.element.io/>,
+or use other web clients or native client applications. Due to the
+`/.well-known` urls set up done above, many clients should
+fill in the required connection details automatically when you enter your
+Matrix Identifier. See
+[Try Matrix Now!](https://matrix.org/docs/projects/try-matrix-now.html)
+for a list of existing clients and their supported featureset.
+```
+{
+  services.nginx.virtualHosts."element.${fqdn}" = {
+    enableACME = true;
+    forceSSL = true;
+    serverAliases = [
+      "element.${config.networking.domain}"
+    ];
+
+    root = pkgs.element-web.override {
+      conf = {
+        default_server_config = clientConfig; # see `clientConfig` from the snippet above.
+      };
+    };
+  };
+}
+```
+
+::: {.note}
+The Element developers do not recommend running Element and your Matrix
+homeserver on the same fully-qualified domain name for security reasons. In
+the example, this means that you should not reuse the
+`myhostname.example.org` virtualHost to also serve Element,
+but instead serve it on a different subdomain, like
+`element.example.org` in the example. See the
+[Element Important Security Notes](https://github.com/vector-im/element-web/tree/v1.10.0#important-security-notes)
+for more information on this subject.
+:::
diff --git a/nixpkgs/nixos/modules/services/matrix/synapse.nix b/nixpkgs/nixos/modules/services/matrix/synapse.nix
index 0dbfcc17638d..3dca3ff94f21 100644
--- a/nixpkgs/nixos/modules/services/matrix/synapse.nix
+++ b/nixpkgs/nixos/modules/services/matrix/synapse.nix
@@ -60,7 +60,7 @@ in {
     '')
     (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
       Database configuration must be done manually. An exemplary setup is demonstrated in
-      <nixpkgs/nixos/tests/matrix-synapse.nix>
+      <nixpkgs/nixos/tests/matrix/synapse.nix>
     '')
     (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
     (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] ''
@@ -80,7 +80,7 @@ in {
     (mkRemovedOptionModule [ "services" "matrix-synapse" "user_creation_max_duration" ] "It is no longer supported by synapse." )
     (mkRemovedOptionModule [ "services" "matrix-synapse" "verbose" ] "Use a log config instead." )
 
-    # options that were moved into rfc42 style settigns
+    # options that were moved into rfc42 style settings
     (mkRemovedOptionModule [ "services" "matrix-synapse" "app_service_config_files" ] "Use settings.app_service_config_files instead" )
     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_args" ] "Use settings.database.args instead" )
     (mkRemovedOptionModule [ "services" "matrix-synapse" "database_name" ] "Use settings.database.args.database instead" )
@@ -138,7 +138,7 @@ in {
 
   options = {
     services.matrix-synapse = {
-      enable = mkEnableOption "matrix.org synapse";
+      enable = mkEnableOption (lib.mdDoc "matrix.org synapse");
 
       configFile = mkOption {
         type = types.path;
@@ -286,6 +286,7 @@ in {
             log_config = mkOption {
               type = types.path;
               default = ./synapse-log_config.yaml;
+              defaultText = lib.literalExpression "nixos/modules/services/matrix/synapse-log_config.yaml";
               description = lib.mdDoc ''
                 The file that holds the logging configuration.
               '';
@@ -506,6 +507,12 @@ in {
                 sqlite3 = null;
                 psycopg2 = "matrix-synapse";
               }.${cfg.settings.database.name};
+              defaultText = lib.literalExpression ''
+                {
+                  sqlite3 = null;
+                  psycopg2 = "matrix-synapse";
+                }.''${cfg.settings.database.name};
+              '';
               description = lib.mdDoc ''
                 Username to connect with psycopg2, set to null
                 when using sqlite3.
@@ -516,7 +523,7 @@ in {
               type = types.bool;
               default = true;
               example = false;
-              description = ''
+              description = lib.mdDoc ''
                 Is the preview URL API enabled?  If enabled, you *must* specify an
                 explicit url_preview_ip_range_blacklist of IPs that the spider is
                 denied from accessing.
@@ -629,6 +636,7 @@ in {
 
             trusted_key_servers = mkOption {
               type = types.listOf (types.submodule {
+                freeformType = format.type;
                 options = {
                   server_name = mkOption {
                     type = types.str;
@@ -637,22 +645,6 @@ in {
                       Hostname of the trusted server.
                     '';
                   };
-
-                  verify_keys = mkOption {
-                    type = types.nullOr (types.attrsOf types.str);
-                    default = null;
-                    example = literalExpression ''
-                      {
-                        "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
-                      }
-                    '';
-                    description = lib.mdDoc ''
-                      Attribute set from key id to base64 encoded public key.
-
-                      If specified synapse will check that the response is signed
-                      by at least one of the given keys.
-                    '';
-                  };
                 };
               });
               default = [ {
@@ -704,7 +696,7 @@ in {
 
             If you
             - try to deploy a fresh synapse, you need to configure the database yourself. An example
-              for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix>
+              for this can be found in <nixpkgs/nixos/tests/matrix/synapse.nix>
             - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
               to your configuration.
 
@@ -748,8 +740,8 @@ in {
         Group = "matrix-synapse";
         WorkingDirectory = cfg.dataDir;
         ExecStartPre = [ ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
-          chown matrix-synapse:matrix-synapse ${cfg.dataDir}/homeserver.signing.key
-          chmod 0600 ${cfg.dataDir}/homeserver.signing.key
+          chown matrix-synapse:matrix-synapse ${cfg.settings.signing_key_path}
+          chmod 0600 ${cfg.settings.signing_key_path}
         '')) ];
         ExecStart = ''
           ${cfg.package}/bin/synapse_homeserver \
@@ -759,6 +751,33 @@ in {
         ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
         Restart = "on-failure";
         UMask = "0077";
+
+        # Security Hardening
+        # Refer to systemd.exec(5) for option descriptions.
+        CapabilityBoundingSet = [ "" ];
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = 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";
+        ReadWritePaths = [ cfg.dataDir ];
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
       };
     };
 
@@ -767,7 +786,7 @@ in {
 
   meta = {
     buildDocsInSandbox = false;
-    doc = ./synapse.xml;
+    doc = ./synapse.md;
     maintainers = teams.matrix.members;
   };
 
diff --git a/nixpkgs/nixos/modules/services/matrix/synapse.xml b/nixpkgs/nixos/modules/services/matrix/synapse.xml
deleted file mode 100644
index 65bc53d33ac3..000000000000
--- a/nixpkgs/nixos/modules/services/matrix/synapse.xml
+++ /dev/null
@@ -1,276 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-matrix">
- <title>Matrix</title>
- <para>
-  <link xlink:href="https://matrix.org/">Matrix</link> is an open standard for
-  interoperable, decentralised, real-time communication over IP. It can be used
-  to power Instant Messaging, VoIP/WebRTC signalling, Internet of Things
-  communication - or anywhere you need a standard HTTP API for publishing and
-  subscribing to data whilst tracking the conversation history.
- </para>
- <para>
-  This chapter will show you how to set up your own, self-hosted Matrix
-  homeserver using the Synapse reference homeserver, and how to serve your own
-  copy of the Element web client. See the
-  <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
-  Matrix Now!</link> overview page for links to Element Apps for Android and iOS,
-  desktop clients, as well as bridges to other networks and other projects
-  around Matrix.
- </para>
- <section xml:id="module-services-matrix-synapse">
-  <title>Synapse Homeserver</title>
-
-  <para>
-   <link xlink:href="https://github.com/matrix-org/synapse">Synapse</link> is
-   the reference homeserver implementation of Matrix from the core development
-   team at matrix.org. The following configuration example will set up a
-   synapse server for the <literal>example.org</literal> domain, served from
-   the host <literal>myhostname.example.org</literal>. For more information,
-   please refer to the
-   <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation">
-   installation instructions of Synapse </link>.
-<programlisting>
-{ pkgs, lib, config, ... }:
-let
-  fqdn = "${config.networking.hostName}.${config.networking.domain}";
-  clientConfig = {
-    "m.homeserver".base_url = "https://${fqdn}";
-    "m.identity_server" = {};
-  };
-  serverConfig."m.server" = "${config.services.matrix-synapse.settings.server_name}:443";
-  mkWellKnown = data: ''
-    add_header Content-Type application/json;
-    add_header Access-Control-Allow-Origin *;
-    return 200 '${builtins.toJSON data}';
-  '';
-in {
-  <xref linkend="opt-networking.hostName" /> = "myhostname";
-  <xref linkend="opt-networking.domain" /> = "example.org";
-  <xref linkend="opt-networking.firewall.allowedTCPPorts" /> = [ 80 443 ];
-
-  <xref linkend="opt-services.postgresql.enable" /> = true;
-  <xref linkend="opt-services.postgresql.initialScript" /> = pkgs.writeText "synapse-init.sql" ''
-    CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
-    CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
-      TEMPLATE template0
-      LC_COLLATE = "C"
-      LC_CTYPE = "C";
-  '';
-
-  services.nginx = {
-    <link linkend="opt-services.nginx.enable">enable</link> = true;
-    <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
-    <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
-    <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
-    <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
-    <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
-      "${config.networking.domain}" = { <co xml:id='ex-matrix-synapse-dns' />
-        <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
-        <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> = mkWellKnown serverConfig; <co xml:id='ex-matrix-synapse-well-known-server' />
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> = mkWellKnown clientConfig; <co xml:id='ex-matrix-synapse-well-known-client' />
-      };
-      "${fqdn}" = {
-        <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
-        <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = '' <co xml:id='ex-matrix-synapse-rev-default' />
-          return 404;
-        '';
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/_matrix".proxyPass</link> = "http://[::1]:8008"; <co xml:id='ex-matrix-synapse-rev-proxy-pass' />
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/_synapse/client".proxyPass</link> = "http://[::1]:8008"; <co xml:id='ex-matrix-synapse-rev-client' />
-      };
-    };
-  };
-
-  services.matrix-synapse = {
-    <link linkend="opt-services.matrix-synapse.enable">enable</link> = true;
-    <link linkend="opt-services.matrix-synapse.settings.server_name">settings.server_name</link> = config.networking.domain;
-    <link linkend="opt-services.matrix-synapse.settings.listeners">settings.listeners</link> = [
-      { <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008;
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_addresses</link> = [ "::1" ];
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.type">type</link> = "http";
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.tls">tls</link> = false;
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.x_forwarded">x_forwarded</link> = true;
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.resources">resources</link> = [ {
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "client" "federation" ];
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.compress">compress</link> = true;
-        } ];
-      }
-    ];
-  };
-}
-</programlisting>
-  </para>
-  <calloutlist>
-   <callout arearefs='ex-matrix-synapse-dns'>
-    <para>
-     If the <code>A</code> and <code>AAAA</code> DNS records on
-     <literal>example.org</literal> do not point on the same host as the records
-     for <code>myhostname.example.org</code>, you can easily move the
-     <code>/.well-known</code> virtualHost section of the code to the host that
-     is serving <literal>example.org</literal>, while the rest stays on
-     <literal>myhostname.example.org</literal> with no other changes required.
-     This pattern also allows to seamlessly move the homeserver from
-     <literal>myhostname.example.org</literal> to
-     <literal>myotherhost.example.org</literal> by only changing the
-     <code>/.well-known</code> redirection target.
-    </para>
-   </callout>
-   <callout arearefs='ex-matrix-synapse-well-known-server'>
-    <para>
-     This section is not needed if the <link linkend="opt-services.matrix-synapse.settings.server_name">server_name</link>
-     of <package>matrix-synapse</package> is equal to the domain (i.e.
-     <literal>example.org</literal> from <literal>@foo:example.org</literal>)
-     and the federation port is 8448.
-     Further reference can be found in the <link xlink:href="https://matrix-org.github.io/synapse/latest/delegate.html">docs
-     about delegation</link>.
-    </para>
-   </callout>
-   <callout arearefs='ex-matrix-synapse-well-known-client'>
-    <para>
-     This is usually needed for homeserver discovery (from e.g. other Matrix clients).
-     Further reference can be found in the <link xlink:href="https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient">upstream docs</link>
-    </para>
-   </callout>
-   <callout arearefs='ex-matrix-synapse-rev-default'>
-    <para>
-     It's also possible to do a redirect here or something else, this vhost is not
-     needed for Matrix. It's recommended though to <emphasis>not put</emphasis> element
-     here, see also the <link linkend='ex-matrix-synapse-rev-default'>section about Element</link>.
-    </para>
-   </callout>
-   <callout arearefs='ex-matrix-synapse-rev-proxy-pass'>
-    <para>
-     Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash
-     <emphasis>must not</emphasis> be used here.
-    </para>
-   </callout>
-   <callout arearefs='ex-matrix-synapse-rev-client'>
-    <para>
-     Forward requests for e.g. SSO and password-resets.
-    </para>
-   </callout>
-  </calloutlist>
- </section>
- <section xml:id="module-services-matrix-register-users">
-  <title>Registering Matrix users</title>
-  <para>
-   If you want to run a server with public registration by anybody, you can
-   then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.settings.enable_registration</link> =
-   true;</literal>. Otherwise, or you can generate a registration secret with
-   <command>pwgen -s 64 1</command> and set it with
-   <option><link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">services.matrix-synapse.settings.registration_shared_secret</link></option>.
-   To create a new user or admin, run the following after you have set the secret
-   and have rebuilt NixOS:
-<screen>
-<prompt>$ </prompt>nix-shell -p matrix-synapse
-<prompt>$ </prompt>register_new_matrix_user -k <replaceable>your-registration-shared-secret</replaceable> http://localhost:8008
-<prompt>New user localpart: </prompt><replaceable>your-username</replaceable>
-<prompt>Password:</prompt>
-<prompt>Confirm password:</prompt>
-<prompt>Make admin [no]:</prompt>
-Success!
-</screen>
-   In the example, this would create a user with the Matrix Identifier
-   <literal>@your-username:example.org</literal>.
-   <warning>
-    <para>
-     When using <xref linkend="opt-services.matrix-synapse.settings.registration_shared_secret" />, the secret
-     will end up in the world-readable store. Instead it's recommended to deploy the secret
-     in an additional file like this:
-     <itemizedlist>
-      <listitem>
-       <para>
-        Create a file with the following contents:
-<programlisting>registration_shared_secret: your-very-secret-secret</programlisting>
-       </para>
-      </listitem>
-      <listitem>
-       <para>
-        Deploy the file with a secret-manager such as <link xlink:href="https://nixops.readthedocs.io/en/latest/overview.html#managing-keys"><option>deployment.keys</option></link>
-        from <citerefentry><refentrytitle>nixops</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-        or <link xlink:href="https://github.com/Mic92/sops-nix/">sops-nix</link> to
-        e.g. <filename>/run/secrets/matrix-shared-secret</filename> and ensure that it's readable
-        by <package>matrix-synapse</package>.
-       </para>
-      </listitem>
-      <listitem>
-       <para>
-        Include the file like this in your configuration:
-<programlisting>
-{
-  <xref linkend="opt-services.matrix-synapse.extraConfigFiles" /> = [
-    "/run/secrets/matrix-shared-secret"
-  ];
-}
-</programlisting>
-       </para>
-      </listitem>
-     </itemizedlist>
-    </para>
-   </warning>
-  </para>
-  <note>
-   <para>
-    It's also possible to user alternative authentication mechanism such as
-    <link xlink:href="https://github.com/matrix-org/matrix-synapse-ldap3">LDAP (via <literal>matrix-synapse-ldap3</literal>)</link>
-    or <link xlink:href="https://matrix-org.github.io/synapse/latest/openid.html">OpenID</link>.
-   </para>
-  </note>
- </section>
- <section xml:id="module-services-matrix-element-web">
-  <title>Element (formerly known as Riot) Web Client</title>
-
-  <para>
-   <link xlink:href="https://github.com/vector-im/riot-web/">Element Web</link> is
-   the reference web client for Matrix and developed by the core team at
-   matrix.org. Element was formerly known as Riot.im, see the
-   <link xlink:href="https://element.io/blog/welcome-to-element/">Element introductory blog post</link>
-   for more information. The following snippet can be optionally added to the code before
-   to complete the synapse installation with a web client served at
-   <code>https://element.myhostname.example.org</code> and
-   <code>https://element.example.org</code>. Alternatively, you can use the hosted
-   copy at <link xlink:href="https://app.element.io/">https://app.element.io/</link>,
-   or use other web clients or native client applications. Due to the
-   <literal>/.well-known</literal> urls set up done above, many clients should
-   fill in the required connection details automatically when you enter your
-   Matrix Identifier. See
-   <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
-   Matrix Now!</link> for a list of existing clients and their supported
-   featureset.
-<programlisting>
-{
-  services.nginx.virtualHosts."element.${fqdn}" = {
-    <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
-    <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-    <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [
-      "element.${config.networking.domain}"
-    ];
-
-    <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.element-web.override {
-      conf = {
-        default_server_config = clientConfig; # see `clientConfig` from the snippet above.
-      };
-    };
-  };
-}
-</programlisting>
-  </para>
-
-  <note>
-   <para>
-    The Element developers do not recommend running Element and your Matrix
-    homeserver on the same fully-qualified domain name for security reasons. In
-    the example, this means that you should not reuse the
-    <literal>myhostname.example.org</literal> virtualHost to also serve Element,
-    but instead serve it on a different subdomain, like
-    <literal>element.example.org</literal> in the example. See the
-    <link xlink:href="https://github.com/vector-im/element-web/tree/v1.10.0#important-security-notes">Element
-    Important Security Notes</link> for more information on this subject.
-   </para>
-  </note>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/airsonic.nix b/nixpkgs/nixos/modules/services/misc/airsonic.nix
index 01e330929ca1..b8e9dcaf4663 100644
--- a/nixpkgs/nixos/modules/services/misc/airsonic.nix
+++ b/nixpkgs/nixos/modules/services/misc/airsonic.nix
@@ -9,7 +9,7 @@ in {
   options = {
 
     services.airsonic = {
-      enable = mkEnableOption "Airsonic, the Free and Open Source media streaming server (fork of Subsonic and Libresonic)";
+      enable = mkEnableOption (lib.mdDoc "Airsonic, the Free and Open Source media streaming server (fork of Subsonic and Libresonic)");
 
       user = mkOption {
         type = types.str;
@@ -48,7 +48,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 4040;
         description = lib.mdDoc ''
           The port on which Airsonic will listen for
diff --git a/nixpkgs/nixos/modules/services/misc/ananicy.nix b/nixpkgs/nixos/modules/services/misc/ananicy.nix
index bf33b2c0602b..d2287fba6afc 100644
--- a/nixpkgs/nixos/modules/services/misc/ananicy.nix
+++ b/nixpkgs/nixos/modules/services/misc/ananicy.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.ananicy = {
-      enable = mkEnableOption "Ananicy, an auto nice daemon";
+      enable = mkEnableOption (lib.mdDoc "Ananicy, an auto nice daemon");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/ankisyncd.nix b/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
index fe71b528b6ab..5198b8242023 100644
--- a/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
+++ b/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
@@ -28,7 +28,7 @@ let
 in
   {
     options.services.ankisyncd = {
-      enable = mkEnableOption "ankisyncd";
+      enable = mkEnableOption (lib.mdDoc "ankisyncd");
 
       package = mkOption {
         type = types.package;
@@ -44,7 +44,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 27701;
         description = lib.mdDoc "ankisyncd port";
       };
diff --git a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
index c428cfbc67e0..598907aaf1c6 100644
--- a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
@@ -40,7 +40,7 @@ in {
     port = mkOption {
       description = lib.mdDoc "Port number the broker should listen on.";
       default = 9092;
-      type = types.int;
+      type = types.port;
     };
 
     hostname = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/atuin.nix b/nixpkgs/nixos/modules/services/misc/atuin.nix
new file mode 100644
index 000000000000..202bd4dfca11
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/atuin.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.atuin;
+in
+{
+  options = {
+    services.atuin = {
+      enable = mkEnableOption (mdDoc "Enable server for shell history sync with atuin");
+
+      openRegistration = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc "Allow new user registrations with the atuin server.";
+      };
+
+      path = mkOption {
+        type = types.str;
+        default = "";
+        description = mdDoc "A path to prepend to all the routes of the server.";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = mdDoc "The host address the atuin server should listen on.";
+      };
+
+      maxHistoryLength = mkOption {
+        type = types.int;
+        default = 8192;
+        description = mdDoc "The max length of each history item the atuin server should store.";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 8888;
+        description = mdDoc "The port the atuin server should listen on.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc "Open ports in the firewall for the atuin server.";
+      };
+
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc "Create the database and database user locally.";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    # enable postgres to host atuin db
+    services.postgresql = {
+      enable = true;
+      ensureUsers = [{
+        name = "atuin";
+        ensurePermissions = {
+          "DATABASE atuin" = "ALL PRIVILEGES";
+        };
+      }];
+      ensureDatabases = [ "atuin" ];
+    };
+
+    systemd.services.atuin = {
+      description = "atuin server";
+      requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ];
+      after = [ "network.target" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ] ;
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.atuin}/bin/atuin server start";
+        RuntimeDirectory = "atuin";
+        RuntimeDirectoryMode = "0700";
+        DynamicUser = true;
+      };
+
+      environment = {
+        ATUIN_HOST = cfg.host;
+        ATUIN_PORT = toString cfg.port;
+        ATUIN_MAX_HISTORY_LENGTH = toString cfg.maxHistoryLength;
+        ATUIN_OPEN_REGISTRATION = boolToString cfg.openRegistration;
+        ATUIN_DB_URI =  mkIf cfg.database.createLocally "postgresql:///atuin";
+        ATUIN_PATH = cfg.path;
+        ATUIN_CONFIG_DIR = "/run/atuin"; # required to start, but not used as configuration is via environment variables
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/autorandr.nix b/nixpkgs/nixos/modules/services/misc/autorandr.nix
index 06f24d7c7e75..aa96acb61306 100644
--- a/nixpkgs/nixos/modules/services/misc/autorandr.nix
+++ b/nixpkgs/nixos/modules/services/misc/autorandr.nix
@@ -149,15 +149,15 @@ let
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           Output scale configuration.
 
           Either configure by pixels or a scaling factor. When using pixel method the
-          <citerefentry><refentrytitle>xrandr</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+          {manpage}`xrandr(1)`
           option
-          <parameter class="command">--scale-from</parameter>
+          `--scale-from`
           will be used; when using factor method the option
-          <parameter class="command">--scale</parameter>
+          `--scale`
           will be used.
 
           This option is a shortcut version of the transform option and they are mutually
@@ -242,7 +242,7 @@ in {
   options = {
 
     services.autorandr = {
-      enable = mkEnableOption "handling of hotplug and sleep events by autorandr";
+      enable = mkEnableOption (lib.mdDoc "handling of hotplug and sleep events by autorandr");
 
       defaultTarget = mkOption {
         default = "default";
@@ -254,11 +254,17 @@ in {
         '';
       };
 
+      ignoreLid = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc "Treat outputs as connected even if their lids are closed";
+      };
+
       hooks = mkOption {
         type = hooksModule;
         description = lib.mdDoc "Global hook scripts";
         default = { };
-        example = ''
+        example = literalExpression ''
           {
             postswitch = {
               "notify-i3" = "''${pkgs.i3}/bin/i3-msg restart";
@@ -279,7 +285,7 @@ in {
                     exit 1
                 esac
                 echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge
-              '''
+              ''';
             };
           }
         '';
@@ -340,7 +346,13 @@ in {
       startLimitIntervalSec = 5;
       startLimitBurst = 1;
       serviceConfig = {
-        ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
+        ExecStart = ''
+          ${pkgs.autorandr}/bin/autorandr \
+            --batch \
+            --change \
+            --default ${cfg.defaultTarget} \
+            ${optionalString cfg.ignoreLid "--ignore-lid"}
+        '';
         Type = "oneshot";
         RemainAfterExit = false;
         KillMode = "process";
diff --git a/nixpkgs/nixos/modules/services/misc/autosuspend.nix b/nixpkgs/nixos/modules/services/misc/autosuspend.nix
new file mode 100644
index 000000000000..b3e362533a09
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/autosuspend.nix
@@ -0,0 +1,230 @@
+{ config, pkgs, lib, ... }:
+let
+  inherit (lib) mapAttrs' nameValuePair filterAttrs types mkEnableOption
+    mdDoc mkPackageOptionMD mkOption literalExpression mkIf flatten
+    maintainers attrValues;
+
+  cfg = config.services.autosuspend;
+
+  settingsFormat = pkgs.formats.ini { };
+
+  checks =
+    mapAttrs'
+      (n: v: nameValuePair "check.${n}" (filterAttrs (_: v: v != null) v))
+      cfg.checks;
+  wakeups =
+    mapAttrs'
+      (n: v: nameValuePair "wakeup.${n}" (filterAttrs (_: v: v != null) v))
+      cfg.wakeups;
+
+  # Whether the given check is enabled
+  hasCheck = class:
+    (filterAttrs
+      (n: v: v.enabled && (if v.class == null then n else v.class) == class)
+      cfg.checks)
+    != { };
+
+  # Dependencies needed by specific checks
+  dependenciesForChecks = {
+    "Smb" = pkgs.samba;
+    "XIdleTime" = [ pkgs.xprintidle pkgs.sudo ];
+  };
+
+  autosuspend-conf =
+    settingsFormat.generate "autosuspend.conf" ({ general = cfg.settings; } // checks // wakeups);
+
+  autosuspend = cfg.package;
+
+  checkType = types.submodule {
+    freeformType = settingsFormat.type.nestedTypes.elemType;
+
+    options.enabled = mkEnableOption (mdDoc "this activity check") // { default = true; };
+
+    options.class = mkOption {
+      default = null;
+      type = with types; nullOr (enum [
+        "ActiveCalendarEvent"
+        "ActiveConnection"
+        "ExternalCommand"
+        "JsonPath"
+        "Kodi"
+        "KodiIdleTime"
+        "LastLogActivity"
+        "Load"
+        "LogindSessionsIdle"
+        "Mpd"
+        "NetworkBandwidth"
+        "Ping"
+        "Processes"
+        "Smb"
+        "Users"
+        "XIdleTime"
+        "XPath"
+      ]);
+      description = mdDoc ''
+        Name of the class implementing the check.  If this option is not specified, the check's
+        name must represent a valid internal check class.
+      '';
+    };
+  };
+
+  wakeupType = types.submodule {
+    freeformType = settingsFormat.type.nestedTypes.elemType;
+
+    options.enabled = mkEnableOption (mdDoc "this wake-up check") // { default = true; };
+
+    options.class = mkOption {
+      default = null;
+      type = with types; nullOr (enum [
+        "Calendar"
+        "Command"
+        "File"
+        "Periodic"
+        "SystemdTimer"
+        "XPath"
+        "XPathDelta"
+      ]);
+      description = mdDoc ''
+        Name of the class implementing the check.  If this option is not specified, the check's
+        name must represent a valid internal check class.
+      '';
+    };
+  };
+in
+{
+  options = {
+    services.autosuspend = {
+      enable = mkEnableOption (mdDoc "the autosuspend daemon");
+
+      package = mkPackageOptionMD pkgs "autosuspend" { };
+
+      settings = mkOption {
+        type = types.submodule {
+          freeformType = settingsFormat.type.nestedTypes.elemType;
+
+          options = {
+            # Provide reasonable defaults for these two (required) options
+            suspend_cmd = mkOption {
+              default = "systemctl suspend";
+              type = with types; str;
+              description = mdDoc ''
+                The command to execute in case the host shall be suspended. This line can contain
+                additional command line arguments to the command to execute.
+              '';
+            };
+            wakeup_cmd = mkOption {
+              default = ''sh -c 'echo 0 > /sys/class/rtc/rtc0/wakealarm && echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm' '';
+              type = with types; str;
+              description = mdDoc ''
+                The command to execute for scheduling a wake up of the system. The given string is
+                processed using Python’s `str.format()` and a format argument called `timestamp`
+                encodes the UTC timestamp of the planned wake up time (float). Additionally `iso`
+                can be used to acquire the timestamp in ISO 8601 format.
+              '';
+            };
+          };
+        };
+        default = { };
+        example = literalExpression ''
+          {
+            enable = true;
+            interval = 30;
+            idle_time = 120;
+          }
+        '';
+        description = mdDoc ''
+          Configuration for autosuspend, see
+          <https://autosuspend.readthedocs.io/en/latest/configuration_file.html#general-configuration>
+          for supported values.
+        '';
+      };
+
+      checks = mkOption {
+        default = { };
+        type = with types; attrsOf checkType;
+        description = mdDoc ''
+          Checks for activity.  For more information, see:
+           - <https://autosuspend.readthedocs.io/en/latest/configuration_file.html#activity-check-configuration>
+           - <https://autosuspend.readthedocs.io/en/latest/available_checks.html>
+        '';
+        example = literalExpression ''
+          {
+            # Basic activity check configuration.
+            # The check class name is derived from the section header (Ping in this case).
+            # Remember to enable desired checks. They are disabled by default.
+            Ping = {
+              hosts = "192.168.0.7";
+            };
+
+            # This check is disabled.
+            Smb.enabled = false;
+
+            # Example for a custom check name.
+            # This will use the Users check with the custom name RemoteUsers.
+            # Custom names are necessary in case a check class is used multiple times.
+            # Custom names can also be used for clarification.
+            RemoteUsers = {
+              class = "Users";
+              name = ".*";
+              terminal = ".*";
+              host = "[0-9].*";
+            };
+
+            # Here the Users activity check is used again with different settings and a different name
+            LocalUsers = {
+              class = "Users";
+              name = ".*";
+              terminal = ".*";
+              host = "localhost";
+            };
+          }
+        '';
+      };
+
+      wakeups = mkOption {
+        default = { };
+        type = with types; attrsOf wakeupType;
+        description = mdDoc ''
+          Checks for wake up.  For more information, see:
+           - <https://autosuspend.readthedocs.io/en/latest/configuration_file.html#wake-up-check-configuration>
+           - <https://autosuspend.readthedocs.io/en/latest/available_wakeups.html>
+        '';
+        example = literalExpression ''
+          {
+            # Wake up checks reuse the same configuration mechanism as activity checks.
+            Calendar = {
+              url = "http://example.org/test.ics";
+            };
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.autosuspend = {
+      description = "A daemon to suspend your server in case of inactivity";
+      documentation = [ "https://autosuspend.readthedocs.io/en/latest/systemd_integration.html" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = flatten (attrValues (filterAttrs (n: _: hasCheck n) dependenciesForChecks));
+      serviceConfig = {
+        ExecStart = ''${autosuspend}/bin/autosuspend -l ${autosuspend}/etc/autosuspend-logging.conf -c ${autosuspend-conf} daemon'';
+      };
+    };
+
+    systemd.services.autosuspend-detect-suspend = {
+      description = "Notifies autosuspend about suspension";
+      documentation = [ "https://autosuspend.readthedocs.io/en/latest/systemd_integration.html" ];
+      wantedBy = [ "sleep.target" ];
+      after = [ "sleep.target" ];
+      serviceConfig = {
+        ExecStart = ''${autosuspend}/bin/autosuspend -l ${autosuspend}/etc/autosuspend-logging.conf -c ${autosuspend-conf} presuspend'';
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [ xlambein ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/bazarr.nix b/nixpkgs/nixos/modules/services/misc/bazarr.nix
index 8c0b4b88e5d7..07c935053591 100644
--- a/nixpkgs/nixos/modules/services/misc/bazarr.nix
+++ b/nixpkgs/nixos/modules/services/misc/bazarr.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.bazarr = {
-      enable = mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr";
+      enable = mkEnableOption (lib.mdDoc "bazarr, a subtitle manager for Sonarr and Radarr");
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/misc/beanstalkd.nix b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
index 498e287ac7d4..4262cae323b9 100644
--- a/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
+++ b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
@@ -12,11 +12,11 @@ in
 
   options = {
     services.beanstalkd = {
-      enable = mkEnableOption "the Beanstalk work queue";
+      enable = mkEnableOption (lib.mdDoc "the Beanstalk work queue");
 
       listen = {
         port = mkOption {
-          type = types.int;
+          type = types.port;
           description = lib.mdDoc "TCP port that will be used to accept client connections.";
           default = 11300;
         };
diff --git a/nixpkgs/nixos/modules/services/misc/bepasty.nix b/nixpkgs/nixos/modules/services/misc/bepasty.nix
index 8d18ef7f1946..70d07629493b 100644
--- a/nixpkgs/nixos/modules/services/misc/bepasty.nix
+++ b/nixpkgs/nixos/modules/services/misc/bepasty.nix
@@ -13,7 +13,7 @@ let
 in
 {
   options.services.bepasty = {
-    enable = mkEnableOption "Bepasty servers";
+    enable = mkEnableOption (lib.mdDoc "Bepasty servers");
 
     servers = mkOption {
       default = {};
diff --git a/nixpkgs/nixos/modules/services/misc/calibre-server.nix b/nixpkgs/nixos/modules/services/misc/calibre-server.nix
index d75c33bab518..77c60381a312 100644
--- a/nixpkgs/nixos/modules/services/misc/calibre-server.nix
+++ b/nixpkgs/nixos/modules/services/misc/calibre-server.nix
@@ -23,7 +23,7 @@ in
   options = {
     services.calibre-server = {
 
-      enable = mkEnableOption "calibre-server";
+      enable = mkEnableOption (lib.mdDoc "calibre-server");
 
       libraries = mkOption {
         description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/services/misc/cfdyndns.nix b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
index 74d7a0b2c629..9cd8b188ffae 100644
--- a/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
+++ b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
@@ -14,7 +14,7 @@ in
 
   options = {
     services.cfdyndns = {
-      enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+      enable = mkEnableOption (lib.mdDoc "Cloudflare Dynamic DNS Client");
 
       email = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/cgminer.nix b/nixpkgs/nixos/modules/services/misc/cgminer.nix
index a67986d30116..fced106cb325 100644
--- a/nixpkgs/nixos/modules/services/misc/cgminer.nix
+++ b/nixpkgs/nixos/modules/services/misc/cgminer.nix
@@ -31,7 +31,7 @@ in
 
     services.cgminer = {
 
-      enable = mkEnableOption "cgminer, an ASIC/FPGA/GPU miner for bitcoin and litecoin";
+      enable = mkEnableOption (lib.mdDoc "cgminer, an ASIC/FPGA/GPU miner for bitcoin and litecoin");
 
       package = mkOption {
         default = pkgs.cgminer;
diff --git a/nixpkgs/nixos/modules/services/misc/clipcat.nix b/nixpkgs/nixos/modules/services/misc/clipcat.nix
index 0c067d23d327..0129de3a9efb 100644
--- a/nixpkgs/nixos/modules/services/misc/clipcat.nix
+++ b/nixpkgs/nixos/modules/services/misc/clipcat.nix
@@ -7,7 +7,7 @@ let
 in {
 
   options.services.clipcat= {
-    enable = mkEnableOption "Clipcat clipboard daemon";
+    enable = mkEnableOption (lib.mdDoc "Clipcat clipboard daemon");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/clipmenu.nix b/nixpkgs/nixos/modules/services/misc/clipmenu.nix
index a31879284e42..1cc8c4c47f7e 100644
--- a/nixpkgs/nixos/modules/services/misc/clipmenu.nix
+++ b/nixpkgs/nixos/modules/services/misc/clipmenu.nix
@@ -7,7 +7,7 @@ let
 in {
 
   options.services.clipmenu = {
-    enable = mkEnableOption "clipmenu, the clipboard management daemon";
+    enable = mkEnableOption (lib.mdDoc "clipmenu, the clipboard management daemon");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/confd.nix b/nixpkgs/nixos/modules/services/misc/confd.nix
index 87a9a25d4913..17c1be57ccbc 100755
--- a/nixpkgs/nixos/modules/services/misc/confd.nix
+++ b/nixpkgs/nixos/modules/services/misc/confd.nix
@@ -17,7 +17,7 @@ let
 
 in {
   options.services.confd = {
-    enable = mkEnableOption "confd service";
+    enable = mkEnableOption (lib.mdDoc "confd service");
 
     backend = mkOption {
       description = lib.mdDoc "Confd config storage backend to use.";
diff --git a/nixpkgs/nixos/modules/services/misc/devmon.nix b/nixpkgs/nixos/modules/services/misc/devmon.nix
index e4a3348646b1..bd0b738b7018 100644
--- a/nixpkgs/nixos/modules/services/misc/devmon.nix
+++ b/nixpkgs/nixos/modules/services/misc/devmon.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.devmon = {
-      enable = mkEnableOption "devmon, an automatic device mounting daemon";
+      enable = mkEnableOption (lib.mdDoc "devmon, an automatic device mounting daemon");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/misc/disnix.nix b/nixpkgs/nixos/modules/services/misc/disnix.nix
index 08e0a321a238..1cdfeef57cef 100644
--- a/nixpkgs/nixos/modules/services/misc/disnix.nix
+++ b/nixpkgs/nixos/modules/services/misc/disnix.nix
@@ -17,7 +17,7 @@ in
 
     services.disnix = {
 
-      enable = mkEnableOption "Disnix";
+      enable = mkEnableOption (lib.mdDoc "Disnix");
 
       enableMultiUser = mkOption {
         type = types.bool;
@@ -25,7 +25,7 @@ in
         description = lib.mdDoc "Whether to support multi-user mode by enabling the Disnix D-Bus service";
       };
 
-      useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat";
+      useWebServiceInterface = mkEnableOption (lib.mdDoc "the DisnixWebService interface running on Apache Tomcat");
 
       package = mkOption {
         type = types.path;
@@ -34,7 +34,7 @@ in
         defaultText = literalExpression "pkgs.disnix";
       };
 
-      enableProfilePath = mkEnableOption "exposing the Disnix profiles in the system's PATH";
+      enableProfilePath = mkEnableOption (lib.mdDoc "exposing the Disnix profiles in the system's PATH");
 
       profiles = mkOption {
         type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/docker-registry.nix b/nixpkgs/nixos/modules/services/misc/docker-registry.nix
index 7a9907fd3510..8ea81f9a7ee2 100644
--- a/nixpkgs/nixos/modules/services/misc/docker-registry.nix
+++ b/nixpkgs/nixos/modules/services/misc/docker-registry.nix
@@ -47,7 +47,15 @@ let
 
 in {
   options.services.dockerRegistry = {
-    enable = mkEnableOption "Docker Registry";
+    enable = mkEnableOption (lib.mdDoc "Docker Registry");
+
+    package = mkOption {
+      type = types.package;
+      description = mdDoc "Which Docker registry package to use.";
+      default = pkgs.docker-distribution;
+      defaultText = literalExpression "pkgs.docker-distribution";
+      example = literalExpression "pkgs.gitlab-container-registry";
+    };
 
     listenAddress = mkOption {
       description = lib.mdDoc "Docker registry host or ip to bind to.";
@@ -76,7 +84,7 @@ in {
       description = lib.mdDoc "Enable delete for manifests and blobs.";
     };
 
-    enableRedisCache = mkEnableOption "redis as blob cache";
+    enableRedisCache = mkEnableOption (lib.mdDoc "redis as blob cache");
 
     redisUrl = mkOption {
       type = types.str;
@@ -98,7 +106,7 @@ in {
       type = types.attrs;
     };
 
-    enableGarbageCollect = mkEnableOption "garbage collect";
+    enableGarbageCollect = mkEnableOption (lib.mdDoc "garbage collect");
 
     garbageCollectDates = mkOption {
       default = "daily";
@@ -117,7 +125,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       script = ''
-        ${pkgs.docker-distribution}/bin/registry serve ${configFile}
+        ${cfg.package}/bin/registry serve ${configFile}
       '';
 
       serviceConfig = {
@@ -136,7 +144,7 @@ in {
       serviceConfig.Type = "oneshot";
 
       script = ''
-        ${pkgs.docker-distribution}/bin/registry garbage-collect ${configFile}
+        ${cfg.package}/bin/registry garbage-collect ${configFile}
         /run/current-system/systemd/bin/systemctl restart docker-registry.service
       '';
 
diff --git a/nixpkgs/nixos/modules/services/misc/domoticz.nix b/nixpkgs/nixos/modules/services/misc/domoticz.nix
index d01158b327a6..fd9fcf0b78eb 100644
--- a/nixpkgs/nixos/modules/services/misc/domoticz.nix
+++ b/nixpkgs/nixos/modules/services/misc/domoticz.nix
@@ -12,7 +12,7 @@ in {
   options = {
 
     services.domoticz = {
-      enable = mkEnableOption pkgDesc;
+      enable = mkEnableOption (lib.mdDoc pkgDesc);
 
       bind = mkOption {
         type = types.str;
@@ -21,7 +21,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8080;
         description = lib.mdDoc "Port to bind to for HTTP, set to 0 to disable HTTP.";
       };
diff --git a/nixpkgs/nixos/modules/services/misc/duckling.nix b/nixpkgs/nixos/modules/services/misc/duckling.nix
index 55a87fccf8e7..4d06ca7fa667 100644
--- a/nixpkgs/nixos/modules/services/misc/duckling.nix
+++ b/nixpkgs/nixos/modules/services/misc/duckling.nix
@@ -7,7 +7,7 @@ let
 in {
   options = {
     services.duckling = {
-      enable = mkEnableOption "duckling";
+      enable = mkEnableOption (lib.mdDoc "duckling");
 
       port = mkOption {
         type = types.port;
diff --git a/nixpkgs/nixos/modules/services/misc/dwm-status.nix b/nixpkgs/nixos/modules/services/misc/dwm-status.nix
index 92705e5515ea..de3e28c41d27 100644
--- a/nixpkgs/nixos/modules/services/misc/dwm-status.nix
+++ b/nixpkgs/nixos/modules/services/misc/dwm-status.nix
@@ -22,7 +22,7 @@ in
 
     services.dwm-status = {
 
-      enable = mkEnableOption "dwm-status user service";
+      enable = mkEnableOption (lib.mdDoc "dwm-status user service");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
index 4d748ec6eb66..0f92265ccbea 100644
--- a/nixpkgs/nixos/modules/services/misc/dysnomia.nix
+++ b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
@@ -114,7 +114,7 @@ in
       };
 
       components = mkOption {
-        description = lib.mdDoc "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";
+        description = lib.mdDoc "An attribute 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;
       };
diff --git a/nixpkgs/nixos/modules/services/misc/etcd.nix b/nixpkgs/nixos/modules/services/misc/etcd.nix
index 3343e94778a2..7bc7a9499113 100644
--- a/nixpkgs/nixos/modules/services/misc/etcd.nix
+++ b/nixpkgs/nixos/modules/services/misc/etcd.nix
@@ -15,6 +15,8 @@ in {
       type = types.bool;
     };
 
+    package = mkPackageOptionMD pkgs "etcd" { };
+
     name = mkOption {
       description = lib.mdDoc "Etcd unique node name.";
       default = config.networking.hostName;
@@ -167,10 +169,11 @@ in {
         ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
         ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
         ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
+        ETCD_PEER_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
         ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
         ETCD_PEER_CERT_FILE = cfg.peerCertFile;
         ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
-        ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
+        ETCD_CLIENT_CERT_AUTH = toString cfg.clientCertAuth;
         ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
         ETCD_CERT_FILE = cfg.certFile;
         ETCD_KEY_FILE = cfg.keyFile;
@@ -186,13 +189,13 @@ in {
 
       serviceConfig = {
         Type = "notify";
-        ExecStart = "${pkgs.etcd}/bin/etcd";
+        ExecStart = "${cfg.package}/bin/etcd";
         User = "etcd";
         LimitNOFILE = 40000;
       };
     };
 
-    environment.systemPackages = [ pkgs.etcd ];
+    environment.systemPackages = [ cfg.package ];
 
     users.users.etcd = {
       isSystemUser = true;
diff --git a/nixpkgs/nixos/modules/services/misc/etebase-server.nix b/nixpkgs/nixos/modules/services/misc/etebase-server.nix
index 1359c265c8f1..045048a1a2e3 100644
--- a/nixpkgs/nixos/modules/services/misc/etebase-server.nix
+++ b/nixpkgs/nixos/modules/services/misc/etebase-server.nix
@@ -133,10 +133,10 @@ in
           };
         };
         default = {};
-        description = ''
-          Configuration for <package>etebase-server</package>. Refer to
-          <link xlink:href="https://github.com/etesync/server/blob/master/etebase-server.ini.example"/>
-          and <link xlink:href="https://github.com/etesync/server/wiki"/>
+        description = lib.mdDoc ''
+          Configuration for `etebase-server`. Refer to
+          <https://github.com/etesync/server/blob/master/etebase-server.ini.example>
+          and <https://github.com/etesync/server/wiki>
           for details on supported values.
         '';
         example = {
@@ -162,7 +162,7 @@ in
 
     environment.systemPackages = with pkgs; [
       (runCommand "etebase-server" {
-        buildInputs = [ makeWrapper ];
+        nativeBuildInputs = [ makeWrapper ];
       } ''
         makeWrapper ${pythonEnv}/bin/etebase-server \
           $out/bin/etebase-server \
@@ -179,21 +179,21 @@ in
       description = "An Etebase (EteSync 2.0) server";
       after = [ "network.target" "systemd-tmpfiles-setup.service" ];
       wantedBy = [ "multi-user.target" ];
+      path = [ pythonEnv ];
       serviceConfig = {
         User = cfg.user;
         Restart = "always";
         WorkingDirectory = cfg.dataDir;
       };
       environment = {
-        PYTHONPATH = "${pythonEnv}/${pkgs.python3.sitePackages}";
         ETEBASE_EASY_CONFIG_PATH = configIni;
       };
       preStart = ''
         # Auto-migrate on first run or if the package has changed
         versionFile="${cfg.dataDir}/src-version"
         if [[ $(cat "$versionFile" 2>/dev/null) != ${pkgs.etebase-server} ]]; then
-          ${pythonEnv}/bin/etebase-server migrate --no-input
-          ${pythonEnv}/bin/etebase-server collectstatic --no-input --clear
+          etebase-server migrate --no-input
+          etebase-server collectstatic --no-input --clear
           echo ${pkgs.etebase-server} > "$versionFile"
         fi
       '';
@@ -204,7 +204,7 @@ in
           else "-b 0.0.0.0 -p ${toString cfg.port}";
         in ''
           cd "${pythonEnv}/lib/etebase-server";
-          ${pythonEnv}/bin/daphne ${networking} \
+          daphne ${networking} \
             etebase_server.asgi:application
         '';
     };
diff --git a/nixpkgs/nixos/modules/services/misc/etesync-dav.nix b/nixpkgs/nixos/modules/services/misc/etesync-dav.nix
index 6a755be8500a..9d99d548d95b 100644
--- a/nixpkgs/nixos/modules/services/misc/etesync-dav.nix
+++ b/nixpkgs/nixos/modules/services/misc/etesync-dav.nix
@@ -7,7 +7,7 @@ let
 in
   {
     options.services.etesync-dav = {
-      enable = mkEnableOption "etesync-dav";
+      enable = mkEnableOption (lib.mdDoc "etesync-dav");
 
       host = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/ethminer.nix b/nixpkgs/nixos/modules/services/misc/ethminer.nix
deleted file mode 100644
index 909c49866e54..000000000000
--- a/nixpkgs/nixos/modules/services/misc/ethminer.nix
+++ /dev/null
@@ -1,117 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.ethminer;
-  poolUrl = escapeShellArg "stratum1+tcp://${cfg.wallet}@${cfg.pool}:${toString cfg.stratumPort}/${cfg.rig}/${cfg.registerMail}";
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.ethminer = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Enable ethminer ether mining.";
-      };
-
-      recheckInterval = mkOption {
-        type = types.ints.unsigned;
-        default = 2000;
-        description = lib.mdDoc "Interval in milliseconds between farm rechecks.";
-      };
-
-      toolkit = mkOption {
-        type = types.enum [ "cuda" "opencl" ];
-        default = "cuda";
-        description = lib.mdDoc "Cuda or opencl toolkit.";
-      };
-
-      apiPort = mkOption {
-        type = types.int;
-        default = -3333;
-        description = lib.mdDoc "Ethminer api port. minus sign puts api in read-only mode.";
-      };
-
-      wallet = mkOption {
-        type = types.str;
-        example = "0x0123456789abcdef0123456789abcdef01234567";
-        description = lib.mdDoc "Ethereum wallet address.";
-      };
-
-      pool = mkOption {
-        type = types.str;
-        example = "eth-us-east1.nanopool.org";
-        description = lib.mdDoc "Mining pool address.";
-      };
-
-      stratumPort = mkOption {
-        type = types.port;
-        default = 9999;
-        description = lib.mdDoc "Stratum protocol tcp port.";
-      };
-
-      rig = mkOption {
-        type = types.str;
-        default = "mining-rig-name";
-        description = lib.mdDoc "Mining rig name.";
-      };
-
-      registerMail = mkOption {
-        type = types.str;
-        example = "email%40example.org";
-        description = lib.mdDoc "Url encoded email address to register with pool.";
-      };
-
-      maxPower = mkOption {
-        type = types.ints.unsigned;
-        default = 113;
-        description = lib.mdDoc "Miner max watt usage.";
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    systemd.services.ethminer = {
-      path = optional (cfg.toolkit == "cuda") [ pkgs.cudaPackages.cudatoolkit ];
-      description = "ethminer ethereum mining service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-
-      serviceConfig = {
-        DynamicUser = true;
-        ExecStartPre = "${pkgs.ethminer}/bin/.ethminer-wrapped --list-devices";
-        ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}";
-        Restart = "always";
-      };
-
-      environment = mkIf (cfg.toolkit == "cuda") {
-        LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
-      };
-
-      script = ''
-        ${pkgs.ethminer}/bin/.ethminer-wrapped \
-          --farm-recheck ${toString cfg.recheckInterval} \
-          --report-hashrate \
-          --${cfg.toolkit} \
-          --api-port ${toString cfg.apiPort} \
-          --pool ${poolUrl}
-      '';
-
-    };
-
-  };
-
-}
diff --git a/nixpkgs/nixos/modules/services/misc/exhibitor.nix b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
index 3db42b8e4a43..91a87b55af59 100644
--- a/nixpkgs/nixos/modules/services/misc/exhibitor.nix
+++ b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
@@ -68,17 +68,12 @@ in
 {
   options = {
     services.exhibitor = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "
-          Whether to enable the exhibitor server.
-        ";
-      };
+      enable = mkEnableOption (lib.mdDoc "exhibitor server");
+
       # See https://github.com/soabase/exhibitor/wiki/Running-Exhibitor for what these mean
       # General options for any type of config
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8080;
         description = lib.mdDoc ''
           The port for exhibitor to listen on and communicate with other exhibitors.
diff --git a/nixpkgs/nixos/modules/services/misc/felix.nix b/nixpkgs/nixos/modules/services/misc/felix.nix
index 7654ad284409..306d4cf0d7cf 100644
--- a/nixpkgs/nixos/modules/services/misc/felix.nix
+++ b/nixpkgs/nixos/modules/services/misc/felix.nix
@@ -17,7 +17,7 @@ in
 
     services.felix = {
 
-      enable = mkEnableOption "the Apache Felix OSGi service";
+      enable = mkEnableOption (lib.mdDoc "the Apache Felix OSGi service");
 
       bundles = mkOption {
         type = types.listOf types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/freeswitch.nix b/nixpkgs/nixos/modules/services/misc/freeswitch.nix
index 8a74b229ce34..b8b81e586944 100644
--- a/nixpkgs/nixos/modules/services/misc/freeswitch.nix
+++ b/nixpkgs/nixos/modules/services/misc/freeswitch.nix
@@ -18,15 +18,15 @@ let
 in {
   options = {
     services.freeswitch = {
-      enable = mkEnableOption "FreeSWITCH";
+      enable = mkEnableOption (lib.mdDoc "FreeSWITCH");
       enableReload = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Issue the <literal>reloadxml</literal> command to FreeSWITCH when configuration directory changes (instead of restart).
-          See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Reloading">FreeSWITCH documentation</link> for more info.
-          The configuration directory is exposed at <filename>/etc/freeswitch</filename>.
-          See also <literal>systemd.services.*.restartIfChanged</literal>.
+        description = lib.mdDoc ''
+          Issue the `reloadxml` command to FreeSWITCH when configuration directory changes (instead of restart).
+          See [FreeSWITCH documentation](https://freeswitch.org/confluence/display/FREESWITCH/Reloading) for more info.
+          The configuration directory is exposed at {file}`/etc/freeswitch`.
+          See also `systemd.services.*.restartIfChanged`.
         '';
       };
       configTemplate = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/fstrim.nix b/nixpkgs/nixos/modules/services/misc/fstrim.nix
index 83e7ca359b22..55fb24e29272 100644
--- a/nixpkgs/nixos/modules/services/misc/fstrim.nix
+++ b/nixpkgs/nixos/modules/services/misc/fstrim.nix
@@ -11,7 +11,7 @@ in {
   options = {
 
     services.fstrim = {
-      enable = mkEnableOption "periodic SSD TRIM of mounted partitions in background";
+      enable = mkEnableOption (lib.mdDoc "periodic SSD TRIM of mounted partitions in background");
 
       interval = mkOption {
         type = types.str;
@@ -34,7 +34,7 @@ in {
 
     systemd.timers.fstrim = {
       timerConfig = {
-        OnCalendar = cfg.interval;
+        OnCalendar = [ "" cfg.interval ];
       };
       wantedBy = [ "timers.target" ];
     };
diff --git a/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
index daa0e22e327a..eff725f5a868 100644
--- a/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
+++ b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
@@ -10,7 +10,7 @@ let
     Connection = ${cfg.device.connection}
     SynchronizeTime = ${if cfg.device.synchronizeTime then "yes" else "no"}
     LogFormat = ${cfg.log.format}
-    ${if (cfg.device.pin != null) then "PIN = ${cfg.device.pin}" else ""}
+    ${optionalString (cfg.device.pin != null) "PIN = ${cfg.device.pin}"}
     ${cfg.extraConfig.gammu}
 
 
@@ -33,10 +33,10 @@ let
     ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "native_pgsql") (
       with cfg.backend; ''
         Driver = ${sql.driver}
-        ${if (sql.database!= null) then "Database = ${sql.database}" else ""}
-        ${if (sql.host != null) then "Host = ${sql.host}" else ""}
-        ${if (sql.user != null) then "User = ${sql.user}" else ""}
-        ${if (sql.password != null) then "Password = ${sql.password}" else ""}
+        ${optionalString (sql.database!= null) "Database = ${sql.database}"}
+        ${optionalString (sql.host != null) "Host = ${sql.host}"}
+        ${optionalString (sql.user != null) "User = ${sql.user}"}
+        ${optionalString (sql.password != null) "Password = ${sql.password}"}
       '')}
 
     ${cfg.extraConfig.smsd}
@@ -45,15 +45,15 @@ let
   initDBDir = "share/doc/gammu/examples/sql";
 
   gammuPackage = with cfg.backend; (pkgs.gammu.override {
-    dbiSupport = (service == "sql" && sql.driver == "sqlite");
-    postgresSupport = (service == "sql" && sql.driver == "native_pgsql");
+    dbiSupport = service == "sql" && sql.driver == "sqlite";
+    postgresSupport = service == "sql" && sql.driver == "native_pgsql";
   });
 
 in {
   options = {
     services.gammu-smsd = {
 
-      enable = mkEnableOption "gammu-smsd daemon";
+      enable = mkEnableOption (lib.mdDoc "gammu-smsd daemon");
 
       user = mkOption {
         type = types.str;
@@ -192,7 +192,7 @@ in {
           password = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = lib.mdDoc "User password used for connetion to the database";
+            description = lib.mdDoc "User password used for connection to the database";
           };
         };
       };
diff --git a/nixpkgs/nixos/modules/services/misc/geoipupdate.nix b/nixpkgs/nixos/modules/services/misc/geoipupdate.nix
index c2007f25fb34..27c1157e9a8c 100644
--- a/nixpkgs/nixos/modules/services/misc/geoipupdate.nix
+++ b/nixpkgs/nixos/modules/services/misc/geoipupdate.nix
@@ -11,10 +11,9 @@ in
 
   options = {
     services.geoipupdate = {
-      enable = lib.mkEnableOption ''
-        periodic downloading of GeoIP databases using
-        <productname>geoipupdate</productname>.
-      '';
+      enable = lib.mkEnableOption (lib.mdDoc ''
+        periodic downloading of GeoIP databases using geoipupdate.
+      '');
 
       interval = lib.mkOption {
         type = lib.types.str;
@@ -36,21 +35,20 @@ in
             ProxyUserPassword = { _secret = "/run/keys/proxy_pass"; };
           }
         '';
-        description = ''
-          <productname>geoipupdate</productname> configuration
-          options. See
-          <link xlink:href="https://github.com/maxmind/geoipupdate/blob/main/doc/GeoIP.conf.md"/>
+        description = lib.mdDoc ''
+          geoipupdate configuration options. See
+          <https://github.com/maxmind/geoipupdate/blob/main/doc/GeoIP.conf.md>
           for a full list of available options.
 
           Settings containing secret data should be set to an
           attribute set containing the attribute
-          <literal>_secret</literal> - a string pointing to a file
+          `_secret` - 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>GeoIP.conf</filename> file, the
-          <literal>ProxyUserPassword</literal> key will be set to the
+          {file}`GeoIP.conf` file, the
+          `ProxyUserPassword` key will be set to the
           contents of the
-          <filename>/run/keys/proxy_pass</filename> file.
+          {file}`/run/keys/proxy_pass` file.
         '';
         type = lib.types.submodule {
           freeformType =
@@ -85,13 +83,12 @@ in
 
             LicenseKey = lib.mkOption {
               type = with lib.types; either path (attrsOf path);
-              description = ''
-                A file containing the
-                <productname>MaxMind</productname> license key.
+              description = lib.mdDoc ''
+                A file containing the MaxMind license key.
 
                 Always handled as a secret whether the value is
-                wrapped in a <literal>{ _secret = ...; }</literal>
-                attrset or not (refer to <xref linkend="opt-services.geoipupdate.settings"/> for
+                wrapped in a `{ _secret = ...; }`
+                attrset or not (refer to [](#opt-services.geoipupdate.settings) for
                 details).
               '';
               apply = x: if isAttrs x then x else { _secret = x; };
@@ -186,7 +183,7 @@ in
         DynamicUser = true;
         ReadWritePaths = cfg.settings.DatabaseDirectory;
         RuntimeDirectory = "geoipupdate";
-        RuntimeDirectoryMode = 0700;
+        RuntimeDirectoryMode = "0700";
         CapabilityBoundingSet = "";
         PrivateDevices = true;
         PrivateMounts = true;
@@ -200,7 +197,7 @@ in
         ProtectKernelTunables = true;
         ProtectProc = "invisible";
         ProcSubset = "pid";
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
         RestrictRealtime = true;
         RestrictNamespaces = true;
diff --git a/nixpkgs/nixos/modules/services/misc/gitea.nix b/nixpkgs/nixos/modules/services/misc/gitea.nix
index 048ff3211654..5c35cfa177a2 100644
--- a/nixpkgs/nixos/modules/services/misc/gitea.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitea.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.gitea;
   opt = options.services.gitea;
-  gitea = cfg.package;
+  exe = lib.getExe cfg.package;
   pg = config.services.postgresql;
   useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
@@ -26,9 +26,18 @@ in
   imports = [
     (mkRenamedOptionModule [ "services" "gitea" "cookieSecure" ] [ "services" "gitea" "settings" "session" "COOKIE_SECURE" ])
     (mkRenamedOptionModule [ "services" "gitea" "disableRegistration" ] [ "services" "gitea" "settings" "service" "DISABLE_REGISTRATION" ])
+    (mkRenamedOptionModule [ "services" "gitea" "domain" ] [ "services" "gitea" "settings" "server" "DOMAIN" ])
+    (mkRenamedOptionModule [ "services" "gitea" "httpAddress" ] [ "services" "gitea" "settings" "server" "HTTP_ADDR" ])
+    (mkRenamedOptionModule [ "services" "gitea" "httpPort" ] [ "services" "gitea" "settings" "server" "HTTP_PORT" ])
     (mkRenamedOptionModule [ "services" "gitea" "log" "level" ] [ "services" "gitea" "settings" "log" "LEVEL" ])
     (mkRenamedOptionModule [ "services" "gitea" "log" "rootPath" ] [ "services" "gitea" "settings" "log" "ROOT_PATH" ])
+    (mkRenamedOptionModule [ "services" "gitea" "rootUrl" ] [ "services" "gitea" "settings" "server" "ROOT_URL" ])
     (mkRenamedOptionModule [ "services" "gitea" "ssh" "clonePort" ] [ "services" "gitea" "settings" "server" "SSH_PORT" ])
+    (mkRenamedOptionModule [ "services" "gitea" "staticRootPath" ] [ "services" "gitea" "settings" "server" "STATIC_ROOT_PATH" ])
+
+    (mkChangedOptionModule [ "services" "gitea" "enableUnixSocket" ] [ "services" "gitea" "settings" "server" "PROTOCOL" ] (
+      config: if config.services.gitea.enableUnixSocket then "http+unix" else "http"
+    ))
 
     (mkRemovedOptionModule [ "services" "gitea" "ssh" "enable" ] "services.gitea.ssh.enable has been migrated into freeform setting services.gitea.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted")
   ];
@@ -57,7 +66,14 @@ in
       stateDir = mkOption {
         default = "/var/lib/gitea";
         type = types.str;
-        description = lib.mdDoc "gitea data directory.";
+        description = lib.mdDoc "Gitea data directory.";
+      };
+
+      customDir = mkOption {
+        default = "${cfg.stateDir}/custom";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/custom"'';
+        type = types.str;
+        description = lib.mdDoc "Gitea custom directory. Used for config, custom templates and other options.";
       };
 
       user = mkOption {
@@ -66,6 +82,12 @@ in
         description = lib.mdDoc "User account under which gitea runs.";
       };
 
+      group = mkOption {
+        type = types.str;
+        default = "gitea";
+        description = lib.mdDoc "Group under which gitea runs.";
+      };
+
       database = {
         type = mkOption {
           type = types.enum [ "sqlite3" "mysql" "postgres" ];
@@ -175,7 +197,7 @@ in
         };
 
         type = mkOption {
-          type = types.enum [ "zip" "rar" "tar" "sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" ];
+          type = types.enum [ "zip" "rar" "tar" "sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" "tar.zst" ];
           default = "zip";
           description = lib.mdDoc "Archive format used to store the dump file.";
         };
@@ -183,7 +205,7 @@ in
         file = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = "Filename to be used for the dump. If `null` a default name is choosen by gitea.";
+          description = lib.mdDoc "Filename to be used for the dump. If `null` a default name is chosen by gitea.";
           example = "gitea-dump";
         };
       };
@@ -216,44 +238,6 @@ in
         description = lib.mdDoc "Path to the git repositories.";
       };
 
-      domain = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = lib.mdDoc "Domain name of your server.";
-      };
-
-      rootUrl = mkOption {
-        type = types.str;
-        default = "http://localhost:3000/";
-        description = lib.mdDoc "Full public URL of gitea server.";
-      };
-
-      httpAddress = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = lib.mdDoc "HTTP listen address.";
-      };
-
-      httpPort = mkOption {
-        type = types.int;
-        default = 3000;
-        description = lib.mdDoc "HTTP listen port.";
-      };
-
-      enableUnixSocket = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Configure Gitea to listen on a unix socket instead of the default TCP port.";
-      };
-
-      staticRootPath = mkOption {
-        type = types.either types.str types.path;
-        default = gitea.data;
-        defaultText = literalExpression "package.data";
-        example = "/var/lib/gitea/data";
-        description = lib.mdDoc "Upper level of template and static files path.";
-      };
-
       mailerPasswordFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -285,7 +269,7 @@ in
             };
           }
         '';
-        type = with types; submodule {
+        type = types.submodule {
           freeformType = format.type;
           options = {
             log = {
@@ -293,27 +277,67 @@ in
                 default = "${cfg.stateDir}/log";
                 defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
                 type = types.str;
-                description = "Root path for log files.";
+                description = lib.mdDoc "Root path for log files.";
               };
               LEVEL = mkOption {
                 default = "Info";
                 type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
-                description = "General log level.";
+                description = lib.mdDoc "General log level.";
               };
             };
 
             server = {
+              PROTOCOL = mkOption {
+                type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
+                default = "http";
+                description = lib.mdDoc ''Listen protocol. `+unix` means "over unix", not "in addition to."'';
+              };
+
+              HTTP_ADDR = mkOption {
+                type = types.either types.str types.path;
+                default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0";
+                defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0"'';
+                description = lib.mdDoc "Listen address. Must be a path when using a unix socket.";
+              };
+
+              HTTP_PORT = mkOption {
+                type = types.port;
+                default = 3000;
+                description = lib.mdDoc "Listen port. Ignored when using a unix socket.";
+              };
+
+              DOMAIN = mkOption {
+                type = types.str;
+                default = "localhost";
+                description = lib.mdDoc "Domain name of your server.";
+              };
+
+              ROOT_URL = mkOption {
+                type = types.str;
+                default = "http://${cfg.settings.server.DOMAIN}:${toString cfg.settings.server.HTTP_PORT}/";
+                defaultText = literalExpression ''"http://''${config.services.gitea.settings.server.DOMAIN}:''${toString config.services.gitea.settings.server.HTTP_PORT}/"'';
+                description = lib.mdDoc "Full public URL of gitea server.";
+              };
+
+              STATIC_ROOT_PATH = mkOption {
+                type = types.either types.str types.path;
+                default = cfg.package.data;
+                defaultText = literalExpression "config.${opt.package}.data";
+                example = "/var/lib/gitea/data";
+                description = lib.mdDoc "Upper level of template and static files path.";
+              };
+
               DISABLE_SSH = mkOption {
                 type = types.bool;
                 default = false;
-                description = "Disable external SSH feature.";
+                description = lib.mdDoc "Disable external SSH feature.";
               };
 
               SSH_PORT = mkOption {
-                type = types.int;
+                type = types.port;
                 default = 22;
                 example = 2222;
-                description = ''
+                description = lib.mdDoc ''
                   SSH port displayed in clone URL.
                   The option is required to configure a service when the external visible port
                   differs from the local listening port i.e. if port forwarding is used.
@@ -322,14 +346,14 @@ in
             };
 
             service = {
-              DISABLE_REGISTRATION = mkEnableOption "the registration lock" // {
-                description = ''
-                  By default any user can create an account on this <literal>gitea</literal> instance.
+              DISABLE_REGISTRATION = mkEnableOption (lib.mdDoc "the registration lock") // {
+                description = lib.mdDoc ''
+                  By default any user can create an account on this `gitea` instance.
                   This can be disabled by using this option.
 
-                  <emphasis>Note:</emphasis> please keep in mind that this should be added after the initial
-                  deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link>
-                  is <literal>true</literal> as the first registered user will be the administrator if
+                  *Note:* please keep in mind that this should be added after the initial
+                  deploy unless [](#opt-services.gitea.useWizard)
+                  is `true` as the first registered user will be the administrator if
                   no install wizard is used.
                 '';
               };
@@ -339,7 +363,7 @@ in
               COOKIE_SECURE = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Marks session cookies as "secure" as a hint for browsers to only send
                   them via HTTPS. This option is recommend, if gitea is being served over HTTPS.
                 '';
@@ -359,12 +383,14 @@ in
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
+      { assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
       }
     ];
 
     services.gitea.settings = {
+      "cron.update_checker".ENABLED = lib.mkDefault false;
+
       database = mkMerge [
         {
           DB_TYPE = cfg.database.type;
@@ -387,27 +413,10 @@ in
         ROOT = cfg.repositoryRoot;
       };
 
-      server = mkMerge [
-        {
-          DOMAIN = cfg.domain;
-          STATIC_ROOT_PATH = toString cfg.staticRootPath;
-          LFS_JWT_SECRET = "#lfsjwtsecret#";
-          ROOT_URL = cfg.rootUrl;
-        }
-        (mkIf cfg.enableUnixSocket {
-          PROTOCOL = "unix";
-          HTTP_ADDR = "/run/gitea/gitea.sock";
-        })
-        (mkIf (!cfg.enableUnixSocket) {
-          HTTP_ADDR = cfg.httpAddress;
-          HTTP_PORT = cfg.httpPort;
-        })
-        (mkIf cfg.lfs.enable {
-          LFS_START_SERVER = true;
-          LFS_CONTENT_PATH = cfg.lfs.contentDir;
-        })
-
-      ];
+      server = mkIf cfg.lfs.enable {
+        LFS_START_SERVER = true;
+        LFS_JWT_SECRET = "#lfsjwtsecret#";
+      };
 
       session = {
         COOKIE_NAME = lib.mkDefault "session";
@@ -426,6 +435,10 @@ in
       oauth2 = {
         JWT_SECRET = "#oauth2jwtsecret#";
       };
+
+      lfs = mkIf cfg.lfs.enable {
+        PATH = cfg.lfs.contentDir;
+      };
     };
 
     services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
@@ -452,88 +465,98 @@ in
     };
 
     systemd.tmpfiles.rules = [
-      "d '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.dump.backupDir}' - ${cfg.user} gitea - -"
-      "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.lfs.contentDir}' - ${cfg.user} gitea - -"
-      "d '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
+      "d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.dump.backupDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.repositoryRoot}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
 
       # If we have a folder or symlink with gitea locales, remove it
       # And symlink the current gitea locales in place
-      "L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
+      "L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
+
+    ] ++ lib.optionals cfg.lfs.enable [
+      "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.lfs.contentDir}' - ${cfg.user} ${cfg.group} - -"
     ];
 
     systemd.services.gitea = {
       description = "gitea";
-      after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
+      after = [ "network.target" ] ++ optional usePostgresql "postgresql.service" ++ optional useMysql "mysql.service";
+      requires = optional usePostgresql "postgresql.service" ++ optional useMysql "mysql.service";
       wantedBy = [ "multi-user.target" ];
-      path = [ gitea pkgs.git ];
+      path = [ cfg.package pkgs.git pkgs.gnupg ];
 
       # In older versions the secret naming for JWT was kind of confusing.
       # The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
-      # wasn't persistant at all.
+      # wasn't persistent at all.
       # To fix that, there is now the file oauth2_jwt_secret containing the
       # values for JWT_SECRET and the file jwt_secret gets renamed to
       # lfs_jwt_secret.
       # We have to consider this to stay compatible with older installations.
       preStart = let
-        runConfig = "${cfg.stateDir}/custom/conf/app.ini";
-        secretKey = "${cfg.stateDir}/custom/conf/secret_key";
-        oauth2JwtSecret = "${cfg.stateDir}/custom/conf/oauth2_jwt_secret";
-        oldLfsJwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret"; # old file for LFS_JWT_SECRET
-        lfsJwtSecret = "${cfg.stateDir}/custom/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
-        internalToken = "${cfg.stateDir}/custom/conf/internal_token";
+        runConfig = "${cfg.customDir}/conf/app.ini";
+        secretKey = "${cfg.customDir}/conf/secret_key";
+        oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
+        oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
+        lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
+        internalToken = "${cfg.customDir}/conf/internal_token";
         replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
       in ''
-        # copy custom configuration and generate a random secret key if needed
+        # copy custom configuration and generate random secrets if needed
         ${optionalString (!cfg.useWizard) ''
           function gitea_setup {
-            cp -f ${configFile} ${runConfig}
+            cp -f '${configFile}' '${runConfig}'
 
-            if [ ! -s ${secretKey} ]; then
-                ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+            if [ ! -s '${secretKey}' ]; then
+                ${exe} generate secret SECRET_KEY > '${secretKey}'
             fi
 
             # Migrate LFS_JWT_SECRET filename
-            if [[ -s ${oldLfsJwtSecret} && ! -s ${lfsJwtSecret} ]]; then
-                mv ${oldLfsJwtSecret} ${lfsJwtSecret}
+            if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
+                mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
             fi
 
-            if [ ! -s ${oauth2JwtSecret} ]; then
-                ${gitea}/bin/gitea generate secret JWT_SECRET > ${oauth2JwtSecret}
+            if [ ! -s '${oauth2JwtSecret}' ]; then
+                ${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
             fi
 
-            if [ ! -s ${lfsJwtSecret} ]; then
-                ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
+            ${lib.optionalString cfg.lfs.enable ''
+            if [ ! -s '${lfsJwtSecret}' ]; then
+                ${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
             fi
+            ''}
 
-            if [ ! -s ${internalToken} ]; then
-                ${gitea}/bin/gitea generate secret INTERNAL_TOKEN > ${internalToken}
+            if [ ! -s '${internalToken}' ]; then
+                ${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
             fi
 
             chmod u+w '${runConfig}'
             ${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
             ${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
             ${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
-            ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
             ${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
 
+            ${lib.optionalString cfg.lfs.enable ''
+              ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
+            ''}
+
             ${lib.optionalString (cfg.mailerPasswordFile != null) ''
               ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
             ''}
@@ -543,30 +566,30 @@ in
         ''}
 
         # run migrations/init the database
-        ${gitea}/bin/gitea migrate
+        ${exe} migrate
 
         # update all hooks' binary paths
-        ${gitea}/bin/gitea admin regenerate hooks
+        ${exe} admin regenerate hooks
 
         # update command option in authorized_keys
         if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
         then
-          ${gitea}/bin/gitea admin regenerate keys
+          ${exe} admin regenerate keys
         fi
       '';
 
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
-        Group = "gitea";
+        Group = cfg.group;
         WorkingDirectory = cfg.stateDir;
-        ExecStart = "${gitea}/bin/gitea web --pid /run/gitea/gitea.pid";
+        ExecStart = "${exe} web --pid /run/gitea/gitea.pid";
         Restart = "always";
         # Runtime directory and mode
         RuntimeDirectory = "gitea";
         RuntimeDirectoryMode = "0755";
         # Access write directories
-        ReadWritePaths = [ cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
+        ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
         UMask = "0027";
         # Capabilities
         CapabilityBoundingSet = "";
@@ -592,13 +615,14 @@ in
         PrivateMounts = true;
         # System Call Filtering
         SystemCallArchitectures = "native";
-        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
+        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
       };
 
       environment = {
         USER = cfg.user;
         HOME = cfg.stateDir;
         GITEA_WORK_DIR = cfg.stateDir;
+        GITEA_CUSTOM = cfg.customDir;
       };
     };
 
@@ -607,12 +631,14 @@ in
         description = "Gitea Service";
         home = cfg.stateDir;
         useDefaultShell = true;
-        group = "gitea";
+        group = cfg.group;
         isSystemUser = true;
       };
     };
 
-    users.groups.gitea = {};
+    users.groups = mkIf (cfg.group == "gitea") {
+      gitea = {};
+    };
 
     warnings =
       optional (cfg.database.password != "") "config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead." ++
@@ -630,8 +656,7 @@ in
     systemd.services.gitea-dump = mkIf cfg.dump.enable {
        description = "gitea dump";
        after = [ "gitea.service" ];
-       wantedBy = [ "default.target" ];
-       path = [ gitea ];
+       path = [ cfg.package ];
 
        environment = {
          USER = cfg.user;
@@ -642,7 +667,7 @@ in
        serviceConfig = {
          Type = "oneshot";
          User = cfg.user;
-         ExecStart = "${gitea}/bin/gitea dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
+         ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
          WorkingDirectory = cfg.dump.backupDir;
        };
     };
@@ -654,5 +679,5 @@ in
       timerConfig.OnCalendar = cfg.dump.interval;
     };
   };
-  meta.maintainers = with lib.maintainers; [ srhb ma27 ];
+  meta.maintainers = with lib.maintainers; [ srhb ma27 thehedgeh0g ];
 }
diff --git a/nixpkgs/nixos/modules/services/misc/gitit.nix b/nixpkgs/nixos/modules/services/misc/gitit.nix
deleted file mode 100644
index f00c03337d91..000000000000
--- a/nixpkgs/nixos/modules/services/misc/gitit.nix
+++ /dev/null
@@ -1,725 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.gitit;
-
-  homeDir = "/var/lib/gitit";
-
-  toYesNo = b: if b then "yes" else "no";
-
-  gititShared = with cfg.haskellPackages; gitit + "/share/" + ghc.targetPrefix + ghc.haskellCompilerName + "/" + gitit.pname + "-" + gitit.version;
-
-  gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self));
-
-  gititSh = hsPkgs: extras: with pkgs; let
-    env = gititWithPkgs hsPkgs extras;
-  in writeScript "gitit" ''
-    #!${runtimeShell}
-    cd $HOME
-    export NIX_GHC="${env}/bin/ghc"
-    export NIX_GHCPKG="${env}/bin/ghc-pkg"
-    export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html"
-    export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir )
-    ${env}/bin/gitit -f ${configFile}
-  '';
-
-  gititOptions = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Enable the gitit service.";
-      };
-
-      haskellPackages = mkOption {
-        default = pkgs.haskellPackages;
-        defaultText = literalExpression "pkgs.haskellPackages";
-        example = literalExpression "pkgs.haskell.packages.ghc784";
-        description = lib.mdDoc "haskellPackages used to build gitit and plugins.";
-      };
-
-      extraPackages = mkOption {
-        type = types.functionTo (types.listOf types.package);
-        default = self: [];
-        example = literalExpression ''
-          haskellPackages: [
-            haskellPackages.wreq
-          ]
-        '';
-        description = ''
-          Extra packages available to ghc when running gitit. The
-          value must be a function which receives the attrset defined
-          in <varname>haskellPackages</varname> as the sole argument.
-        '';
-      };
-
-      address = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = lib.mdDoc "IP address on which the web server will listen.";
-      };
-
-      port = mkOption {
-        type = types.int;
-        default = 5001;
-        description = lib.mdDoc "Port on which the web server will run.";
-      };
-
-      wikiTitle = mkOption {
-        type = types.str;
-        default = "Gitit!";
-        description = lib.mdDoc "The wiki title.";
-      };
-
-      repositoryType = mkOption {
-        type = types.enum ["git" "darcs" "mercurial"];
-        default = "git";
-        description = lib.mdDoc "Specifies the type of repository used for wiki content.";
-      };
-
-      repositoryPath = mkOption {
-        type = types.path;
-        default = homeDir + "/wiki";
-        description = lib.mdDoc ''
-          Specifies the path of the repository directory. If it does not
-          exist, gitit will create it on startup.
-        '';
-      };
-
-      requireAuthentication = mkOption {
-        type = types.enum [ "none" "modify" "read" ];
-        default = "modify";
-        description = lib.mdDoc ''
-          If 'none', login is never required, and pages can be edited
-          anonymously.  If 'modify', login is required to modify the wiki
-          (edit, add, delete pages, upload files).  If 'read', login is
-          required to see any wiki pages.
-        '';
-      };
-
-      authenticationMethod = mkOption {
-        type = types.enum [ "form" "http" "generic" "github" ];
-        default = "form";
-        description = lib.mdDoc ''
-          'form' means that users will be logged in and registered using forms
-          in the gitit web interface.  'http' means that gitit will assume that
-          HTTP authentication is in place and take the logged in username from
-          the "Authorization" field of the HTTP request header (in addition,
-          the login/logout and registration links will be suppressed).
-          'generic' means that gitit will assume that some form of
-          authentication is in place that directly sets REMOTE_USER to the name
-          of the authenticated user (e.g. mod_auth_cas on apache).  'rpx' means
-          that gitit will attempt to log in through https://rpxnow.com.  This
-          requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below,
-          and that 'curl' be in the system path.
-        '';
-      };
-
-      userFile = mkOption {
-        type = types.path;
-        default = homeDir + "/gitit-users";
-        description = lib.mdDoc ''
-          Specifies the path of the file containing user login information.  If
-          it does not exist, gitit will create it (with an empty user list).
-          This file is not used if 'http' is selected for
-          authentication-method.
-        '';
-      };
-
-      sessionTimeout = mkOption {
-        type = types.int;
-        default = 60;
-        description = lib.mdDoc ''
-          Number of minutes of inactivity before a session expires.
-        '';
-      };
-
-      staticDir = mkOption {
-        type = types.path;
-        default = gititShared + "/data/static";
-        description = lib.mdDoc ''
-          Specifies the path of the static directory (containing javascript,
-          css, and images).  If it does not exist, gitit will create it and
-          populate it with required scripts, stylesheets, and images.
-        '';
-      };
-
-      defaultPageType = mkOption {
-        type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ];
-        default = "markdown";
-        description = lib.mdDoc ''
-          Specifies the type of markup used to interpret pages in the wiki.
-          Possible values are markdown, rst, latex, html, markdown+lhs,
-          rst+lhs, and latex+lhs. (the +lhs variants treat the input as
-          literate Haskell. See pandoc's documentation for more details.) If
-          Markdown is selected, pandoc's syntax extensions (for footnotes,
-          delimited code blocks, etc.) will be enabled. Note that pandoc's
-          restructuredtext parser is not complete, so some pages may not be
-          rendered correctly if rst is selected. The same goes for latex and
-          html.
-        '';
-      };
-
-      math = mkOption {
-        type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ];
-        default = "mathml";
-        description = lib.mdDoc ''
-          Specifies how LaTeX math is to be displayed.  Possible values are
-          mathml, raw, mathjax, jsmath, and google.  If mathml is selected,
-          gitit will convert LaTeX math to MathML and link in a script,
-          MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers,
-          IE + mathplayer, and Opera. In other browsers you may get a jumble of
-          characters.  If raw is selected, the LaTeX math will be displayed as
-          raw LaTeX math.  If mathjax is selected, gitit will link to the
-          remote mathjax script.  If jsMath is selected, gitit will link to the
-          script /js/jsMath/easy/load.js, and will assume that jsMath has been
-          installed into the js/jsMath directory.  This is the most portable
-          solution. If google is selected, the google chart API is called to
-          render the formula as an image. This requires a connection to google,
-          and might raise a technical or a privacy problem.
-        '';
-      };
-
-      mathJaxScript = mkOption {
-        type = types.str;
-        default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
-        description = lib.mdDoc ''
-          Specifies the path to MathJax rendering script.  You might want to
-          use your own MathJax script to render formulas without Internet
-          connection or if you want to use some special LaTeX packages.  Note:
-          path specified there cannot be an absolute path to a script on your
-          hdd, instead you should run your (local if you wish) HTTP server
-          which will serve the MathJax.js script. You can easily (in four lines
-          of code) serve MathJax.js using
-          http://happstack.com/docs/crashcourse/FileServing.html Do not forget
-          the "http://" prefix (e.g. http://localhost:1234/MathJax.js).
-        '';
-      };
-
-      showLhsBirdTracks = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Specifies whether to show Haskell code blocks in "bird style", with
-          "> " at the beginning of each line.
-        '';
-      };
-
-      templatesDir = mkOption {
-        type = types.path;
-        default = gititShared + "/data/templates";
-        description = ''
-          Specifies the path of the directory containing page templates.  If it
-          does not exist, gitit will create it with default templates.  Users
-          may wish to edit the templates to customize the appearance of their
-          wiki. The template files are HStringTemplate templates.  Variables to
-          be interpolated appear between $\'s. Literal $\'s must be
-          backslash-escaped.
-        '';
-      };
-
-      logFile = mkOption {
-        type = types.path;
-        default = homeDir + "/gitit.log";
-        description = lib.mdDoc ''
-          Specifies the path of gitit's log file.  If it does not exist, gitit
-          will create it. The log is in Apache combined log format.
-        '';
-      };
-
-      logLevel = mkOption {
-        type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ];
-        default = "ERROR";
-        description = lib.mdDoc ''
-          Determines how much information is logged.  Possible values (from
-          most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR,
-          CRITICAL, ALERT, EMERGENCY.
-        '';
-      };
-
-      frontPage = mkOption {
-        type = types.str;
-        default = "Front Page";
-        description = lib.mdDoc ''
-          Specifies which wiki page is to be used as the wiki's front page.
-          Gitit creates a default front page on startup, if one does not exist
-          already.
-        '';
-      };
-
-      noDelete = mkOption {
-        type = types.str;
-        default = "Front Page, Help";
-        description = lib.mdDoc ''
-          Specifies pages that cannot be deleted through the web interface.
-          (They can still be deleted directly using git or darcs.) A
-          comma-separated list of page names.  Leave blank to allow every page
-          to be deleted.
-        '';
-      };
-
-      noEdit = mkOption {
-        type = types.str;
-        default = "Help";
-        description = lib.mdDoc ''
-          Specifies pages that cannot be edited through the web interface.
-          Leave blank to allow every page to be edited.
-        '';
-      };
-
-      defaultSummary = mkOption {
-        type = types.str;
-        default = "";
-        description = lib.mdDoc ''
-          Specifies text to be used in the change description if the author
-          leaves the "description" field blank.  If default-summary is blank
-          (the default), the author will be required to fill in the description
-          field.
-        '';
-      };
-
-      tableOfContents = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Specifies whether to print a tables of contents (with links to
-          sections) on each wiki page.
-        '';
-      };
-
-      plugins = mkOption {
-        type = with types; listOf str;
-        default = [ (gititShared + "/plugins/Dot.hs") ];
-        description = lib.mdDoc ''
-          Specifies a list of plugins to load. Plugins may be specified either
-          by their path or by their module name. If the plugin name starts
-          with Gitit.Plugin., gitit will assume that the plugin is an installed
-          module and will not try to find a source file.
-        '';
-      };
-
-      useCache = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Specifies whether to cache rendered pages.  Note that if use-feed is
-          selected, feeds will be cached regardless of the value of use-cache.
-        '';
-      };
-
-      cacheDir = mkOption {
-        type = types.path;
-        default = homeDir + "/cache";
-        description = lib.mdDoc "Path where rendered pages will be cached.";
-      };
-
-      maxUploadSize = mkOption {
-        type = types.str;
-        default = "1000K";
-        description = lib.mdDoc ''
-          Specifies an upper limit on the size (in bytes) of files uploaded
-          through the wiki's web interface.  To disable uploads, set this to
-          0K.  This will result in the uploads link disappearing and the
-          _upload url becoming inactive.
-        '';
-      };
-
-      maxPageSize = mkOption {
-        type = types.str;
-        default = "1000K";
-        description = lib.mdDoc "Specifies an upper limit on the size (in bytes) of pages.";
-      };
-
-      debugMode = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Causes debug information to be logged while gitit is running.";
-      };
-
-      compressResponses = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc "Specifies whether HTTP responses should be compressed.";
-      };
-
-      mimeTypesFile = mkOption {
-        type = types.path;
-        default = "/etc/mime/types.info";
-        description = ''
-          Specifies the path of a file containing mime type mappings.  Each
-          line of the file should contain two fields, separated by whitespace.
-          The first field is the mime type, the second is a file extension.
-          For example:
-<programlisting>
-video/x-ms-wmx  wmx
-</programlisting>
-          If the file is not found, some simple defaults will be used.
-        '';
-      };
-
-      useReCaptcha = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          If true, causes gitit to use the reCAPTCHA service
-          (http://recaptcha.net) to prevent bots from creating accounts.
-        '';
-      };
-
-      reCaptchaPrivateKey = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc ''
-          Specifies the private key for the reCAPTCHA service.  To get
-          these, you need to create an account at http://recaptcha.net.
-        '';
-      };
-
-      reCaptchaPublicKey = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc ''
-          Specifies the public key for the reCAPTCHA service.  To get
-          these, you need to create an account at http://recaptcha.net.
-        '';
-      };
-
-      accessQuestion = mkOption {
-        type = types.str;
-        default = "What is the code given to you by Ms. X?";
-        description = lib.mdDoc ''
-          Specifies a question that users must answer when they attempt to
-          create an account
-        '';
-      };
-
-      accessQuestionAnswers = mkOption {
-        type = types.str;
-        default = "RED DOG, red dog";
-        description = lib.mdDoc ''
-          Specifies a question that users must answer when they attempt to
-          create an account, along with a comma-separated list of acceptable
-          answers.  This can be used to institute a rudimentary password for
-          signing up as a user on the wiki, or as an alternative to reCAPTCHA.
-          Example:
-          access-question:  What is the code given to you by Ms. X?
-          access-question-answers:  RED DOG, red dog
-        '';
-      };
-
-      rpxDomain = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc ''
-          Specifies the domain and key of your RPX account.  The domain is just
-          the prefix of the complete RPX domain, so if your full domain is
-          'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain.
-        '';
-      };
-
-      rpxKey = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "RPX account access key.";
-      };
-
-      mailCommand = mkOption {
-        type = types.str;
-        default = "sendmail %s";
-        description = lib.mdDoc ''
-          Specifies the command to use to send notification emails.  '%s' will
-          be replaced by the destination email address.  The body of the
-          message will be read from stdin.  If this field is left blank,
-          password reset will not be offered.
-        '';
-      };
-
-      resetPasswordMessage = mkOption {
-        type = types.lines;
-        default = ''
-          > From: gitit@$hostname$
-          > To: $useremail$
-          > Subject: Wiki password reset
-          >
-          > Hello $username$,
-          >
-          > To reset your password, please follow the link below:
-          > http://$hostname$:$port$$resetlink$
-          >
-          > Regards
-        '';
-        description = lib.mdDoc ''
-          Gives the text of the message that will be sent to the user should
-          she want to reset her password, or change other registration info.
-          The lines must be indented, and must begin with '>'.  The initial
-          spaces and '> ' will be stripped off.  $username$ will be replaced by
-          the user's username, $useremail$ by her email address, $hostname$ by
-          the hostname on which the wiki is running (as returned by the
-          hostname system call), $port$ by the port on which the wiki is
-          running, and $resetlink$ by the relative path of a reset link derived
-          from the user's existing hashed password. If your gitit wiki is being
-          proxied to a location other than the root path of $port$, you should
-          change the link to reflect this: for example, to
-          http://$hostname$/path/to/wiki$resetlink$ or
-          http://gitit.$hostname$$resetlink$
-        '';
-      };
-
-      useFeed = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Specifies whether an ATOM feed should be enabled (for the site and
-          for individual pages).
-        '';
-      };
-
-      baseUrl = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc ''
-          The base URL of the wiki, to be used in constructing feed IDs and RPX
-          token_urls.  Set this if useFeed is false or authentication-method
-          is 'rpx'.
-        '';
-      };
-
-      absoluteUrls = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Make wikilinks absolute with respect to the base-url.  So, for
-          example, in a wiki served at the base URL '/wiki', on a page
-          Sub/Page, the wikilink '[Cactus]()' will produce a link to
-          '/wiki/Cactus' if absoluteUrls is true, and a relative link to
-          'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'.
-        '';
-      };
-
-      feedDays = mkOption {
-        type = types.int;
-        default = 14;
-        description = lib.mdDoc "Number of days to be included in feeds.";
-      };
-
-      feedRefreshTime = mkOption {
-        type = types.int;
-        default = 60;
-        description = lib.mdDoc "Number of minutes to cache feeds before refreshing.";
-      };
-
-      pdfExport = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          If true, PDF will appear in export options. PDF will be created using
-          pdflatex, which must be installed and in the path. Note that PDF
-          exports create significant additional server load.
-        '';
-      };
-
-      pandocUserData = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        description = lib.mdDoc ''
-          If a directory is specified, this will be searched for pandoc
-          customizations. These can include a templates/ directory for custom
-          templates for various export formats, an S5 directory for custom S5
-          styles, and a reference.odt for ODT exports. If no directory is
-          specified, $HOME/.pandoc will be searched. See pandoc's README for
-          more information.
-        '';
-      };
-
-      xssSanitize = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          If true, all HTML (including that produced by pandoc) is filtered
-          through xss-sanitize.  Set to no only if you trust all of your users.
-        '';
-      };
-
-      oauthClientId = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "OAuth client ID";
-      };
-
-      oauthClientSecret = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "OAuth client secret";
-      };
-
-      oauthCallback = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "OAuth callback URL";
-      };
-
-      oauthAuthorizeEndpoint = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "OAuth authorize endpoint";
-      };
-
-      oauthAccessTokenEndpoint = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "OAuth access token endpoint";
-      };
-
-      githubOrg = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = lib.mdDoc "Github organization";
-      };
-  };
-
-  configFile = pkgs.writeText "gitit.conf" ''
-    address: ${cfg.address}
-    port: ${toString cfg.port}
-    wiki-title: ${cfg.wikiTitle}
-    repository-type: ${cfg.repositoryType}
-    repository-path: ${cfg.repositoryPath}
-    require-authentication: ${cfg.requireAuthentication}
-    authentication-method: ${cfg.authenticationMethod}
-    user-file: ${cfg.userFile}
-    session-timeout: ${toString cfg.sessionTimeout}
-    static-dir: ${cfg.staticDir}
-    default-page-type: ${cfg.defaultPageType}
-    math: ${cfg.math}
-    mathjax-script: ${cfg.mathJaxScript}
-    show-lhs-bird-tracks: ${toYesNo cfg.showLhsBirdTracks}
-    templates-dir: ${cfg.templatesDir}
-    log-file: ${cfg.logFile}
-    log-level: ${cfg.logLevel}
-    front-page: ${cfg.frontPage}
-    no-delete: ${cfg.noDelete}
-    no-edit: ${cfg.noEdit}
-    default-summary: ${cfg.defaultSummary}
-    table-of-contents: ${toYesNo cfg.tableOfContents}
-    plugins: ${concatStringsSep "," cfg.plugins}
-    use-cache: ${toYesNo cfg.useCache}
-    cache-dir: ${cfg.cacheDir}
-    max-upload-size: ${cfg.maxUploadSize}
-    max-page-size: ${cfg.maxPageSize}
-    debug-mode: ${toYesNo cfg.debugMode}
-    compress-responses: ${toYesNo cfg.compressResponses}
-    mime-types-file: ${cfg.mimeTypesFile}
-    use-recaptcha: ${toYesNo cfg.useReCaptcha}
-    recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey}
-    recaptcha-public-key: ${toString cfg.reCaptchaPublicKey}
-    access-question: ${cfg.accessQuestion}
-    access-question-answers: ${cfg.accessQuestionAnswers}
-    rpx-domain: ${toString cfg.rpxDomain}
-    rpx-key: ${toString cfg.rpxKey}
-    mail-command: ${cfg.mailCommand}
-    reset-password-message: ${cfg.resetPasswordMessage}
-    use-feed: ${toYesNo cfg.useFeed}
-    base-url: ${toString cfg.baseUrl}
-    absolute-urls: ${toYesNo cfg.absoluteUrls}
-    feed-days: ${toString cfg.feedDays}
-    feed-refresh-time: ${toString cfg.feedRefreshTime}
-    pdf-export: ${toYesNo cfg.pdfExport}
-    pandoc-user-data: ${toString cfg.pandocUserData}
-    xss-sanitize: ${toYesNo cfg.xssSanitize}
-
-    [Github]
-    oauthclientid: ${toString cfg.oauthClientId}
-    oauthclientsecret: ${toString cfg.oauthClientSecret}
-    oauthcallback: ${toString cfg.oauthCallback}
-    oauthauthorizeendpoint: ${toString cfg.oauthAuthorizeEndpoint}
-    oauthaccesstokenendpoint: ${toString cfg.oauthAccessTokenEndpoint}
-    github-org: ${toString cfg.githubOrg}
-  '';
-
-in
-
-{
-
-  options.services.gitit = gititOptions;
-
-  config = mkIf cfg.enable {
-
-    users.users.gitit = {
-      group = config.users.groups.gitit.name;
-      description = "Gitit user";
-      home = homeDir;
-      createHome = true;
-      uid = config.ids.uids.gitit;
-    };
-
-    users.groups.gitit.gid = config.ids.gids.gitit;
-
-    systemd.services.gitit = let
-      uid = toString config.ids.uids.gitit;
-      gid = toString config.ids.gids.gitit;
-    in {
-      description = "Git and Pandoc Powered Wiki";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [ curl ]
-             ++ optional cfg.pdfExport texlive.combined.scheme-basic
-             ++ optional (cfg.repositoryType == "darcs") darcs
-             ++ optional (cfg.repositoryType == "mercurial") mercurial
-             ++ optional (cfg.repositoryType == "git") git;
-
-      preStart = let
-        gm = "gitit@${config.networking.hostName}";
-      in
-      with cfg; ''
-        chown ${uid}:${gid} -R ${homeDir}
-        for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir}
-        do
-          if [ ! -d $dir ]
-          then
-            mkdir -p $dir
-            find $dir -type d -exec chmod 0750 {} +
-            find $dir -type f -exec chmod 0640 {} +
-          fi
-        done
-        cd ${repositoryPath}
-        ${
-          if repositoryType == "darcs" then
-          ''
-          if [ ! -d _darcs ]
-          then
-            darcs initialize
-            echo "${gm}" > _darcs/prefs/email
-          ''
-          else if repositoryType == "mercurial" then
-          ''
-          if [ ! -d .hg ]
-          then
-            hg init
-            cat >> .hg/hgrc <<NAMED
-[ui]
-username = gitit ${gm}
-NAMED
-          ''
-          else
-          ''
-          if [ ! -d  .git ]
-          then
-            git init
-            git config user.email "${gm}"
-            git config user.name "gitit"
-          ''}
-          chown ${uid}:${gid} -R ${repositoryPath}
-          fi
-        cd -
-      '';
-
-      serviceConfig = {
-        User = config.users.users.gitit.name;
-        Group = config.users.groups.gitit.name;
-        ExecStart = with cfg; gititSh haskellPackages extraPackages;
-      };
-    };
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.md b/nixpkgs/nixos/modules/services/misc/gitlab.md
new file mode 100644
index 000000000000..916b23584ed0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.md
@@ -0,0 +1,112 @@
+# GitLab {#module-services-gitlab}
+
+GitLab is a feature-rich git hosting service.
+
+## Prerequisites {#module-services-gitlab-prerequisites}
+
+The `gitlab` service exposes only an Unix socket at
+`/run/gitlab/gitlab-workhorse.socket`. You need to
+configure a webserver to proxy HTTP requests to the socket.
+
+For instance, the following configuration could be used to use nginx as
+frontend proxy:
+```
+services.nginx = {
+  enable = true;
+  recommendedGzipSettings = true;
+  recommendedOptimisation = true;
+  recommendedProxySettings = true;
+  recommendedTlsSettings = true;
+  virtualHosts."git.example.com" = {
+    enableACME = true;
+    forceSSL = true;
+    locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
+  };
+};
+```
+
+## Configuring {#module-services-gitlab-configuring}
+
+GitLab depends on both PostgreSQL and Redis and will automatically enable
+both services. In the case of PostgreSQL, a database and a role will be
+created.
+
+The default state dir is `/var/gitlab/state`. This is where
+all data like the repositories and uploads will be stored.
+
+A basic configuration with some custom settings could look like this:
+```
+services.gitlab = {
+  enable = true;
+  databasePasswordFile = "/var/keys/gitlab/db_password";
+  initialRootPasswordFile = "/var/keys/gitlab/root_password";
+  https = true;
+  host = "git.example.com";
+  port = 443;
+  user = "git";
+  group = "git";
+  smtp = {
+    enable = true;
+    address = "localhost";
+    port = 25;
+  };
+  secrets = {
+    dbFile = "/var/keys/gitlab/db";
+    secretFile = "/var/keys/gitlab/secret";
+    otpFile = "/var/keys/gitlab/otp";
+    jwsFile = "/var/keys/gitlab/jws";
+  };
+  extraConfig = {
+    gitlab = {
+      email_from = "gitlab-no-reply@example.com";
+      email_display_name = "Example GitLab";
+      email_reply_to = "gitlab-no-reply@example.com";
+      default_projects_features = { builds = false; };
+    };
+  };
+};
+```
+
+If you're setting up a new GitLab instance, generate new
+secrets. You for instance use
+`tr -dc A-Za-z0-9 < /dev/urandom | head -c 128 > /var/keys/gitlab/db` to
+generate a new db secret. Make sure the files can be read by, and
+only by, the user specified by
+[services.gitlab.user](#opt-services.gitlab.user). GitLab
+encrypts sensitive data stored in the database. If you're restoring
+an existing GitLab instance, you must specify the secrets secret
+from `config/secrets.yml` located in your GitLab
+state folder.
+
+When `incoming_mail.enabled` is set to `true`
+in [extraConfig](#opt-services.gitlab.extraConfig) an additional
+service called `gitlab-mailroom` is enabled for fetching incoming mail.
+
+Refer to [](#ch-options) for all available configuration
+options for the [services.gitlab](#opt-services.gitlab.enable) module.
+
+## Maintenance {#module-services-gitlab-maintenance}
+
+### Backups {#module-services-gitlab-maintenance-backups}
+
+Backups can be configured with the options in
+[services.gitlab.backup](#opt-services.gitlab.backup.keepTime). Use
+the [services.gitlab.backup.startAt](#opt-services.gitlab.backup.startAt)
+option to configure regular backups.
+
+To run a manual backup, start the `gitlab-backup` service:
+```ShellSession
+$ systemctl start gitlab-backup.service
+```
+
+### Rake tasks {#module-services-gitlab-maintenance-rake}
+
+You can run GitLab's rake tasks with `gitlab-rake`
+which will be available on the system when GitLab is enabled. You
+will have to run the command as the user that you configured to run
+GitLab with.
+
+A list of all available rake tasks can be obtained by running:
+```ShellSession
+$ sudo -u git -H gitlab-rake -T
+```
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.nix b/nixpkgs/nixos/modules/services/misc/gitlab.nix
index 0bf2bca4a0b8..9c18a2eed1c6 100644
--- a/nixpkgs/nixos/modules/services/misc/gitlab.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.nix
@@ -6,28 +6,13 @@ let
   cfg = config.services.gitlab;
   opt = options.services.gitlab;
 
-  ruby = cfg.packages.gitlab.ruby;
+  toml = pkgs.formats.toml {};
+  yaml = pkgs.formats.yaml {};
 
   postgresqlPackage = if config.services.postgresql.enable then
                         config.services.postgresql.package
                       else
-                        pkgs.postgresql_12;
-
-  # Git 2.36.1 seemingly contains a commit-graph related bug which is
-  # easily triggered through GitLab, so we downgrade it to 2.35.x
-  # until this issue is solved. See
-  # https://gitlab.com/gitlab-org/gitlab/-/issues/360783#note_992870101.
-  gitPackage =
-    let
-      version = "2.35.4";
-    in
-      pkgs.git.overrideAttrs (oldAttrs: rec {
-        inherit version;
-        src = pkgs.fetchurl {
-          url = "https://www.kernel.org/pub/software/scm/git/git-${version}.tar.xz";
-          sha256 = "sha256-mv13OdNkXggeKQkJ+47QcJ6lYmcw6Qjri1ZJ2ETCTOk=";
-        };
-      });
+                        pkgs.postgresql_13;
 
   gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
   gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
@@ -53,14 +38,12 @@ let
 
   gitalyToml = pkgs.writeText "gitaly.toml" ''
     socket_path = "${lib.escape ["\""] gitalySocket}"
+    runtime_dir = "/run/gitaly"
     bin_dir = "${cfg.packages.gitaly}/bin"
     prometheus_listen_addr = "localhost:9236"
 
     [git]
-    bin_path = "${gitPackage}/bin/git"
-
-    [gitaly-ruby]
-    dir = "${cfg.packages.gitaly.ruby}"
+    bin_path = "${pkgs.git}/bin/git"
 
     [gitlab-shell]
     dir = "${cfg.packages.gitlab-shell}"
@@ -89,21 +72,20 @@ let
     repos_path = "${cfg.statePath}/repositories";
     secret_file = "${cfg.statePath}/gitlab_shell_secret";
     log_file = "${cfg.statePath}/log/gitlab-shell.log";
-    redis = {
-      bin = "${pkgs.redis}/bin/redis-cli";
-      host = "127.0.0.1";
-      port = config.services.redis.servers.gitlab.port;
-      database = 0;
-      namespace = "resque:gitlab";
-    };
   };
 
   redisConfig.production.url = cfg.redisUrl;
 
-  pagesArgs = [
-    "-pages-domain" gitlabConfig.production.pages.host
-    "-pages-root" "${gitlabConfig.production.shared.path}/pages"
-  ] ++ cfg.pagesExtraArgs;
+  cableYml = yaml.generate "cable.yml" {
+    production = {
+      adapter = "redis";
+      url = cfg.redisUrl;
+      channel_prefix = "gitlab_production";
+    };
+  };
+
+  # Redis configuration file
+  resqueYml = pkgs.writeText "resque.yml" (builtins.toJSON redisConfig);
 
   gitlabConfig = {
     # These are the default settings from config/gitlab.example.yml
@@ -153,7 +135,7 @@ let
       };
       workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
       gitlab_kas.secret_file = "${cfg.statePath}/.gitlab_kas_secret";
-      git.bin_path = "${gitPackage}/bin/git";
+      git.bin_path = "git";
       monitoring = {
         ip_whitelist = [ "127.0.0.0/8" "::1/128" ];
         sidekiq_exporter = {
@@ -168,10 +150,16 @@ let
         port = cfg.registry.externalPort;
         key = cfg.registry.keyFile;
         api_url = "http://${config.services.dockerRegistry.listenAddress}:${toString config.services.dockerRegistry.port}/";
-        issuer = "gitlab-issuer";
+        issuer = cfg.registry.issuer;
       };
       extra = {};
       uploads.storage_path = cfg.statePath;
+      pages = optionalAttrs cfg.pages.enable {
+        enabled = cfg.pages.enable;
+        port = 8090;
+        host = cfg.pages.settings.pages-domain;
+        secret_file = cfg.pages.settings.api-secret-key;
+      };
     };
   };
 
@@ -182,22 +170,32 @@ let
     SCHEMA = "${cfg.statePath}/db/structure.sql";
     GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     GITLAB_LOG_PATH = "${cfg.statePath}/log";
-    GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
     prometheus_multiproc_dir = "/run/gitlab";
     RAILS_ENV = "production";
     MALLOC_ARENA_MAX = "2";
   } // cfg.extraEnv;
 
+  runtimeDeps = with pkgs; [
+    nodejs
+    gzip
+    git
+    gnutar
+    postgresqlPackage
+    coreutils
+    procps
+    findutils # Needed for gitlab:cleanup:orphan_job_artifact_files
+  ];
+
   gitlab-rake = pkgs.stdenv.mkDerivation {
     name = "gitlab-rake";
-    buildInputs = [ pkgs.makeWrapper ];
+    nativeBuildInputs = [ pkgs.makeWrapper ];
     dontBuild = true;
     dontUnpack = true;
     installPhase = ''
       mkdir -p $out/bin
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
           ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
-          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
+          --set PATH '${lib.makeBinPath runtimeDeps}:$PATH' \
           --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
           --chdir '${cfg.packages.gitlab}/share/gitlab'
      '';
@@ -205,14 +203,14 @@ let
 
   gitlab-rails = pkgs.stdenv.mkDerivation {
     name = "gitlab-rails";
-    buildInputs = [ pkgs.makeWrapper ];
+    nativeBuildInputs = [ pkgs.makeWrapper ];
     dontBuild = true;
     dontUnpack = true;
     installPhase = ''
       mkdir -p $out/bin
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
           ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
-          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
+          --set PATH '${lib.makeBinPath runtimeDeps}:$PATH' \
           --chdir '${cfg.packages.gitlab}/share/gitlab'
      '';
   };
@@ -245,6 +243,8 @@ in {
     (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
     (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ])
     (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
+    (mkRemovedOptionModule [ "services" "gitlab" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.gitlab directly instead")
+    (mkRemovedOptionModule [ "services" "gitlab" "pagesExtraArgs" ] "Use services.gitlab.pages.settings instead")
   ];
 
   options = {
@@ -338,10 +338,9 @@ in {
         default = 0;
         example = 48;
         apply = x: x * 60 * 60;
-        description = ''
+        description = lib.mdDoc ''
           How long to keep the backups around, in
-          hours. <literal>0</literal> means <quote>keep
-          forever</quote>.
+          hours. `0` means “keep forever”.
         '';
       };
 
@@ -415,9 +414,9 @@ in {
       databaseHost = mkOption {
         type = types.str;
         default = "";
-        description = ''
-          GitLab database hostname. An empty string means <quote>use
-          local unix socket connection</quote>.
+        description = lib.mdDoc ''
+          GitLab database hostname. An empty string means
+          “use local unix socket connection”.
         '';
       };
 
@@ -469,9 +468,9 @@ in {
 
       redisUrl = mkOption {
         type = types.str;
-        default = "redis://localhost:${toString config.services.redis.servers.gitlab.port}/";
-        defaultText = literalExpression ''redis://localhost:''${toString config.services.redis.servers.gitlab.port}/'';
-        description = lib.mdDoc "Redis URL for all GitLab services except gitlab-shell";
+        default = "unix:/run/gitlab/redis.sock";
+        example = "redis://localhost:6379/";
+        description = lib.mdDoc "Redis URL for all GitLab services.";
       };
 
       extraGitlabRb = mkOption {
@@ -554,6 +553,20 @@ in {
           default = false;
           description = lib.mdDoc "Enable GitLab container registry.";
         };
+        package = mkOption {
+          type = types.package;
+          default =
+            if versionAtLeast config.system.stateVersion "23.11"
+            then pkgs.gitlab-container-registry
+            else pkgs.docker-distribution;
+          defaultText = literalExpression "pkgs.docker-distribution";
+          description = lib.mdDoc ''
+            Container registry package to use.
+
+            External container registries such as `pkgs.docker-distribution` are not supported
+            anymore since GitLab 16.0.0.
+          '';
+        };
         host = mkOption {
           type = types.str;
           default = config.services.gitlab.host;
@@ -561,7 +574,7 @@ in {
           description = lib.mdDoc "GitLab container registry host name.";
         };
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 4567;
           description = lib.mdDoc "GitLab container registry port.";
         };
@@ -614,7 +627,7 @@ in {
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 25;
           description = lib.mdDoc "Port of the SMTP server for GitLab.";
         };
@@ -667,10 +680,127 @@ in {
         };
       };
 
-      pagesExtraArgs = mkOption {
-        type = types.listOf types.str;
-        default = [ "-listen-proxy" "127.0.0.1:8090" ];
-        description = lib.mdDoc "Arguments to pass to the gitlab-pages daemon";
+      pages.enable = mkEnableOption (lib.mdDoc "the GitLab Pages service");
+
+      pages.settings = mkOption {
+        example = literalExpression ''
+          {
+            pages-domain = "example.com";
+            auth-client-id = "generated-id-xxxxxxx";
+            auth-client-secret = { _secret = "/var/keys/auth-client-secret"; };
+            auth-redirect-uri = "https://projects.example.com/auth";
+            auth-secret = { _secret = "/var/keys/auth-secret"; };
+            auth-server = "https://gitlab.example.com";
+          }
+        '';
+
+        description = lib.mdDoc ''
+          Configuration options to set in the GitLab Pages config
+          file.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute `_secret` - 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 configuration file, the `auth-client-secret` and
+          `auth-secret` keys will be set to the contents of the
+          {file}`/var/keys/auth-client-secret` and
+          {file}`/var/keys/auth-secret` files respectively.
+        '';
+
+        type = types.submodule {
+          freeformType = with types; attrsOf (nullOr (oneOf [ str int bool attrs ]));
+
+          options = {
+            listen-http = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [];
+              description = lib.mdDoc ''
+                The address(es) to listen on for HTTP requests.
+              '';
+            };
+
+            listen-https = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [];
+              description = lib.mdDoc ''
+                The address(es) to listen on for HTTPS requests.
+              '';
+            };
+
+            listen-proxy = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [ "127.0.0.1:8090" ];
+              description = lib.mdDoc ''
+                The address(es) to listen on for proxy requests.
+              '';
+            };
+
+            artifacts-server = mkOption {
+              type = with types; nullOr str;
+              default = "http${optionalString cfg.https "s"}://${cfg.host}/api/v4";
+              defaultText = "http(s)://<services.gitlab.host>/api/v4";
+              example = "https://gitlab.example.com/api/v4";
+              description = lib.mdDoc ''
+                API URL to proxy artifact requests to.
+              '';
+            };
+
+            gitlab-server = mkOption {
+              type = with types; nullOr str;
+              default = "http${optionalString cfg.https "s"}://${cfg.host}";
+              defaultText = "http(s)://<services.gitlab.host>";
+              example = "https://gitlab.example.com";
+              description = lib.mdDoc ''
+                Public GitLab server URL.
+              '';
+            };
+
+            internal-gitlab-server = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              defaultText = "http(s)://<services.gitlab.host>";
+              example = "https://gitlab.example.internal";
+              description = lib.mdDoc ''
+                Internal GitLab server used for API requests, useful
+                if you want to send that traffic over an internal load
+                balancer. By default, the value of
+                `services.gitlab.pages.settings.gitlab-server` is
+                used.
+              '';
+            };
+
+            api-secret-key = mkOption {
+              type = with types; nullOr str;
+              default = "${cfg.statePath}/gitlab_pages_secret";
+              internal = true;
+              description = lib.mdDoc ''
+                File with secret key used to authenticate with the
+                GitLab API.
+              '';
+            };
+
+            pages-domain = mkOption {
+              type = with types; nullOr str;
+              example = "example.com";
+              description = lib.mdDoc ''
+                The domain to serve static pages on.
+              '';
+            };
+
+            pages-root = mkOption {
+              type = types.str;
+              default = "${gitlabConfig.production.shared.path}/pages";
+              defaultText = literalExpression ''config.${opt.extraConfig}.production.shared.path + "/pages"'';
+              description = lib.mdDoc ''
+                The directory where pages are stored.
+              '';
+            };
+          };
+        };
       };
 
       secrets.secretFile = mkOption {
@@ -749,18 +879,15 @@ in {
         type = types.int;
         default = 2;
         apply = x: builtins.toString x;
-        description = ''
+        description = lib.mdDoc ''
           The number of worker processes Puma should spawn. This
           controls the amount of parallel Ruby code can be
-          executed. GitLab recommends <quote>Number of CPU cores -
-          1</quote>, but at least two.
-
-          <note>
-            <para>
-              Each worker consumes quite a bit of memory, so
-              be careful when increasing this.
-            </para>
-          </note>
+          executed. GitLab recommends `Number of CPU cores - 1`, but at least two.
+
+          ::: {.note}
+          Each worker consumes quite a bit of memory, so
+          be careful when increasing this.
+          :::
         '';
       };
 
@@ -768,16 +895,14 @@ in {
         type = types.int;
         default = 0;
         apply = x: builtins.toString x;
-        description = ''
+        description = lib.mdDoc ''
           The minimum number of threads Puma should use per
           worker.
 
-          <note>
-            <para>
-              Each thread consumes memory and contributes to Global VM
-              Lock contention, so be careful when increasing this.
-            </para>
-          </note>
+          ::: {.note}
+          Each thread consumes memory and contributes to Global VM
+          Lock contention, so be careful when increasing this.
+          :::
         '';
       };
 
@@ -785,19 +910,17 @@ in {
         type = types.int;
         default = 4;
         apply = x: builtins.toString x;
-        description = ''
+        description = lib.mdDoc ''
           The maximum number of threads Puma should use per
           worker. This limits how many threads Puma will automatically
           spawn in response to requests. In contrast to workers,
           threads will never be able to run Ruby code in parallel, but
           give higher IO parallelism.
 
-          <note>
-            <para>
-              Each thread consumes memory and contributes to Global VM
-              Lock contention, so be careful when increasing this.
-            </para>
-          </note>
+          ::: {.note}
+          Each thread consumes memory and contributes to Global VM
+          Lock contention, so be careful when increasing this.
+          :::
         '';
       };
 
@@ -864,19 +987,43 @@ in {
           default = 30;
           description = lib.mdDoc "How many rotations to keep.";
         };
+      };
 
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
-          description = lib.mdDoc ''
-            Extra logrotate config options for this path. Refer to
-            <https://linux.die.net/man/8/logrotate> for details.
-          '';
-        };
+      workhorse.config = mkOption {
+        type = toml.type;
+        default = {};
+        example = literalExpression ''
+          {
+            object_storage.provider = "AWS";
+            object_storage.s3 = {
+              aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX";
+              aws_secret_access_key = { _secret = "/var/keys/aws_secret_access_key"; };
+            };
+          };
+        '';
+        description = lib.mdDoc ''
+          Configuration options to add to Workhorse's configuration
+          file.
+
+          See
+          <https://gitlab.com/gitlab-org/gitlab/-/blob/master/workhorse/config.toml.example>
+          and
+          <https://docs.gitlab.com/ee/development/workhorse/configuration.html>
+          for examples and option documentation.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute `_secret` - 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 configuration file, the
+          `object_storage.s3.aws_secret_access_key` key will be set to
+          the contents of the {file}`/var/keys/aws_secret_access_key`
+          file.
+        '';
       };
 
       extraConfig = mkOption {
-        type = types.attrs;
+        type = yaml.type;
         default = {};
         example = literalExpression ''
           {
@@ -934,6 +1081,13 @@ in {
   };
 
   config = mkIf cfg.enable {
+    warnings = [
+      (mkIf
+        (cfg.registry.enable && versionAtLeast (getVersion cfg.packages.gitlab) "16.0.0" && cfg.registry.package == pkgs.docker-distribution)
+        ''Support for container registries other than gitlab-container-registry has ended since GitLab 16.0.0 and is scheduled for removal in a future release.
+          Please back up your data and migrate to the gitlab-container-registry package.''
+      )
+    ];
 
     assertions = [
       {
@@ -965,8 +1119,8 @@ in {
         message = "services.gitlab.secrets.jwsFile must be set!";
       }
       {
-        assertion = versionAtLeast postgresqlPackage.version "12.0.0";
-        message = "PostgreSQL >=12 is required to run GitLab 14. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading";
+        assertion = versionAtLeast postgresqlPackage.version "13.6.0";
+        message = "PostgreSQL >=13.6 is required to run GitLab 16. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading";
       }
     ];
 
@@ -980,8 +1134,9 @@ in {
     # Redis is required for the sidekiq queue runner.
     services.redis.servers.gitlab = {
       enable = mkDefault true;
-      port = mkDefault 31636;
-      bind = mkDefault "127.0.0.1";
+      user = mkDefault cfg.user;
+      unixSocket = mkDefault "/run/gitlab/redis.sock";
+      unixSocketPerm = mkDefault 770;
     };
 
     # We use postgres as the main data store.
@@ -1001,7 +1156,6 @@ in {
           rotate = cfg.logrotate.keep;
           copytruncate = true;
           compress = true;
-          extraConfig = cfg.logrotate.extraConfig;
         };
       };
     };
@@ -1070,15 +1224,17 @@ in {
     # Ensure Docker Registry launches after the certificate generation job
     systemd.services.docker-registry = optionalAttrs cfg.registry.enable {
       wants = [ "gitlab-registry-cert.service" ];
+      after = [ "gitlab-registry-cert.service" ];
     };
 
     # Enable Docker Registry, if GitLab-Container Registry is enabled
     services.dockerRegistry = optionalAttrs cfg.registry.enable {
       enable = true;
       enableDelete = true; # This must be true, otherwise GitLab won't manage it correctly
+      package = cfg.registry.package;
       extraConfig = {
         auth.token = {
-          realm = "http${if cfg.https == true then "s" else ""}://${cfg.host}/jwt/auth";
+          realm = "http${optionalString (cfg.https == true) "s"}://${cfg.host}/jwt/auth";
           service = cfg.registry.serviceName;
           issuer = cfg.registry.issuer;
           rootcertbundle = cfg.registry.certFile;
@@ -1123,6 +1279,7 @@ in {
       "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/packages 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path}/registry 0750 ${cfg.user} ${cfg.group} -"
       "d ${gitlabConfig.production.shared.path}/terraform_state 0750 ${cfg.user} ${cfg.group} -"
       "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
       "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
@@ -1176,6 +1333,8 @@ in {
           cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
           cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
           ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
+          ln -sf ${cableYml} ${cfg.statePath}/config/cable.yml
+          ln -sf ${resqueYml} ${cfg.statePath}/config/resque.yml
 
           ${cfg.packages.gitlab-shell}/bin/install
 
@@ -1190,6 +1349,9 @@ in {
             umask u=rwx,g=,o=
 
             openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+            ${optionalString cfg.pages.enable ''
+                openssl rand -base64 32 > ${cfg.pages.settings.api-secret-key}
+            ''}
 
             rm -f '${cfg.statePath}/config/database.yml'
 
@@ -1290,7 +1452,7 @@ in {
       });
       path = with pkgs; [
         postgresqlPackage
-        gitPackage
+        git
         ruby
         openssh
         nodejs
@@ -1320,10 +1482,7 @@ in {
       partOf = [ "gitlab.target" ];
       path = with pkgs; [
         openssh
-        procps  # See https://gitlab.com/gitlab-org/gitaly/issues/1562
-        gitPackage
-        cfg.packages.gitaly.rubyEnv
-        cfg.packages.gitaly.rubyEnv.wrappedRuby
+        git
         gzip
         bzip2
       ];
@@ -1334,39 +1493,79 @@ in {
         TimeoutSec = "infinity";
         Restart = "on-failure";
         WorkingDirectory = gitlabEnv.HOME;
+        RuntimeDirectory = "gitaly";
         ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}";
       };
     };
 
-    systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) {
-      description = "GitLab static pages daemon";
-      after = [ "network.target" "gitlab-config.service" ];
-      bindsTo = [ "gitlab-config.service" ];
-      wantedBy = [ "gitlab.target" ];
-      partOf = [ "gitlab.target" ];
-
-      path = [ pkgs.unzip ];
-
-      serviceConfig = {
-        Type = "simple";
-        TimeoutSec = "infinity";
-        Restart = "on-failure";
-
-        User = cfg.user;
-        Group = cfg.group;
-
-        ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}";
-        WorkingDirectory = gitlabEnv.HOME;
-      };
+    services.gitlab.pages.settings = {
+      api-secret-key = "${cfg.statePath}/gitlab_pages_secret";
     };
 
+    systemd.services.gitlab-pages =
+      let
+        filteredConfig = filterAttrs (_: v: v != null) cfg.pages.settings;
+        isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+        mkPagesKeyValue = lib.generators.toKeyValue {
+          mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" rec {
+            mkValueString = v:
+              if isInt           v then toString v
+              else if isString   v then v
+              else if true  ==   v then "true"
+              else if false ==   v then "false"
+              else if isSecret   v then builtins.hashString "sha256" v._secret
+              else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
+          };
+        };
+        secretPaths = lib.catAttrs "_secret" (lib.collect isSecret filteredConfig);
+        mkSecretReplacement = file: ''
+          replace-secret ${lib.escapeShellArgs [ (builtins.hashString "sha256" file) file "/run/gitlab-pages/gitlab-pages.conf" ]}
+        '';
+        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+        configFile = pkgs.writeText "gitlab-pages.conf" (mkPagesKeyValue filteredConfig);
+      in
+        mkIf cfg.pages.enable {
+          description = "GitLab static pages daemon";
+          after = [ "network.target" "gitlab-config.service" "gitlab.service" ];
+          bindsTo = [ "gitlab-config.service" "gitlab.service" ];
+          wantedBy = [ "gitlab.target" ];
+          partOf = [ "gitlab.target" ];
+
+          path = with pkgs; [
+            unzip
+            replace-secret
+          ];
+
+          serviceConfig = {
+            Type = "simple";
+            TimeoutSec = "infinity";
+            Restart = "on-failure";
+
+            User = cfg.user;
+            Group = cfg.group;
+
+            ExecStartPre = pkgs.writeShellScript "gitlab-pages-pre-start" ''
+              set -o errexit -o pipefail -o nounset
+              shopt -s dotglob nullglob inherit_errexit
+
+              install -m u=rw ${configFile} /run/gitlab-pages/gitlab-pages.conf
+              ${secretReplacements}
+            '';
+            ExecStart = "${cfg.packages.pages}/bin/gitlab-pages -config=/run/gitlab-pages/gitlab-pages.conf";
+            WorkingDirectory = gitlabEnv.HOME;
+            RuntimeDirectory = "gitlab-pages";
+            RuntimeDirectoryMode = "0700";
+          };
+        };
+
     systemd.services.gitlab-workhorse = {
       after = [ "network.target" ];
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
       path = with pkgs; [
+        remarshal
         exiftool
-        gitPackage
+        git
         gnutar
         gzip
         openssh
@@ -1379,6 +1578,17 @@ in {
         TimeoutSec = "infinity";
         Restart = "on-failure";
         WorkingDirectory = gitlabEnv.HOME;
+        ExecStartPre = pkgs.writeShellScript "gitlab-workhorse-pre-start" ''
+          set -o errexit -o pipefail -o nounset
+          shopt -s dotglob nullglob inherit_errexit
+
+          ${utils.genJqSecretsReplacementSnippet
+              cfg.workhorse.config
+              "${cfg.statePath}/config/gitlab-workhorse.json"}
+
+          json2toml "${cfg.statePath}/config/gitlab-workhorse.json" "${cfg.statePath}/config/gitlab-workhorse.toml"
+          rm "${cfg.statePath}/config/gitlab-workhorse.json"
+        '';
         ExecStart =
           "${cfg.packages.gitlab-workhorse}/bin/workhorse "
           + "-listenUmask 0 "
@@ -1386,6 +1596,7 @@ in {
           + "-listenAddr /run/gitlab/gitlab-workhorse.socket "
           + "-authSocket ${gitlabSocket} "
           + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public "
+          + "-config ${cfg.statePath}/config/gitlab-workhorse.toml "
           + "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret";
       };
     };
@@ -1427,7 +1638,7 @@ in {
       environment = gitlabEnv;
       path = with pkgs; [
         postgresqlPackage
-        gitPackage
+        git
         openssh
         nodejs
         procps
@@ -1470,6 +1681,6 @@ in {
 
   };
 
-  meta.doc = ./gitlab.xml;
+  meta.doc = ./gitlab.md;
 
 }
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.xml b/nixpkgs/nixos/modules/services/misc/gitlab.xml
deleted file mode 100644
index 40424c5039a2..000000000000
--- a/nixpkgs/nixos/modules/services/misc/gitlab.xml
+++ /dev/null
@@ -1,151 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-gitlab">
- <title>GitLab</title>
- <para>
-  GitLab is a feature-rich git hosting service.
- </para>
- <section xml:id="module-services-gitlab-prerequisites">
-  <title>Prerequisites</title>
-
-  <para>
-   The <literal>gitlab</literal> service exposes only an Unix socket at
-   <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to
-   configure a webserver to proxy HTTP requests to the socket.
-  </para>
-
-  <para>
-   For instance, the following configuration could be used to use nginx as
-   frontend proxy:
-<programlisting>
-<link linkend="opt-services.nginx.enable">services.nginx</link> = {
-  <link linkend="opt-services.nginx.enable">enable</link> = true;
-  <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
-  <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
-  <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
-  <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
-  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link>."git.example.com" = {
-    <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
-    <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-    <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/".proxyPass</link> = "http://unix:/run/gitlab/gitlab-workhorse.socket";
-  };
-};
-</programlisting>
-  </para>
- </section>
- <section xml:id="module-services-gitlab-configuring">
-  <title>Configuring</title>
-
-  <para>
-   GitLab depends on both PostgreSQL and Redis and will automatically enable
-   both services. In the case of PostgreSQL, a database and a role will be
-   created.
-  </para>
-
-  <para>
-   The default state dir is <literal>/var/gitlab/state</literal>. This is where
-   all data like the repositories and uploads will be stored.
-  </para>
-
-  <para>
-   A basic configuration with some custom settings could look like this:
-<programlisting>
-services.gitlab = {
-  <link linkend="opt-services.gitlab.enable">enable</link> = true;
-  <link linkend="opt-services.gitlab.databasePasswordFile">databasePasswordFile</link> = "/var/keys/gitlab/db_password";
-  <link linkend="opt-services.gitlab.initialRootPasswordFile">initialRootPasswordFile</link> = "/var/keys/gitlab/root_password";
-  <link linkend="opt-services.gitlab.https">https</link> = true;
-  <link linkend="opt-services.gitlab.host">host</link> = "git.example.com";
-  <link linkend="opt-services.gitlab.port">port</link> = 443;
-  <link linkend="opt-services.gitlab.user">user</link> = "git";
-  <link linkend="opt-services.gitlab.group">group</link> = "git";
-  smtp = {
-    <link linkend="opt-services.gitlab.smtp.enable">enable</link> = true;
-    <link linkend="opt-services.gitlab.smtp.address">address</link> = "localhost";
-    <link linkend="opt-services.gitlab.smtp.port">port</link> = 25;
-  };
-  secrets = {
-    <link linkend="opt-services.gitlab.secrets.dbFile">dbFile</link> = "/var/keys/gitlab/db";
-    <link linkend="opt-services.gitlab.secrets.secretFile">secretFile</link> = "/var/keys/gitlab/secret";
-    <link linkend="opt-services.gitlab.secrets.otpFile">otpFile</link> = "/var/keys/gitlab/otp";
-    <link linkend="opt-services.gitlab.secrets.jwsFile">jwsFile</link> = "/var/keys/gitlab/jws";
-  };
-  <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> = {
-    gitlab = {
-      email_from = "gitlab-no-reply@example.com";
-      email_display_name = "Example GitLab";
-      email_reply_to = "gitlab-no-reply@example.com";
-      default_projects_features = { builds = false; };
-    };
-  };
-};
-</programlisting>
-  </para>
-
-  <para>
-   If you're setting up a new GitLab instance, generate new
-   secrets. You for instance use <literal>tr -dc A-Za-z0-9 &lt;
-   /dev/urandom | head -c 128 &gt; /var/keys/gitlab/db</literal> to
-   generate a new db secret. Make sure the files can be read by, and
-   only by, the user specified by <link
-   linkend="opt-services.gitlab.user">services.gitlab.user</link>. GitLab
-   encrypts sensitive data stored in the database. If you're restoring
-   an existing GitLab instance, you must specify the secrets secret
-   from <literal>config/secrets.yml</literal> located in your GitLab
-   state folder.
-  </para>
-
-  <para>
-    When <literal>incoming_mail.enabled</literal> is set to <literal>true</literal>
-    in <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> an additional
-    service called <literal>gitlab-mailroom</literal> is enabled for fetching incoming mail.
-  </para>
-
-  <para>
-   Refer to <xref linkend="ch-options" /> for all available configuration
-   options for the
-   <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.
-  </para>
- </section>
- <section xml:id="module-services-gitlab-maintenance">
-  <title>Maintenance</title>
-
-  <section xml:id="module-services-gitlab-maintenance-backups">
-   <title>Backups</title>
-   <para>
-     Backups can be configured with the options in <link
-     linkend="opt-services.gitlab.backup.keepTime">services.gitlab.backup</link>. Use
-     the <link
-     linkend="opt-services.gitlab.backup.startAt">services.gitlab.backup.startAt</link>
-     option to configure regular backups.
-   </para>
-
-   <para>
-     To run a manual backup, start the <literal>gitlab-backup</literal> service:
-<screen>
-<prompt>$ </prompt>systemctl start gitlab-backup.service
-</screen>
-   </para>
-  </section>
-
-  <section xml:id="module-services-gitlab-maintenance-rake">
-   <title>Rake tasks</title>
-
-   <para>
-    You can run GitLab's rake tasks with <literal>gitlab-rake</literal>
-    which will be available on the system when GitLab is enabled. You
-    will have to run the command as the user that you configured to run
-    GitLab with.
-   </para>
-
-   <para>
-    A list of all availabe rake tasks can be obtained by running:
-<screen>
-<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
-</screen>
-   </para>
-  </section>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/gitolite.nix b/nixpkgs/nixos/modules/services/misc/gitolite.nix
index b313be074db0..012abda2d76f 100644
--- a/nixpkgs/nixos/modules/services/misc/gitolite.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitolite.nix
@@ -14,12 +14,11 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable gitolite management under the
-          <literal>gitolite</literal> user. After
+          `gitolite` user. After
           switching to a configuration with Gitolite enabled, you can
-          then run <literal>git clone
-          gitolite@host:gitolite-admin.git</literal> to manage it further.
+          then run `git clone gitolite@host:gitolite-admin.git` to manage it further.
         '';
       };
 
@@ -72,25 +71,25 @@ in
             @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
           '''
         '';
-        description = ''
-          Extra configuration to append to the default <literal>~/.gitolite.rc</literal>.
+        description = lib.mdDoc ''
+          Extra configuration to append to the default `~/.gitolite.rc`.
 
-          This should be Perl code that modifies the <literal>%RC</literal>
-          configuration variable. The default <literal>~/.gitolite.rc</literal>
-          content is generated by invoking <literal>gitolite print-default-rc</literal>,
+          This should be Perl code that modifies the `%RC`
+          configuration variable. The default `~/.gitolite.rc`
+          content is generated by invoking `gitolite print-default-rc`,
           and extra configuration from this option is appended to it. The result
-          is placed to Nix store, and the <literal>~/.gitolite.rc</literal> file
+          is placed to Nix store, and the `~/.gitolite.rc` file
           becomes a symlink to it.
 
           If you already have a customized (or otherwise changed)
-          <literal>~/.gitolite.rc</literal> file, NixOS will refuse to replace
+          `~/.gitolite.rc` file, NixOS will refuse to replace
           it with a symlink, and the `gitolite-init` initialization service
           will fail. In this situation, in order to use this option, you
           will need to take any customizations you may have in
-          <literal>~/.gitolite.rc</literal>, convert them to appropriate Perl
+          `~/.gitolite.rc`, convert them to appropriate Perl
           statements, add them to this option, and remove the file.
 
-          See also the <literal>enableGitAnnex</literal> option.
+          See also the `enableGitAnnex` option.
         '';
       };
 
@@ -102,6 +101,14 @@ in
         '';
       };
 
+      description = mkOption {
+        type = types.str;
+        default = "Gitolite user";
+        description = lib.mdDoc ''
+          Gitolite user account's description.
+        '';
+      };
+
       group = mkOption {
         type = types.str;
         default = "gitolite";
@@ -146,7 +153,7 @@ in
     '';
 
     users.users.${cfg.user} = {
-      description     = "Gitolite user";
+      description     = cfg.description;
       home            = cfg.dataDir;
       uid             = config.ids.uids.gitolite;
       group           = cfg.group;
diff --git a/nixpkgs/nixos/modules/services/misc/gitweb.nix b/nixpkgs/nixos/modules/services/misc/gitweb.nix
index ef20347ee245..aac0dac8a080 100644
--- a/nixpkgs/nixos/modules/services/misc/gitweb.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitweb.nix
@@ -47,7 +47,7 @@ in
         $highlight_bin = "${pkgs.highlight}/bin/highlight";
         ${cfg.extraConfig}
       '';
-      defaultText = literalDocBook "generated config file";
+      defaultText = literalMD "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 e726f2c5c7ce..fa172ed277dc 100644
--- a/nixpkgs/nixos/modules/services/misc/gogs.nix
+++ b/nixpkgs/nixos/modules/services/misc/gogs.nix
@@ -90,7 +90,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 3306;
           description = lib.mdDoc "Database host port.";
         };
@@ -167,7 +167,7 @@ in
       };
 
       httpPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
         description = lib.mdDoc "HTTP listen port.";
       };
diff --git a/nixpkgs/nixos/modules/services/misc/gollum.nix b/nixpkgs/nixos/modules/services/misc/gollum.nix
index a4bed2da8a2f..4eec9610b5e9 100644
--- a/nixpkgs/nixos/modules/services/misc/gollum.nix
+++ b/nixpkgs/nixos/modules/services/misc/gollum.nix
@@ -8,11 +8,7 @@ in
 
 {
   options.services.gollum = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable the Gollum service.";
-    };
+    enable = mkEnableOption (lib.mdDoc "Gollum service");
 
     address = mkOption {
       type = types.str;
@@ -21,7 +17,7 @@ in
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 4567;
       description = lib.mdDoc "Port on which the web server will run.";
     };
@@ -87,6 +83,14 @@ in
       description = lib.mdDoc "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
     };
 
+    package = mkOption {
+      type = types.package;
+      default = pkgs.gollum;
+      defaultText = literalExpression "pkgs.gollum";
+      description = lib.mdDoc ''
+        The package used in the service
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -120,7 +124,7 @@ in
         Group = config.users.groups.gollum.name;
         WorkingDirectory = cfg.stateDir;
         ExecStart = ''
-          ${pkgs.gollum}/bin/gollum \
+          ${cfg.package}/bin/gollum \
             --port ${toString cfg.port} \
             --host ${cfg.address} \
             --config ${pkgs.writeText "gollum-config.rb" cfg.extraConfig} \
diff --git a/nixpkgs/nixos/modules/services/misc/gpsd.nix b/nixpkgs/nixos/modules/services/misc/gpsd.nix
index 1ab8d1bbe062..ce0f9bb3ba28 100644
--- a/nixpkgs/nixos/modules/services/misc/gpsd.nix
+++ b/nixpkgs/nixos/modules/services/misc/gpsd.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -8,12 +8,15 @@ let
   gid = config.ids.gids.gpsd;
   cfg = config.services.gpsd;
 
-in
-
-{
+in {
 
   ###### interface
 
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "gpsd" "device" ]
+      "Use `services.gpsd.devices` instead.")
+  ];
+
   options = {
 
     services.gpsd = {
@@ -22,17 +25,21 @@ in
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Whether to enable `gpsd', a GPS service daemon.
+          Whether to enable `gpsd`, a GPS service daemon.
         '';
       };
 
-      device = mkOption {
-        type = types.str;
-        default = "/dev/ttyUSB0";
+      devices = mkOption {
+        type = types.listOf types.str;
+        default = [ "/dev/ttyUSB0" ];
         description = lib.mdDoc ''
-          A device may be a local serial device for GPS input, or a URL of the form:
-               `[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]`
-          in which case it specifies an input source for DGPS or ntrip data.
+          List of devices that `gpsd` should subscribe to.
+
+          A device may be a local serial device for GPS input, or a
+          URL of the form:
+          `[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]` in
+          which case it specifies an input source for DGPS or ntrip
+          data.
         '';
       };
 
@@ -77,21 +84,28 @@ in
         '';
       };
 
+      listenany = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Listen on all addresses rather than just loopback.
+        '';
+      };
+
     };
 
   };
 
-
   ###### implementation
 
   config = mkIf cfg.enable {
 
-    users.users.gpsd =
-      { inherit uid;
-        group = "gpsd";
-        description = "gpsd daemon user";
-        home = "/var/empty";
-      };
+    users.users.gpsd = {
+      inherit uid;
+      group = "gpsd";
+      description = "gpsd daemon user";
+      home = "/var/empty";
+    };
 
     users.groups.gpsd = { inherit gid; };
 
@@ -101,12 +115,15 @@ in
       after = [ "network.target" ];
       serviceConfig = {
         Type = "forking";
-        ExecStart = ''
+        ExecStart = let
+          devices = utils.escapeSystemdExecArgs cfg.devices;
+        in ''
           ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}"  \
             -S "${toString cfg.port}"                             \
             ${optionalString cfg.readonly "-b"}                   \
             ${optionalString cfg.nowait "-n"}                     \
-            "${cfg.device}"
+            ${optionalString cfg.listenany "-G"}                  \
+            ${devices}
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/misc/greenclip.nix b/nixpkgs/nixos/modules/services/misc/greenclip.nix
index 210827ea0755..45847af71141 100644
--- a/nixpkgs/nixos/modules/services/misc/greenclip.nix
+++ b/nixpkgs/nixos/modules/services/misc/greenclip.nix
@@ -7,7 +7,7 @@ let
 in {
 
   options.services.greenclip = {
-    enable = mkEnableOption "Greenclip daemon";
+    enable = mkEnableOption (lib.mdDoc "Greenclip daemon");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/heisenbridge.nix b/nixpkgs/nixos/modules/services/misc/heisenbridge.nix
index 486ba512ac54..d07e0e420462 100644
--- a/nixpkgs/nixos/modules/services/misc/heisenbridge.nix
+++ b/nixpkgs/nixos/modules/services/misc/heisenbridge.nix
@@ -23,13 +23,12 @@ let
 in
 {
   options.services.heisenbridge = {
-    enable = mkEnableOption "the Matrix to IRC bridge";
+    enable = mkEnableOption (lib.mdDoc "the Matrix to IRC bridge");
 
     package = mkOption {
       type = types.package;
       default = pkgs.heisenbridge;
-      defaultText = "pkgs.heisenbridge";
-      example = "pkgs.heisenbridge.override { … = …; }";
+      defaultText = lib.literalExpression "pkgs.heisenbridge";
       description = lib.mdDoc ''
         Package of the application to run, exposed for overriding purposes.
       '';
@@ -99,7 +98,7 @@ in
       };
     };
 
-    identd.enable = mkEnableOption "identd service support";
+    identd.enable = mkEnableOption (lib.mdDoc "identd service support");
     identd.port = mkOption {
       type = types.port;
       description = lib.mdDoc "identd listen port";
diff --git a/nixpkgs/nixos/modules/services/misc/ihaskell.nix b/nixpkgs/nixos/modules/services/misc/ihaskell.nix
index ff5709922e61..4782053c4fb8 100644
--- a/nixpkgs/nixos/modules/services/misc/ihaskell.nix
+++ b/nixpkgs/nixos/modules/services/misc/ihaskell.nix
@@ -30,10 +30,10 @@ in
             haskellPackages.lens
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra packages available to ghc when running ihaskell. The
           value must be a function which receives the attrset defined
-          in <varname>haskellPackages</varname> as the sole argument.
+          in {var}`haskellPackages` as the sole argument.
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/misc/input-remapper.nix b/nixpkgs/nixos/modules/services/misc/input-remapper.nix
index f66d714e1174..3f6d97f85738 100644
--- a/nixpkgs/nixos/modules/services/misc/input-remapper.nix
+++ b/nixpkgs/nixos/modules/services/misc/input-remapper.nix
@@ -6,9 +6,9 @@ let cfg = config.services.input-remapper; in
 {
   options = {
     services.input-remapper = {
-      enable = mkEnableOption "input-remapper, an easy to use tool to change the mapping of your input device buttons.";
-      package = options.mkPackageOption pkgs "input-remapper" { };
-      enableUdevRules = mkEnableOption "udev rules added by input-remapper to handle hotplugged devices. Currently disabled by default due to https://github.com/sezanzeb/input-remapper/issues/140";
+      enable = mkEnableOption (lib.mdDoc "input-remapper, an easy to use tool to change the mapping of your input device buttons");
+      package = mkPackageOptionMD pkgs "input-remapper" { };
+      enableUdevRules = mkEnableOption (lib.mdDoc "udev rules added by input-remapper to handle hotplugged devices. Currently disabled by default due to https://github.com/sezanzeb/input-remapper/issues/140");
       serviceWantedBy = mkOption {
         default = [ "graphical.target" ];
         example = [ "multi-user.target" ];
diff --git a/nixpkgs/nixos/modules/services/misc/jackett.nix b/nixpkgs/nixos/modules/services/misc/jackett.nix
index e8315d13417b..b0edf0d18da4 100644
--- a/nixpkgs/nixos/modules/services/misc/jackett.nix
+++ b/nixpkgs/nixos/modules/services/misc/jackett.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.jackett = {
-      enable = mkEnableOption "Jackett";
+      enable = mkEnableOption (lib.mdDoc "Jackett");
 
       dataDir = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/jellyfin.nix b/nixpkgs/nixos/modules/services/misc/jellyfin.nix
index af5256e46da8..2a4483199d7d 100644
--- a/nixpkgs/nixos/modules/services/misc/jellyfin.nix
+++ b/nixpkgs/nixos/modules/services/misc/jellyfin.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.jellyfin = {
-      enable = mkEnableOption "Jellyfin Media Server";
+      enable = mkEnableOption (lib.mdDoc "Jellyfin Media Server");
 
       user = mkOption {
         type = types.str;
@@ -81,7 +81,7 @@ in
         ProtectKernelTunables = !config.boot.isContainer;
         LockPersonality = true;
         PrivateTmp = !config.boot.isContainer;
-        # needed for hardware accelaration
+        # needed for hardware acceleration
         PrivateDevices = false;
         PrivateUsers = true;
         RemoveIPC = true;
diff --git a/nixpkgs/nixos/modules/services/misc/jellyseerr.nix b/nixpkgs/nixos/modules/services/misc/jellyseerr.nix
new file mode 100644
index 000000000000..31e0c5beb673
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/jellyseerr.nix
@@ -0,0 +1,62 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.jellyseerr;
+in
+{
+  meta.maintainers = [ maintainers.camillemndn ];
+
+  options.services.jellyseerr = {
+    enable = mkEnableOption (mdDoc ''Jellyseerr, a requests manager for Jellyfin'');
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''Open port in the firewall for the Jellyseerr web interface.'';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 5055;
+      description = mdDoc ''The port which the Jellyseerr web UI should listen to.'';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.jellyseerr = {
+      description = "Jellyseerr, a requests manager for Jellyfin";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PORT = toString cfg.port;
+      serviceConfig = {
+        Type = "exec";
+        StateDirectory = "jellyseerr";
+        WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr";
+        DynamicUser = true;
+        ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr";
+        BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ];
+        Restart = "on-failure";
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        NoNewPrivileges = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/klipper.nix b/nixpkgs/nixos/modules/services/misc/klipper.nix
index 34e9acc71929..ad881d4462a3 100644
--- a/nixpkgs/nixos/modules/services/misc/klipper.nix
+++ b/nixpkgs/nixos/modules/services/misc/klipper.nix
@@ -14,7 +14,7 @@ in
   ##### interface
   options = {
     services.klipper = {
-      enable = mkEnableOption "Klipper, the 3D printer firmware";
+      enable = mkEnableOption (lib.mdDoc "Klipper, the 3D printer firmware");
 
       package = mkOption {
         type = types.package;
@@ -23,6 +23,16 @@ in
         description = lib.mdDoc "The Klipper package.";
       };
 
+      logFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/klipper/klipper.log";
+        description = lib.mdDoc ''
+          Path of the file Klipper should log to.
+          If `null`, it logs to stdout, which is not recommended by upstream.
+        '';
+      };
+
       inputTTY = mkOption {
         type = types.path;
         default = "/run/klipper/tty";
@@ -35,6 +45,30 @@ in
         description = lib.mdDoc "Path of the API socket to create.";
       };
 
+      mutableConfig = mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description = lib.mdDoc ''
+          Whether to copy the config to a mutable directory instead of using the one directly from the nix store.
+          This will only copy the config if the file at `services.klipper.mutableConfigPath` doesn't exist.
+        '';
+      };
+
+      mutableConfigFolder = mkOption {
+        type = types.path;
+        default = "/var/lib/klipper";
+        description = lib.mdDoc "Path to mutable Klipper config file.";
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          Path to default Klipper config.
+        '';
+      };
+
       octoprintIntegration = mkOption {
         type = types.bool;
         default = false;
@@ -62,8 +96,8 @@ in
       };
 
       settings = mkOption {
-        type = format.type;
-        default = { };
+        type = types.nullOr format.type;
+        default = null;
         description = lib.mdDoc ''
           Configuration for Klipper. See the [documentation](https://www.klipper3d.org/Overview.html#configuration-and-tuning-guides)
           for supported values.
@@ -76,13 +110,17 @@ in
         type = with types; attrsOf
           (submodule {
             options = {
-              enable = mkEnableOption ''
+              enable = mkEnableOption (lib.mdDoc ''
                 building of firmware and addition of klipper-flash tools for manual flashing.
                 This will add `klipper-flash-$mcu` scripts to your environment which can be called to flash the firmware.
-              '';
+              '');
+              serial = mkOption {
+                type = types.nullOr path;
+                description = lib.mdDoc "Path to serial port this printer is connected to. Leave `null` to derive it from `service.klipper.settings`.";
+              };
               configFile = mkOption {
                 type = path;
-                description = "Path to firmware config which is generated using `klipper-genconf`";
+                description = lib.mdDoc "Path to firmware config which is generated using `klipper-genconf`";
               };
             };
           });
@@ -95,19 +133,25 @@ in
     assertions = [
       {
         assertion = cfg.octoprintIntegration -> config.services.octoprint.enable;
-        message = "Option klipper.octoprintIntegration requires Octoprint to be enabled on this system. Please enable services.octoprint to use it.";
+        message = "Option services.klipper.octoprintIntegration requires Octoprint to be enabled on this system. Please enable services.octoprint to use it.";
       }
       {
         assertion = cfg.user != null -> cfg.group != null;
-        message = "Option klipper.group is not set when a user is specified.";
+        message = "Option services.klipper.group is not set when services.klipper.user is specified.";
       }
       {
-        assertion = foldl (a: b: a && b) true (mapAttrsToList (mcu: _: mcu != null -> (hasAttrByPath [ "${mcu}" "serial" ] cfg.settings)) cfg.firmwares);
-        message = "Option klipper.settings.$mcu.serial must be set when klipper.firmware.$mcu is specified";
+        assertion = cfg.settings != null -> foldl (a: b: a && b) true (mapAttrsToList (mcu: _: mcu != null -> (hasAttrByPath [ "${mcu}" "serial" ] cfg.settings)) cfg.firmwares);
+        message = "Option services.klipper.settings.$mcu.serial must be set when settings.klipper.firmware.$mcu is specified";
+      }
+      {
+        assertion = (cfg.configFile != null) != (cfg.settings != null);
+        message = "You need to either specify services.klipper.settings or services.klipper.configFile.";
       }
     ];
 
-    environment.etc."klipper.cfg".source = format.generate "klipper.cfg" cfg.settings;
+    environment.etc = mkIf (!cfg.mutableConfig) {
+      "klipper.cfg".source = if cfg.settings != null then format.generate "klipper.cfg" cfg.settings else cfg.configFile;
+    };
 
     services.klipper = mkIf cfg.octoprintIntegration {
       user = config.services.octoprint.user;
@@ -117,16 +161,37 @@ in
     systemd.services.klipper =
       let
         klippyArgs = "--input-tty=${cfg.inputTTY}"
-          + optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}";
+          + optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}"
+          + optionalString (cfg.logFile != null) " --logfile=${cfg.logFile}"
+        ;
+        printerConfigPath =
+          if cfg.mutableConfig
+          then cfg.mutableConfigFolder + "/printer.cfg"
+          else "/etc/klipper.cfg";
+        printerConfigFile =
+          if cfg.settings != null
+          then format.generate "klipper.cfg" cfg.settings
+          else cfg.configFile;
       in
       {
         description = "Klipper 3D Printer Firmware";
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
+        preStart = ''
+          mkdir -p ${cfg.mutableConfigFolder}
+          ${lib.optionalString (cfg.mutableConfig) ''
+            [ -e ${printerConfigPath} ] || {
+              cp ${printerConfigFile} ${printerConfigPath}
+              chmod +w ${printerConfigPath}
+            }
+          ''}
+          mkdir -p ${cfg.mutableConfigFolder}/gcodes
+        '';
 
         serviceConfig = {
-          ExecStart = "${cfg.package}/lib/klipper/klippy.py ${klippyArgs} /etc/klipper.cfg";
+          ExecStart = "${cfg.package}/bin/klippy ${klippyArgs} ${printerConfigPath}";
           RuntimeDirectory = "klipper";
+          StateDirectory = "klipper";
           SupplementaryGroups = [ "dialout" ];
           WorkingDirectory = "${cfg.package}/lib";
           OOMScoreAdjust = "-999";
@@ -134,6 +199,7 @@ in
           CPUSchedulingPriority = 99;
           IOSchedulingClass = "realtime";
           IOSchedulingPriority = 0;
+          UMask = "0002";
         } // (if cfg.user != null then {
           Group = cfg.group;
           User = cfg.user;
@@ -146,8 +212,9 @@ in
     environment.systemPackages =
       with pkgs;
       let
+        default = a: b: if a != null then a else b;
         firmwares = filterAttrs (n: v: v!= null) (mapAttrs
-          (mcu: { enable, configFile }: if enable then pkgs.klipper-firmware.override {
+          (mcu: { enable, configFile, serial }: if enable then pkgs.klipper-firmware.override {
             mcu = lib.strings.sanitizeDerivationName mcu;
             firmwareConfig = configFile;
           } else null)
@@ -156,11 +223,14 @@ in
           (mcu: firmware: pkgs.klipper-flash.override {
             mcu = lib.strings.sanitizeDerivationName mcu;
             klipper-firmware = firmware;
-            flashDevice = cfg.settings."${mcu}".serial;
+            flashDevice = default cfg.firmwares."${mcu}".serial cfg.settings."${mcu}".serial;
             firmwareConfig = cfg.firmwares."${mcu}".configFile;
           })
           firmwares;
       in
       [ klipper-genconf ] ++ firmwareFlasher ++ attrValues firmwares;
   };
+  meta.maintainers = [
+    maintainers.cab404
+  ];
 }
diff --git a/nixpkgs/nixos/modules/services/misc/languagetool.nix b/nixpkgs/nixos/modules/services/misc/languagetool.nix
new file mode 100644
index 000000000000..9adf792373b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/languagetool.nix
@@ -0,0 +1,78 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.languagetool;
+  settingsFormat = pkgs.formats.javaProperties {};
+in {
+  options.services.languagetool = {
+    enable = mkEnableOption (mdDoc "the LanguageTool server");
+
+    port = mkOption {
+      type = types.port;
+      default = 8081;
+      example = 8081;
+      description = mdDoc ''
+        Port on which LanguageTool listens.
+      '';
+    };
+
+    public = mkEnableOption (mdDoc "access from anywhere (rather than just localhost)");
+
+    allowOrigin = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "https://my-website.org";
+      description = mdDoc ''
+        Set the Access-Control-Allow-Origin header in the HTTP response,
+        used for direct (non-proxy) JavaScript-based access from browsers.
+        `null` to allow access from all sites.
+      '';
+    };
+
+    settings = lib.mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options.cacheSize = mkOption {
+          type = types.ints.unsigned;
+          default = 1000;
+          apply = toString;
+          description = mdDoc "Number of sentences cached.";
+        };
+      };
+      default = {};
+      description = mdDoc ''
+        Configuration file options for LanguageTool, see
+        'languagetool-http-server --help'
+        for supported settings.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.languagetool =  {
+      description = "LanguageTool HTTP server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        User = "languagetool";
+        Group = "languagetool";
+        CapabilityBoundingSet = [ "" ];
+        RestrictNamespaces = [ "" ];
+        SystemCallFilter = [ "@system-service" "~ @privileged" ];
+        ProtectHome = "yes";
+        ExecStart = ''
+          ${pkgs.languagetool}/bin/languagetool-http-server \
+            --port ${toString cfg.port} \
+            ${optionalString cfg.public "--public"} \
+            ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \
+            "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings}
+          '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/leaps.nix b/nixpkgs/nixos/modules/services/misc/leaps.nix
index 0308fbfcf475..5522223ecc97 100644
--- a/nixpkgs/nixos/modules/services/misc/leaps.nix
+++ b/nixpkgs/nixos/modules/services/misc/leaps.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.leaps = {
-      enable = mkEnableOption "leaps";
+      enable = mkEnableOption (lib.mdDoc "leaps");
       port = mkOption {
         type = types.port;
         default = 8080;
diff --git a/nixpkgs/nixos/modules/services/misc/libreddit.nix b/nixpkgs/nixos/modules/services/misc/libreddit.nix
index 0359f57c0dcf..fd58928d2821 100644
--- a/nixpkgs/nixos/modules/services/misc/libreddit.nix
+++ b/nixpkgs/nixos/modules/services/misc/libreddit.nix
@@ -13,7 +13,14 @@ in
 {
   options = {
     services.libreddit = {
-      enable = mkEnableOption "Private front-end for Reddit";
+      enable = mkEnableOption (lib.mdDoc "Private front-end for Reddit");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.libreddit;
+        defaultText = literalExpression "pkgs.libreddit";
+        description = lib.mdDoc "Libreddit package to use.";
+      };
 
       address = mkOption {
         default = "0.0.0.0";
@@ -45,7 +52,7 @@ in
         after = [ "network.target" ];
         serviceConfig = {
           DynamicUser = true;
-          ExecStart = "${pkgs.libreddit}/bin/libreddit ${args}";
+          ExecStart = "${cfg.package}/bin/libreddit ${args}";
           AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
           Restart = "on-failure";
           RestartSec = "2s";
diff --git a/nixpkgs/nixos/modules/services/misc/lidarr.nix b/nixpkgs/nixos/modules/services/misc/lidarr.nix
index d070a7f091f2..92b00054bdff 100644
--- a/nixpkgs/nixos/modules/services/misc/lidarr.nix
+++ b/nixpkgs/nixos/modules/services/misc/lidarr.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.lidarr = {
-      enable = mkEnableOption "Lidarr";
+      enable = mkEnableOption (lib.mdDoc "Lidarr");
 
       dataDir = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/lifecycled.nix b/nixpkgs/nixos/modules/services/misc/lifecycled.nix
index fc8c77c6ca4f..fb5cabb4f038 100644
--- a/nixpkgs/nixos/modules/services/misc/lifecycled.nix
+++ b/nixpkgs/nixos/modules/services/misc/lifecycled.nix
@@ -25,10 +25,10 @@ in
 
   options = {
     services.lifecycled = {
-      enable = mkEnableOption "lifecycled";
+      enable = mkEnableOption (lib.mdDoc "lifecycled");
 
       queueCleaner = {
-        enable = mkEnableOption "lifecycled-queue-cleaner";
+        enable = mkEnableOption (lib.mdDoc "lifecycled-queue-cleaner");
 
         frequency = mkOption {
           type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/logkeys.nix b/nixpkgs/nixos/modules/services/misc/logkeys.nix
index 628f5627433b..75d073a0c94b 100644
--- a/nixpkgs/nixos/modules/services/misc/logkeys.nix
+++ b/nixpkgs/nixos/modules/services/misc/logkeys.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.logkeys;
 in {
   options.services.logkeys = {
-    enable = mkEnableOption "logkeys service";
+    enable = mkEnableOption (lib.mdDoc "logkeys service");
 
     device = mkOption {
       description = lib.mdDoc "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
diff --git a/nixpkgs/nixos/modules/services/misc/mbpfan.nix b/nixpkgs/nixos/modules/services/misc/mbpfan.nix
index 786ecf2d696f..e75c35254143 100644
--- a/nixpkgs/nixos/modules/services/misc/mbpfan.nix
+++ b/nixpkgs/nixos/modules/services/misc/mbpfan.nix
@@ -1,32 +1,33 @@
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
   cfg = config.services.mbpfan;
-  verbose = if cfg.verbose then "v" else "";
+  verbose = optionalString cfg.verbose "v";
   settingsFormat = pkgs.formats.ini {};
   settingsFile = settingsFormat.generate "mbpfan.ini" cfg.settings;
 
 in {
   options.services.mbpfan = {
-    enable = mkEnableOption "mbpfan, fan controller daemon for Apple Macs and MacBooks";
+    enable = mkEnableOption (lib.mdDoc "mbpfan, fan controller daemon for Apple Macs and MacBooks");
 
     package = mkOption {
       type = types.package;
       default = pkgs.mbpfan;
       defaultText = literalExpression "pkgs.mbpfan";
-      description = lib.mdDoc ''
-        The package used for the mbpfan daemon.
-      '';
+      description = lib.mdDoc "The package used for the mbpfan daemon.";
     };
 
     verbose = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc ''
-        If true, sets the log level to verbose.
-      '';
+      description = lib.mdDoc "If true, sets the log level to verbose.";
+    };
+
+    aggressive = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "If true, favors higher default fan speeds.";
     };
 
     settings = mkOption {
@@ -35,24 +36,14 @@ in {
       type = types.submodule {
         freeformType = settingsFormat.type;
 
-        options.general.min_fan1_speed = mkOption {
-          type = types.nullOr types.int;
-          default = 2000;
-          description = ''
-            You can check minimum and maximum fan limits with
-            "cat /sys/devices/platform/applesmc.768/fan*_min" and
-            "cat /sys/devices/platform/applesmc.768/fan*_max" respectively.
-            Setting to null implies using default value from applesmc.
-          '';
-        };
         options.general.low_temp = mkOption {
           type = types.int;
-          default = 55;
+          default = 63;
           description = lib.mdDoc "If temperature is below this, fans will run at minimum speed.";
         };
         options.general.high_temp = mkOption {
           type = types.int;
-          default = 58;
+          default = 66;
           description = lib.mdDoc "If temperature is above this, fan speed will gradually increase.";
         };
         options.general.max_temp = mkOption {
@@ -79,10 +70,16 @@ in {
   ];
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "coretemp" "applesmc" ];
+    services.mbpfan.settings = mkIf cfg.aggressive {
+      general.min_fan1_speed = mkDefault 2000;
+      general.low_temp = mkDefault 55;
+      general.high_temp = mkDefault 58;
+      general.max_temp = mkDefault 70;
+    };
 
-    environment.etc."mbpfan.conf".source = settingsFile;
+    boot.kernelModules = [ "coretemp" "applesmc" ];
     environment.systemPackages = [ cfg.package ];
+    environment.etc."mbpfan.conf".source = settingsFile;
 
     systemd.services.mbpfan = {
       description = "A fan manager daemon for MacBook Pro";
diff --git a/nixpkgs/nixos/modules/services/misc/mediatomb.nix b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
index 8cac87f53266..632b7caaac40 100644
--- a/nixpkgs/nixos/modules/services/misc/mediatomb.nix
+++ b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
@@ -288,7 +288,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 49152;
         description = lib.mdDoc ''
           The network port to listen on.
@@ -362,7 +362,9 @@ in {
     in mkIf cfg.enable {
     systemd.services.mediatomb = {
       description = "${cfg.serverName} media Server";
-      after = [ "network.target" ];
+      # Gerbera might fail if the network interface is not available on startup
+      # https://github.com/gerbera/gerbera/issues/1324
+      after = [ "network.target" "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig.ExecStart = "${binaryCommand} --port ${toString cfg.port} ${interfaceFlag} ${configFlag} --home ${cfg.dataDir}";
       serviceConfig.User = cfg.user;
diff --git a/nixpkgs/nixos/modules/services/misc/metabase.nix b/nixpkgs/nixos/modules/services/misc/metabase.nix
index 26c48c05037e..883fa0b95911 100644
--- a/nixpkgs/nixos/modules/services/misc/metabase.nix
+++ b/nixpkgs/nixos/modules/services/misc/metabase.nix
@@ -13,7 +13,7 @@ in {
   options = {
 
     services.metabase = {
-      enable = mkEnableOption "Metabase service";
+      enable = mkEnableOption (lib.mdDoc "Metabase service");
 
       listen = {
         ip = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/moonraker.nix b/nixpkgs/nixos/modules/services/misc/moonraker.nix
index 5b4e4bd34dc9..7e306d718e08 100644
--- a/nixpkgs/nixos/modules/services/misc/moonraker.nix
+++ b/nixpkgs/nixos/modules/services/misc/moonraker.nix
@@ -11,10 +11,12 @@ let
       else lib.concatMapStrings (s: "\n  ${generators.mkValueStringDefault {} s}") l;
     mkKeyValue = generators.mkKeyValueDefault {} ":";
   };
+
+  unifiedConfigDir = cfg.stateDir + "/config";
 in {
   options = {
     services.moonraker = {
-      enable = mkEnableOption "Moonraker, an API web server for Klipper";
+      enable = mkEnableOption (lib.mdDoc "Moonraker, an API web server for Klipper");
 
       klipperSocket = mkOption {
         type = types.path;
@@ -30,11 +32,10 @@ in {
       };
 
       configDir = mkOption {
-        type = types.path;
-        default = cfg.stateDir + "/config";
-        defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
+        type = types.nullOr types.path;
+        default = null;
         description = lib.mdDoc ''
-          The directory containing client-writable configuration files.
+          Deprecated directory containing client-writable configuration files.
 
           Clients will be able to edit files in this directory via the API. This directory must be writable.
         '';
@@ -71,7 +72,7 @@ in {
         example = {
           authorization = {
             trusted_clients = [ "10.0.0.0/24" ];
-            cors_domains = [ "https://app.fluidd.xyz" ];
+            cors_domains = [ "https://app.fluidd.xyz" "https://my.mainsail.xyz" ];
           };
         };
         description = lib.mdDoc ''
@@ -96,8 +97,18 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optional (cfg.settings ? update_manager)
-      ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.'';
+    warnings = []
+      ++ optional (cfg.settings ? update_manager)
+        ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.''
+      ++ optional (cfg.configDir != null)
+        ''
+          services.moonraker.configDir has been deprecated upstream and will be removed.
+
+          Action: ${
+            if cfg.configDir == unifiedConfigDir then "Simply remove services.moonraker.configDir from your config."
+            else "Move files from `${cfg.configDir}` to `${unifiedConfigDir}` then remove services.moonraker.configDir from your config."
+          }
+        '';
 
     assertions = [
       {
@@ -123,17 +134,21 @@ in {
           host = cfg.address;
           port = cfg.port;
           klippy_uds_address = cfg.klipperSocket;
+        };
+        machine = {
+          validate_service = false;
+        };
+      } // (lib.optionalAttrs (cfg.configDir != null) {
+        file_manager = {
           config_path = cfg.configDir;
-          database_path = "${cfg.stateDir}/database";
         };
-      };
+      });
       fullConfig = recursiveUpdate cfg.settings forcedConfig;
     in format.generate "moonraker.cfg" fullConfig;
 
     systemd.tmpfiles.rules = [
       "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -"
-    ];
+    ] ++ lib.optional (cfg.configDir != null) "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -";
 
     systemd.services.moonraker = {
       description = "Moonraker, an API web server for Klipper";
@@ -143,9 +158,16 @@ in {
 
       # Moonraker really wants its own config to be writable...
       script = ''
-        cp /etc/moonraker.cfg ${cfg.configDir}/moonraker-temp.cfg
-        chmod u+w ${cfg.configDir}/moonraker-temp.cfg
-        exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg
+        config_path=${
+          # Deprecated separate config dir
+          if cfg.configDir != null then "${cfg.configDir}/moonraker-temp.cfg"
+          # Config in unified data path
+          else "${unifiedConfigDir}/moonraker-temp.cfg"
+        }
+        mkdir -p $(dirname "$config_path")
+        cp /etc/moonraker.cfg "$config_path"
+        chmod u+w "$config_path"
+        exec ${pkg}/bin/moonraker -d ${cfg.stateDir} -c "$config_path"
       '';
 
       # Needs `ip` command
@@ -153,6 +175,7 @@ in {
 
       serviceConfig = {
         WorkingDirectory = cfg.stateDir;
+        PrivateTmp = true;
         Group = cfg.group;
         User = cfg.user;
       };
@@ -175,4 +198,10 @@ in {
       });
     '';
   };
+
+  meta.maintainers = with maintainers; [
+    cab404
+    vtuan10
+    zhaofengli
+  ];
 }
diff --git a/nixpkgs/nixos/modules/services/misc/n8n.nix b/nixpkgs/nixos/modules/services/misc/n8n.nix
index 40a262116c83..cdfe9dc8482c 100644
--- a/nixpkgs/nixos/modules/services/misc/n8n.nix
+++ b/nixpkgs/nixos/modules/services/misc/n8n.nix
@@ -9,8 +9,7 @@ let
 in
 {
   options.services.n8n = {
-
-    enable = mkEnableOption "n8n server";
+    enable = mkEnableOption (lib.mdDoc "n8n server");
 
     openFirewall = mkOption {
       type = types.bool;
@@ -22,7 +21,7 @@ in
       type = format.type;
       default = {};
       description = lib.mdDoc ''
-        Configuration for n8n, see <https://docs.n8n.io/reference/configuration.html>
+        Configuration for n8n, see <https://docs.n8n.io/hosting/environment-variables/configuration-methods/>
         for supported values.
       '';
     };
@@ -45,6 +44,10 @@ in
         N8N_USER_FOLDER = "/var/lib/n8n";
         HOME = "/var/lib/n8n";
         N8N_CONFIG_FILES = "${configFile}";
+
+        # Don't phone home
+        N8N_DIAGNOSTICS_ENABLED = "false";
+        N8N_VERSION_NOTIFICATIONS_ENABLED = "false";
       };
       serviceConfig = {
         Type = "simple";
diff --git a/nixpkgs/nixos/modules/services/misc/nitter.nix b/nixpkgs/nixos/modules/services/misc/nitter.nix
index e6cf69d23569..9336dbe38f34 100644
--- a/nixpkgs/nixos/modules/services/misc/nitter.nix
+++ b/nixpkgs/nixos/modules/services/misc/nitter.nix
@@ -45,9 +45,14 @@ let
   '';
 in
 {
+  imports = [
+    # https://github.com/zedeus/nitter/pull/772
+    (mkRemovedOptionModule [ "services" "nitter" "replaceInstagram" ] "Nitter no longer supports this option as Bibliogram has been discontinued.")
+  ];
+
   options = {
     services.nitter = {
-      enable = mkEnableOption "If enabled, start Nitter.";
+      enable = mkEnableOption (lib.mdDoc "Nitter");
 
       package = mkOption {
         default = pkgs.nitter;
@@ -155,6 +160,22 @@ in
           description = lib.mdDoc "Use base64 encoding for proxied media URLs.";
         };
 
+        enableRSS = mkEnableOption (lib.mdDoc "RSS feeds") // { default = true; };
+
+        enableDebug = mkEnableOption (lib.mdDoc "request logs and debug endpoints");
+
+        proxy = mkOption {
+          type = types.str;
+          default = "";
+          description = lib.mdDoc "URL to a HTTP/HTTPS proxy.";
+        };
+
+        proxyAuth = mkOption {
+          type = types.str;
+          default = "";
+          description = lib.mdDoc "Credentials for proxy.";
+        };
+
         tokenCount = mkOption {
           type = types.int;
           default = 10;
@@ -185,10 +206,11 @@ in
           description = lib.mdDoc "Replace YouTube links with links to this instance (blank to disable).";
         };
 
-        replaceInstagram = mkOption {
+        replaceReddit = mkOption {
           type = types.str;
           default = "";
-          description = lib.mdDoc "Replace Instagram links with links to this instance (blank to disable).";
+          example = "teddit.net";
+          description = lib.mdDoc "Replace Reddit links with links to this instance (blank to disable).";
         };
 
         mp4Playback = mkOption {
@@ -268,6 +290,12 @@ in
           default = false;
           description = lib.mdDoc "Hide tweet replies.";
         };
+
+        squareAvatars = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "Square profile pictures.";
+        };
       };
 
       settings = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
index d299c4a0a8f1..26dbae344164 100644
--- a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
@@ -42,7 +42,7 @@ let
         else if isDerivation v then toString v
         else if builtins.isPath v then toString v
         else if isString v then v
-        else if isCoercibleToString v then toString v
+        else if strings.isConvertibleWithToString v then toString v
         else abort "The nix conf value: ${toPretty {} v} can not be encoded";
 
       mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}";
@@ -59,7 +59,7 @@ let
         ${mkKeyValuePairs cfg.settings}
         ${cfg.extraOptions}
       '';
-      checkPhase =
+      checkPhase = lib.optionalString cfg.checkConfig (
         if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
           echo "Ignoring validation for cross-compilation"
         ''
@@ -72,9 +72,9 @@ let
             ${cfg.package}/bin/nix show-config ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
               ${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
             |& sed -e 's/^warning:/error:/' \
-            | (! grep '${if cfg.checkConfig then "^error:" else "^error: unknown setting"}')
+            | (! grep '${if cfg.checkAllErrors then "^error:" else "^error: unknown setting"}')
           set -o pipefail
-        '';
+        '');
     };
 
   legacyConfMappings = {
@@ -115,6 +115,7 @@ in
     (mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "useChroot" ]; to = [ "nix" "useSandbox" ]; })
     (mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "chrootDirs" ]; to = [ "nix" "sandboxPaths" ]; })
     (mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" "daemonIONiceLevel" ]; to = [ "nix" "daemonIOSchedPriority" ]; })
+    (mkRenamedOptionModuleWith { sinceRelease = 2211; from = [ "nix" "readOnlyStore" ]; to = [ "boot" "readOnlyNixStore" ]; })
     (mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.")
   ] ++ mapAttrsToList (oldConf: newConf: mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" oldConf ]; to = [ "nix" "settings" newConf ]; }) legacyConfMappings;
 
@@ -206,7 +207,7 @@ in
 
       daemonIOSchedPriority = mkOption {
         type = types.int;
-        default = 0;
+        default = 4;
         example = 1;
         description = lib.mdDoc ''
           Nix daemon process I/O scheduling priority. This priority propagates
@@ -227,14 +228,27 @@ in
                 The hostname of the build machine.
               '';
             };
+            protocol = mkOption {
+              type = types.enum [ null "ssh" "ssh-ng" ];
+              default = "ssh";
+              example = "ssh-ng";
+              description = lib.mdDoc ''
+                The protocol used for communicating with the build machine.
+                Use `ssh-ng` if your remote builder and your
+                local Nix version support that improved protocol.
+
+                Use `null` when trying to change the special localhost builder
+                without a protocol which is for example used by hydra.
+              '';
+            };
             system = mkOption {
               type = types.nullOr types.str;
               default = null;
               example = "x86_64-linux";
-              description = ''
+              description = lib.mdDoc ''
                 The system type the build machine can execute derivations on.
-                Either this attribute or <varname>systems</varname> must be
-                present, where <varname>system</varname> takes precedence if
+                Either this attribute or {var}`systems` must be
+                present, where {var}`system` takes precedence if
                 both are set.
               '';
             };
@@ -242,10 +256,10 @@ in
               type = types.listOf types.str;
               default = [ ];
               example = [ "x86_64-linux" "aarch64-linux" ];
-              description = ''
+              description = lib.mdDoc ''
                 The system types the build machine can execute derivations on.
-                Either this attribute or <varname>system</varname> must be
-                present, where <varname>system</varname> takes precedence if
+                Either this attribute or {var}`system` must be
+                present, where {var}`system` takes precedence if
                 both are set.
               '';
             };
@@ -264,7 +278,7 @@ in
               type = types.nullOr types.str;
               default = null;
               example = "/root/.ssh/id_buildhost_builduser";
-              description = ''
+              description = lib.mdDoc ''
                 The path to the SSH private key with which to authenticate on
                 the build machine. The private key must not have a passphrase.
                 If null, the building user (root on NixOS machines) must have an
@@ -297,11 +311,11 @@ in
               type = types.listOf types.str;
               default = [ ];
               example = [ "big-parallel" ];
-              description = ''
+              description = lib.mdDoc ''
                 A list of features mandatory for this builder. The builder will
                 be ignored for derivations that don't require all features in
                 this list. All mandatory features are automatically included in
-                <varname>supportedFeatures</varname>.
+                {var}`supportedFeatures`.
               '';
             };
             supportedFeatures = mkOption {
@@ -340,7 +354,7 @@ in
         type = types.attrs;
         internal = true;
         default = { };
-        description = "Environment variables used by Nix.";
+        description = lib.mdDoc "Environment variables used by Nix.";
       };
 
       nrBuildUsers = mkOption {
@@ -353,17 +367,6 @@ in
         '';
       };
 
-      readOnlyStore = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          If set, NixOS will enforce the immutability of the Nix store
-          by making {file}`/nix/store` a read-only bind
-          mount.  Nix will automatically make the store writable when
-          needed.
-        '';
-      };
-
       nixPath = mkOption {
         type = types.listOf types.str;
         default = [
@@ -382,8 +385,15 @@ in
         type = types.bool;
         default = true;
         description = lib.mdDoc ''
-          If enabled (the default), checks for data type mismatches and that Nix
-          can parse the generated nix.conf.
+          If enabled, checks that Nix can parse the generated nix.conf.
+        '';
+      };
+
+      checkAllErrors = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          If enabled, checks the nix.conf parsing for any kind of error. When disabled, checks only for unknown settings.
         '';
       };
 
@@ -394,6 +404,7 @@ in
               str
               int
               bool
+              path
               package
             ]);
           in
@@ -430,13 +441,14 @@ in
             };
             config = {
               from = mkDefault { type = "indirect"; id = name; };
-              to = mkIf (config.flake != null) (mkDefault
+              to = mkIf (config.flake != null) (mkDefault (
                 {
                   type = "path";
                   path = config.flake.outPath;
                 } // filterAttrs
-                (n: _: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
-                config.flake);
+                  (n: _: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
+                  config.flake
+              ));
             };
           }
         ));
@@ -508,10 +520,17 @@ in
                 will set up automatically for each build. This prevents impurities
                 in builds by disallowing access to dependencies outside of the Nix
                 store by using network and mount namespaces in a chroot environment.
+
                 This is enabled by default even though it has a possible performance
                 impact due to the initial setup time of a sandbox for each build. It
                 doesn't affect derivation hashes, so changing this option will not
                 trigger a rebuild of packages.
+
+                When set to "relaxed", this option permits derivations that set
+                `__noChroot = true;` to run outside of the sandboxed environment.
+                Exercise caution when using this mode of operation! It is intended to
+                be a quick hack when building with packages that are not easily setup
+                to be built reproducibly.
               '';
             };
 
@@ -562,13 +581,13 @@ in
             trusted-public-keys = mkOption {
               type = types.listOf types.str;
               example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ];
-              description = ''
+              description = lib.mdDoc ''
                 List of public keys used to sign binary caches. If
-                <option>nix.settings.trusted-public-keys</option> is enabled,
+                {option}`nix.settings.trusted-public-keys` is enabled,
                 then Nix will use a binary from a binary cache if and only
-                if it is signed by <emphasis>any</emphasis> of the keys
+                if it is signed by *any* of the keys
                 listed here. By default, only the key for
-                <uri>cache.nixos.org</uri> is included.
+                `cache.nixos.org` is included.
               '';
             };
 
@@ -597,7 +616,7 @@ in
 
                 By default, pseudo-features `nixos-test`, `benchmark`,
                 and `big-parallel` used in Nixpkgs are set, `kvm`
-                is also included in it is avaliable.
+                is also included if it is available.
               '';
             };
 
@@ -605,13 +624,13 @@ in
               type = types.listOf types.str;
               default = [ "*" ];
               example = [ "@wheel" "@builders" "alice" "bob" ];
-              description = ''
+              description = lib.mdDoc ''
                 A list of names of users (separated by whitespace) that are
                 allowed to connect to the Nix daemon. As with
-                <option>nix.settings.trusted-users</option>, you can specify groups by
-                prefixing them with <literal>@</literal>. Also, you can
-                allow all users by specifying <literal>*</literal>. The
-                default is <literal>*</literal>. Note that trusted users are
+                {option}`nix.settings.trusted-users`, you can specify groups by
+                prefixing them with `@`. Also, you can
+                allow all users by specifying `*`. The
+                default is `*`. Note that trusted users are
                 always allowed to connect.
               '';
             };
@@ -627,17 +646,17 @@ in
             sandbox-paths = { "/bin/sh" = "''${pkgs.busybox-sandbox-shell.out}/bin/busybox"; };
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Configuration for Nix, see
-          <link xlink:href="https://nixos.org/manual/nix/stable/#sec-conf-file"/> or
-          <citerefentry><refentrytitle>nix.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> for avalaible options.
+          <https://nixos.org/manual/nix/stable/#sec-conf-file> or
+          {manpage}`nix.conf(5)` for available options.
           The value declared here will be translated directly to the key-value pairs Nix expects.
 
-          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.nix.settings</command>
+          You can use {command}`nix-instantiate --eval --strict '<nixpkgs/nixos>' -A config.nix.settings`
           to view the current value. By default it is empty.
 
-          Nix configurations defined under <option>nix.*</option> will be translated and applied to this
-          option. In addition, configuration specified in <option>nix.extraOptions</option> which will be appended
+          Nix configurations defined under {option}`nix.*` will be translated and applied to this
+          option. In addition, configuration specified in {option}`nix.extraOptions` which will be appended
           verbatim to the resulting config file.
         '';
       };
@@ -669,13 +688,15 @@ in
         concatMapStrings
           (machine:
             (concatStringsSep " " ([
-              "${optionalString (machine.sshUser != null) "${machine.sshUser}@"}${machine.hostName}"
+              "${optionalString (machine.protocol != null) "${machine.protocol}://"}${optionalString (machine.sshUser != null) "${machine.sshUser}@"}${machine.hostName}"
               (if machine.system != null then machine.system else if machine.systems != [ ] then concatStringsSep "," machine.systems else "-")
               (if machine.sshKey != null then machine.sshKey else "-")
               (toString machine.maxJobs)
               (toString machine.speedFactor)
-              (concatStringsSep "," (machine.supportedFeatures ++ machine.mandatoryFeatures))
-              (concatStringsSep "," machine.mandatoryFeatures)
+              (let res = (machine.supportedFeatures ++ machine.mandatoryFeatures);
+               in if (res == []) then "-" else (concatStringsSep "," res))
+              (let res = machine.mandatoryFeatures;
+               in if (res == []) then "-" else (concatStringsSep "," machine.mandatoryFeatures))
             ]
             ++ optional (isNixAtLeast "2.4pre") (if machine.publicHostKey != null then machine.publicHostKey else "-")))
             + "\n"
@@ -778,7 +799,10 @@ in
         fi
       '';
 
-    nix.nrBuildUsers = mkDefault (max 32 (if cfg.settings.max-jobs == "auto" then 0 else cfg.settings.max-jobs));
+    nix.nrBuildUsers = mkDefault (
+      if cfg.settings.auto-allocate-uids or false then 0
+      else max 32 (if cfg.settings.max-jobs == "auto" then 0 else cfg.settings.max-jobs)
+    );
 
     users.users = nixbldUsers;
 
@@ -802,10 +826,10 @@ in
 
         system-features = mkDefault (
           [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
-          optionals (pkgs.hostPlatform ? gcc.arch) (
+          optionals (pkgs.stdenv.hostPlatform ? gcc.arch) (
             # a builder can run code for `gcc.arch` and inferior architectures
-            [ "gccarch-${pkgs.hostPlatform.gcc.arch}" ] ++
-            map (x: "gccarch-${x}") systems.architectures.inferiors.${pkgs.hostPlatform.gcc.arch}
+            [ "gccarch-${pkgs.stdenv.hostPlatform.gcc.arch}" ] ++
+            map (x: "gccarch-${x}") (systems.architectures.inferiors.${pkgs.stdenv.hostPlatform.gcc.arch} or [])
           )
         );
       }
diff --git a/nixpkgs/nixos/modules/services/misc/novacomd.nix b/nixpkgs/nixos/modules/services/misc/novacomd.nix
index 7cfc68d2b673..bde8328d46f8 100644
--- a/nixpkgs/nixos/modules/services/misc/novacomd.nix
+++ b/nixpkgs/nixos/modules/services/misc/novacomd.nix
@@ -10,7 +10,7 @@ in {
 
   options = {
     services.novacomd = {
-      enable = mkEnableOption "Novacom service for connecting to WebOS devices";
+      enable = mkEnableOption (lib.mdDoc "Novacom service for connecting to WebOS devices");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/misc/ntfy-sh.nix b/nixpkgs/nixos/modules/services/misc/ntfy-sh.nix
new file mode 100644
index 000000000000..3258f91f7044
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ntfy-sh.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ntfy-sh;
+
+  settingsFormat = pkgs.formats.yaml { };
+in
+
+{
+  options.services.ntfy-sh = {
+    enable = mkEnableOption (mdDoc "[ntfy-sh](https://ntfy.sh), a push notification service");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.ntfy-sh;
+      defaultText = literalExpression "pkgs.ntfy-sh";
+      description = mdDoc "The ntfy.sh package to use.";
+    };
+
+    user = mkOption {
+      default = "ntfy-sh";
+      type = types.str;
+      description = lib.mdDoc "User the ntfy-sh server runs under.";
+    };
+
+    group = mkOption {
+      default = "ntfy-sh";
+      type = types.str;
+      description = lib.mdDoc "Primary group of ntfy-sh user.";
+    };
+
+    settings = mkOption {
+      type = types.submodule { freeformType = settingsFormat.type; };
+
+      default = { };
+
+      example = literalExpression ''
+        {
+          listen-http = ":8080";
+        }
+      '';
+
+      description = mdDoc ''
+        Configuration for ntfy.sh, supported values are [here](https://ntfy.sh/docs/config/#config-options).
+      '';
+    };
+  };
+
+  config =
+    let
+      configuration = settingsFormat.generate "server.yml" cfg.settings;
+    in
+    mkIf cfg.enable {
+      # to configure access control via the cli
+      environment = {
+        etc."ntfy/server.yml".source = configuration;
+        systemPackages = [ cfg.package ];
+      };
+
+      services.ntfy-sh.settings = {
+        auth-file = mkDefault "/var/lib/ntfy-sh/user.db";
+        listen-http = mkDefault "127.0.0.1:2586";
+        attachment-cache-dir = mkDefault "/var/lib/ntfy-sh/attachments";
+        cache-file = mkDefault "/var/lib/ntfy-sh/cache-file.db";
+      };
+
+      systemd.tmpfiles.rules = [
+        "f ${cfg.settings.auth-file} 0600 ${cfg.user} ${cfg.group} - -"
+        "d ${cfg.settings.attachment-cache-dir} 0700 ${cfg.user} ${cfg.group} - -"
+        "f ${cfg.settings.cache-file} 0600 ${cfg.user} ${cfg.group} - -"
+      ];
+
+      systemd.services.ntfy-sh = {
+        description = "Push notifications server";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/ntfy serve -c ${configuration}";
+          User = cfg.user;
+          StateDirectory = "ntfy-sh";
+
+          DynamicUser = true;
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          PrivateTmp = true;
+          NoNewPrivileges = true;
+          CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+          ProtectSystem = "full";
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          PrivateDevices = true;
+          RestrictSUIDSGID = true;
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          MemoryDenyWriteExecute = true;
+          # Upstream Recommandation
+          LimitNOFILE = 20500;
+        };
+      };
+
+      users.groups = optionalAttrs (cfg.group == "ntfy-sh") {
+        ntfy-sh = { };
+      };
+
+      users.users = optionalAttrs (cfg.user == "ntfy-sh") {
+        ntfy-sh = {
+          isSystemUser = true;
+          group = cfg.group;
+        };
+      };
+    };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nzbget.nix b/nixpkgs/nixos/modules/services/misc/nzbget.nix
index ddcb16e135cf..d02fda62fa4f 100644
--- a/nixpkgs/nixos/modules/services/misc/nzbget.nix
+++ b/nixpkgs/nixos/modules/services/misc/nzbget.nix
@@ -25,7 +25,7 @@ in
 
   options = {
     services.nzbget = {
-      enable = mkEnableOption "NZBGet";
+      enable = mkEnableOption (lib.mdDoc "NZBGet");
 
       user = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/nzbhydra2.nix b/nixpkgs/nixos/modules/services/misc/nzbhydra2.nix
index b728ca248ce3..47d08135f57e 100644
--- a/nixpkgs/nixos/modules/services/misc/nzbhydra2.nix
+++ b/nixpkgs/nixos/modules/services/misc/nzbhydra2.nix
@@ -7,7 +7,7 @@ let cfg = config.services.nzbhydra2;
 in {
   options = {
     services.nzbhydra2 = {
-      enable = mkEnableOption "NZBHydra2";
+      enable = mkEnableOption (lib.mdDoc "NZBHydra2");
 
       dataDir = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/octoprint.nix b/nixpkgs/nixos/modules/services/misc/octoprint.nix
index 071174c141d1..43e0ce0c21d3 100644
--- a/nixpkgs/nixos/modules/services/misc/octoprint.nix
+++ b/nixpkgs/nixos/modules/services/misc/octoprint.nix
@@ -17,7 +17,7 @@ let
 
   cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
 
-  pluginsEnv = package.python.withPackages (ps: [ps.octoprint] ++ (cfg.plugins ps));
+  pluginsEnv = package.python.withPackages (ps: [ ps.octoprint ] ++ (cfg.plugins ps));
 
   package = pkgs.octoprint;
 
@@ -29,7 +29,7 @@ in
 
     services.octoprint = {
 
-      enable = mkEnableOption "OctoPrint, web interface for 3D printers";
+      enable = mkEnableOption (lib.mdDoc "OctoPrint, web interface for 3D printers");
 
       host = mkOption {
         type = types.str;
@@ -47,6 +47,12 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Open ports in the firewall for OctoPrint.";
+      };
+
       user = mkOption {
         type = types.str;
         default = "octoprint";
@@ -67,7 +73,7 @@ in
 
       plugins = mkOption {
         type = types.functionTo (types.listOf types.package);
-        default = plugins: [];
+        default = plugins: [ ];
         defaultText = literalExpression "plugins: []";
         example = literalExpression "plugins: with plugins; [ themeify stlviewer ]";
         description = lib.mdDoc "Additional plugins to be used. Available plugins are passed through the plugins input.";
@@ -75,7 +81,7 @@ in
 
       extraConfig = mkOption {
         type = types.attrs;
-        default = {};
+        default = { };
         description = lib.mdDoc "Extra options which are added to OctoPrint's YAML configuration file.";
       };
 
@@ -100,6 +106,9 @@ in
 
     systemd.tmpfiles.rules = [
       "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+      # this will allow octoprint access to raspberry specific hardware to check for throttling
+      # read-only will not work: "VCHI initialization failed" error
+      "a /dev/vchiq - - - - u:octoprint:rw"
     ];
 
     systemd.services.octoprint = {
@@ -128,6 +137,6 @@ in
       };
     };
 
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
   };
-
 }
diff --git a/nixpkgs/nixos/modules/services/misc/ombi.nix b/nixpkgs/nixos/modules/services/misc/ombi.nix
index 51cfb05d35c9..8bf6a9b116ec 100644
--- a/nixpkgs/nixos/modules/services/misc/ombi.nix
+++ b/nixpkgs/nixos/modules/services/misc/ombi.nix
@@ -7,11 +7,11 @@ let cfg = config.services.ombi;
 in {
   options = {
     services.ombi = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         Ombi.
-        Optionally see <link xlink:href="https://docs.ombi.app/info/reverse-proxy"/>
+        Optionally see <https://docs.ombi.app/info/reverse-proxy>
         on how to set up a reverse proxy
-      '';
+      '');
 
       dataDir = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/osrm.nix b/nixpkgs/nixos/modules/services/misc/osrm.nix
index bcfb868422cc..12c908a761e3 100644
--- a/nixpkgs/nixos/modules/services/misc/osrm.nix
+++ b/nixpkgs/nixos/modules/services/misc/osrm.nix
@@ -21,7 +21,7 @@ in
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 5000;
       description = lib.mdDoc "Port on which the web server will run.";
     };
diff --git a/nixpkgs/nixos/modules/services/misc/owncast.nix b/nixpkgs/nixos/modules/services/misc/owncast.nix
index 23c49d1c1196..01fe34cf50fe 100644
--- a/nixpkgs/nixos/modules/services/misc/owncast.nix
+++ b/nixpkgs/nixos/modules/services/misc/owncast.nix
@@ -5,7 +5,7 @@ in {
 
   options.services.owncast = {
 
-    enable = mkEnableOption "owncast";
+    enable = mkEnableOption (lib.mdDoc "owncast");
 
     dataDir = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/packagekit.nix b/nixpkgs/nixos/modules/services/misc/packagekit.nix
index 04150ef76ff6..f3e6bf50e9b2 100644
--- a/nixpkgs/nixos/modules/services/misc/packagekit.nix
+++ b/nixpkgs/nixos/modules/services/misc/packagekit.nix
@@ -39,11 +39,11 @@ in
   ];
 
   options.services.packagekit = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       PackageKit provides a cross-platform D-Bus abstraction layer for
       installing software. Software utilizing PackageKit can install
       software regardless of the package manager.
-    '';
+    '');
 
     settings = mkOption {
       type = iniFmt.type;
diff --git a/nixpkgs/nixos/modules/services/misc/paperless.nix b/nixpkgs/nixos/modules/services/misc/paperless.nix
index fbf1338a0dff..4199e7713304 100644
--- a/nixpkgs/nixos/modules/services/misc/paperless.nix
+++ b/nixpkgs/nixos/modules/services/misc/paperless.nix
@@ -3,8 +3,10 @@
 with lib;
 let
   cfg = config.services.paperless;
+  pkg = cfg.package;
 
   defaultUser = "paperless";
+  nltkDir = "/var/cache/paperless/nltk";
 
   # Don't start a redis instance if the user sets a custom redis connection
   enableRedis = !hasAttr "PAPERLESS_REDIS" cfg.extraConfig;
@@ -14,19 +16,24 @@ let
     PAPERLESS_DATA_DIR = cfg.dataDir;
     PAPERLESS_MEDIA_ROOT = cfg.mediaDir;
     PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
+    PAPERLESS_NLTK_DIR = nltkDir;
     GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
+  } // optionalAttrs (config.time.timeZone != null) {
+    PAPERLESS_TIME_ZONE = config.time.timeZone;
+  } // optionalAttrs enableRedis {
+    PAPERLESS_REDIS = "unix://${redisServer.unixSocket}";
   } // (
     lib.mapAttrs (_: toString) cfg.extraConfig
-  ) // (optionalAttrs enableRedis {
-    PAPERLESS_REDIS = "unix://${redisServer.unixSocket}";
-  });
+  );
 
-  manage = let
-    setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
-  in pkgs.writeShellScript "manage" ''
-    ${setupEnv}
-    exec ${cfg.package}/bin/paperless-ngx "$@"
-  '';
+  manage =
+    let
+      setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
+    in
+    pkgs.writeShellScript "manage" ''
+      ${setupEnv}
+      exec ${pkg}/bin/paperless-ngx "$@"
+    '';
 
   # Secure the services
   defaultServiceConfig = {
@@ -44,6 +51,7 @@ let
       cfg.dataDir
       cfg.mediaDir
     ];
+    CacheDirectory = "paperless";
     CapabilityBoundingSet = "";
     # ProtectClock adds DeviceAllow=char-rtc r
     DeviceAllow = "";
@@ -77,7 +85,7 @@ let
     RestrictSUIDSGID = true;
     SupplementaryGroups = optional enableRedis redisServer.user;
     SystemCallArchitectures = "native";
-    SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
+    SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
     # Does not work well with the temporary root
     #UMask = "0066";
   };
@@ -167,18 +175,17 @@ in
 
     extraConfig = mkOption {
       type = types.attrs;
-      default = {};
+      default = { };
       description = lib.mdDoc ''
         Extra paperless config options.
 
         See [the documentation](https://paperless-ngx.readthedocs.io/en/latest/configuration.html)
         for available options.
       '';
-      example = literalExpression ''
-        {
-          PAPERLESS_OCR_LANGUAGE = "deu+eng";
-        }
-      '';
+      example = {
+        PAPERLESS_OCR_LANGUAGE = "deu+eng";
+        PAPERLESS_DBHOST = "/run/postgresql";
+      };
     };
 
     user = mkOption {
@@ -209,28 +216,41 @@ in
     ];
 
     systemd.services.paperless-scheduler = {
-      description = "Paperless scheduler";
+      description = "Paperless Celery Beat";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "paperless-consumer.service" "paperless-web.service" "paperless-task-queue.service" ];
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = "${cfg.package}/bin/paperless-ngx qcluster";
+        ExecStart = "${pkg}/bin/celery --app paperless beat --loglevel INFO";
         Restart = "on-failure";
-        # The `mbind` syscall is needed for running the classifier.
-        SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ];
-        # Needs to talk to mail server for automated import rules
-        PrivateNetwork = false;
       };
       environment = env;
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "paperless-consumer.service" "paperless-web.service" ];
 
       preStart = ''
         ln -sf ${manage} ${cfg.dataDir}/paperless-manage
 
         # Auto-migrate on first run or if the package has changed
         versionFile="${cfg.dataDir}/src-version"
-        if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
-          ${cfg.package}/bin/paperless-ngx migrate
-          echo ${cfg.package} > "$versionFile"
+        version=$(cat "$versionFile" 2>/dev/null || echo 0)
+
+        if [[ $version != ${pkg.version} ]]; then
+          ${pkg}/bin/paperless-ngx migrate
+
+          # Parse old version string format for backwards compatibility
+          version=$(echo "$version" | grep -ohP '[^-]+$')
+
+          versionLessThan() {
+            target=$1
+            [[ $({ echo "$version"; echo "$target"; } | sort -V | head -1) != "$target" ]]
+          }
+
+          if versionLessThan 1.12.0; then
+            # Reindex documents as mentioned in https://github.com/paperless-ngx/paperless-ngx/releases/tag/v1.12.1
+            echo "Reindexing documents, to allow searching old comments. Required after the 1.12.x upgrade."
+            ${pkg}/bin/paperless-ngx document_index reindex
+          fi
+
+          echo ${pkg.version} > "$versionFile"
         fi
       ''
       + optionalString (cfg.passwordFile != null) ''
@@ -240,7 +260,7 @@ in
         superuserStateFile="${cfg.dataDir}/superuser-state"
 
         if [[ $(cat "$superuserStateFile" 2>/dev/null) != $superuserState ]]; then
-          ${cfg.package}/bin/paperless-ngx manage_superuser
+          ${pkg}/bin/paperless-ngx manage_superuser
           echo "$superuserState" > "$superuserStateFile"
         fi
       '';
@@ -248,6 +268,21 @@ in
       after = [ "redis-paperless.service" ];
     };
 
+    systemd.services.paperless-task-queue = {
+      description = "Paperless Celery Workers";
+      after = [ "paperless-scheduler.service" ];
+      serviceConfig = defaultServiceConfig // {
+        User = cfg.user;
+        ExecStart = "${pkg}/bin/celery --app paperless worker --loglevel INFO";
+        Restart = "on-failure";
+        # The `mbind` syscall is needed for running the classifier.
+        SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ];
+        # Needs to talk to mail server for automated import rules
+        PrivateNetwork = false;
+      };
+      environment = env;
+    };
+
     # Reading the user-provided password file requires root access
     systemd.services.paperless-copy-password = mkIf (cfg.passwordFile != null) {
       requiredBy = [ "paperless-scheduler.service" ];
@@ -261,48 +296,76 @@ in
       };
     };
 
-    systemd.services.paperless-consumer = {
-      description = "Paperless document consumer";
+    # Download NLTK corpus data
+    systemd.services.paperless-download-nltk-data = {
+      wantedBy = [ "paperless-scheduler.service" ];
+      before = [ "paperless-scheduler.service" ];
+      after = [ "network-online.target" ];
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = "${cfg.package}/bin/paperless-ngx document_consumer";
-        Restart = "on-failure";
+        Type = "oneshot";
+        # Enable internet access
+        PrivateNetwork = false;
+        # Restrict write access
+        BindPaths = [];
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/ssl/certs"
+          "-/etc/static/ssl/certs"
+          "-/etc/hosts"
+          "-/etc/localtime"
+        ];
+        ExecStart = let pythonWithNltk = pkg.python.withPackages (ps: [ ps.nltk ]); in ''
+          ${pythonWithNltk}/bin/python -m nltk.downloader -d '${nltkDir}' punkt snowball_data stopwords
+        '';
       };
-      environment = env;
+    };
+
+    systemd.services.paperless-consumer = {
+      description = "Paperless document consumer";
       # Bind to `paperless-scheduler` so that the consumer never runs
       # during migrations
       bindsTo = [ "paperless-scheduler.service" ];
       after = [ "paperless-scheduler.service" ];
+      serviceConfig = defaultServiceConfig // {
+        User = cfg.user;
+        ExecStart = "${pkg}/bin/paperless-ngx document_consumer";
+        Restart = "on-failure";
+      };
+      environment = env;
     };
 
     systemd.services.paperless-web = {
       description = "Paperless web server";
+      # Bind to `paperless-scheduler` so that the web server never runs
+      # during migrations
+      bindsTo = [ "paperless-scheduler.service" ];
+      after = [ "paperless-scheduler.service" ];
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
         ExecStart = ''
-          ${pkgs.python3Packages.gunicorn}/bin/gunicorn \
-            -c ${cfg.package}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
+          ${pkg.python.pkgs.gunicorn}/bin/gunicorn \
+            -c ${pkg}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
         '';
         Restart = "on-failure";
 
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
-        # gunicorn needs setuid
-        SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid" ];
+        # gunicorn needs setuid, liblapack needs mbind
+        SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid mbind" ];
         # Needs to serve web page
         PrivateNetwork = false;
+      } // lib.optionalAttrs (cfg.port < 1024) {
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
       };
       environment = env // {
-        PATH = mkForce cfg.package.path;
-        PYTHONPATH = "${cfg.package.pythonPath}:${cfg.package}/lib/paperless-ngx/src";
+        PATH = mkForce pkg.path;
+        PYTHONPATH = "${pkg.python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/paperless-ngx/src";
       };
       # Allow the web interface to access the private /tmp directory of the server.
       # This is required to support uploading files via the web interface.
-      unitConfig.JoinsNamespaceOf = "paperless-scheduler.service";
-      # Bind to `paperless-scheduler` so that the web server never runs
-      # during migrations
-      bindsTo = [ "paperless-scheduler.service" ];
-      after = [ "paperless-scheduler.service" ];
+      unitConfig.JoinsNamespaceOf = "paperless-task-queue.service";
     };
 
     users = optionalAttrs (cfg.user == defaultUser) {
diff --git a/nixpkgs/nixos/modules/services/misc/parsoid.nix b/nixpkgs/nixos/modules/services/misc/parsoid.nix
index 101ece5ab4c6..6f4a340c8a18 100644
--- a/nixpkgs/nixos/modules/services/misc/parsoid.nix
+++ b/nixpkgs/nixos/modules/services/misc/parsoid.nix
@@ -70,7 +70,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8000;
         description = lib.mdDoc ''
           Port to listen on.
diff --git a/nixpkgs/nixos/modules/services/misc/persistent-evdev.nix b/nixpkgs/nixos/modules/services/misc/persistent-evdev.nix
index fd6e298ef651..b1f367fec7fb 100644
--- a/nixpkgs/nixos/modules/services/misc/persistent-evdev.nix
+++ b/nixpkgs/nixos/modules/services/misc/persistent-evdev.nix
@@ -11,21 +11,21 @@ let
 in
 {
   options.services.persistent-evdev = {
-    enable = lib.mkEnableOption "virtual input devices that persist even if the backing device is hotplugged";
+    enable = lib.mkEnableOption (lib.mdDoc "virtual input devices that persist even if the backing device is hotplugged");
 
     devices = lib.mkOption {
       default = {};
       type = with lib.types; attrsOf str;
-      description = ''
+      description = lib.mdDoc ''
         A set of virtual proxy device labels with backing physical device ids.
 
-        Physical devices should already exist in <filename class="devicefile">/dev/input/by-id/</filename>.
-        Proxy devices will be automatically given a <literal>uinput-</literal> prefix.
+        Physical devices should already exist in {file}`/dev/input/by-id/`.
+        Proxy devices will be automatically given a `uinput-` prefix.
 
-        See the <link xlink:href="https://github.com/aiberia/persistent-evdev#example-usage-with-libvirt">project page</link>
+        See the [project page](https://github.com/aiberia/persistent-evdev#example-usage-with-libvirt)
         for example configuration of virtual devices with libvirt
-        and remember to add <literal>uinput-*</literal> devices to the qemu
-        <literal>cgroup_device_acl</literal> list (see <xref linkend="opt-virtualisation.libvirtd.qemu.verbatimConfig"/>).
+        and remember to add `uinput-*` devices to the qemu
+        `cgroup_device_acl` list (see [](#opt-virtualisation.libvirtd.qemu.verbatimConfig)).
       '';
       example = lib.literalExpression ''
         {
diff --git a/nixpkgs/nixos/modules/services/misc/pinnwand.nix b/nixpkgs/nixos/modules/services/misc/pinnwand.nix
index 4eda25b4eb88..5fca9f4125a8 100644
--- a/nixpkgs/nixos/modules/services/misc/pinnwand.nix
+++ b/nixpkgs/nixos/modules/services/misc/pinnwand.nix
@@ -10,7 +10,7 @@ let
 in
 {
   options.services.pinnwand = {
-    enable = mkEnableOption "Pinnwand";
+    enable = mkEnableOption (lib.mdDoc "Pinnwand");
 
     port = mkOption {
       type = types.port;
@@ -19,29 +19,66 @@ in
     };
 
     settings = mkOption {
-      type = format.type;
+      default = {};
       description = lib.mdDoc ''
         Your {file}`pinnwand.toml` as a Nix attribute set. Look up
-        possible options in the [pinnwand.toml-example](https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example).
+        possible options in the [documentation](https://pinnwand.readthedocs.io/en/v${pkgs.pinnwand.version}/configuration.html).
       '';
-      default = {};
+      type = types.submodule {
+        freeformType = format.type;
+        options = {
+          database_uri = mkOption {
+            type = types.str;
+            default = "sqlite:////var/lib/pinnwand/pinnwand.db";
+            example = "sqlite:///:memory";
+            description = lib.mdDoc ''
+              Database URI compatible with [SQLAlchemyhttps://docs.sqlalchemy.org/en/14/core/engines.html#database-urls].
+
+              Additional packages may need to be introduced into the environment for certain databases.
+            '';
+          };
+
+          paste_size = mkOption {
+            type = types.ints.positive;
+            default = 262144;
+            example = 524288;
+            description = lib.mdDoc ''
+              Maximum size of a paste in bytes.
+            '';
+          };
+          paste_help = mkOption {
+            type = types.str;
+            default = ''
+              <p>Welcome to pinnwand, this site is a pastebin. It allows you to share code with others. If you write code in the text area below and press the paste button you will be given a link you can share with others so they can view your code as well.</p><p>People with the link can view your pasted code, only you can remove your paste and it expires automatically. Note that anyone could guess the URI to your paste so don't rely on it being private.</p>
+              '';
+            description = lib.mdDoc ''
+              Raw HTML help text shown in the header area.
+            '';
+          };
+          footer = mkOption {
+            type = types.str;
+            default = ''
+              View <a href="//github.com/supakeen/pinnwand" target="_BLANK">source code</a>, the <a href="/removal">removal</a> or <a href="/expiry">expiry</a> stories, or read the <a href="/about">about</a> page.
+            '';
+            description = lib.mdDoc ''
+              The footer in raw HTML.
+            '';
+          };
+        };
+      };
     };
   };
 
   config = mkIf cfg.enable {
-    services.pinnwand.settings = {
-      database_uri = mkDefault "sqlite:////var/lib/pinnwand/pinnwand.db";
-      paste_size = mkDefault 262144;
-      paste_help = mkDefault ''
-        <p>Welcome to pinnwand, this site is a pastebin. It allows you to share code with others. If you write code in the text area below and press the paste button you will be given a link you can share with others so they can view your code as well.</p><p>People with the link can view your pasted code, only you can remove your paste and it expires automatically. Note that anyone could guess the URI to your paste so don't rely on it being private.</p>
-      '';
-      footer = mkDefault ''
-        View <a href="//github.com/supakeen/pinnwand" target="_BLANK">source code</a>, the <a href="/removal">removal</a> or <a href="/expiry">expiry</a> stories, or read the <a href="/about">about</a> page.
-      '';
-    };
+    systemd.services.pinnwand = {
+      description = "Pinnwannd HTTP Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      unitConfig.Documentation = "https://pinnwand.readthedocs.io/en/latest/";
 
-    systemd.services = let
-      hardeningOptions = {
+      serviceConfig = {
+        ExecStart = "${pkgs.pinnwand}/bin/pinnwand --configuration-path ${configFile} http --port ${toString cfg.port}";
         User = "pinnwand";
         DynamicUser = true;
 
@@ -72,32 +109,14 @@ in
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = "@system-service";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
         UMask = "0077";
       };
-
-      command = "${pkgs.pinnwand}/bin/pinnwand --configuration-path ${configFile}";
-    in {
-      pinnwand = {
-        description = "Pinnwannd HTTP Server";
-        after = [ "network.target" ];
-        wantedBy = [ "multi-user.target" ];
-
-        unitConfig.Documentation = "https://pinnwand.readthedocs.io/en/latest/";
-
-        serviceConfig = {
-          ExecStart = "${command} http --port ${toString(cfg.port)}";
-        } // hardeningOptions;
-      };
-
-      pinnwand-reaper = {
-        description = "Pinnwand Reaper";
-        startAt = "daily";
-
-        serviceConfig = {
-          ExecStart = "${command} -vvvv reap";  # verbosity increased to show number of deleted pastes
-        } // hardeningOptions;
-      };
     };
   };
+
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixpkgs/nixos/modules/services/misc/plex.nix b/nixpkgs/nixos/modules/services/misc/plex.nix
index cb41bbb54b27..7fc76028c02a 100644
--- a/nixpkgs/nixos/modules/services/misc/plex.nix
+++ b/nixpkgs/nixos/modules/services/misc/plex.nix
@@ -12,7 +12,7 @@ in
 
   options = {
     services.plex = {
-      enable = mkEnableOption "Plex Media Server";
+      enable = mkEnableOption (lib.mdDoc "Plex Media Server");
 
       dataDir = mkOption {
         type = types.str;
@@ -134,6 +134,7 @@ in
 
         ExecStart = "${cfg.package}/bin/plexmediaserver";
         KillSignal = "SIGQUIT";
+        PIDFile = "${cfg.dataDir}/Plex Media Server/plexmediaserver.pid";
         Restart = "on-failure";
       };
 
diff --git a/nixpkgs/nixos/modules/services/misc/plikd.nix b/nixpkgs/nixos/modules/services/misc/plikd.nix
index 9ae9e064fd54..9b0825bf40c9 100644
--- a/nixpkgs/nixos/modules/services/misc/plikd.nix
+++ b/nixpkgs/nixos/modules/services/misc/plikd.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.plikd = {
-      enable = mkEnableOption "the plikd server";
+      enable = mkEnableOption (lib.mdDoc "the plikd server");
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/misc/podgrab.nix b/nixpkgs/nixos/modules/services/misc/podgrab.nix
index 590309ace7ec..c596122fd31c 100644
--- a/nixpkgs/nixos/modules/services/misc/podgrab.nix
+++ b/nixpkgs/nixos/modules/services/misc/podgrab.nix
@@ -4,7 +4,7 @@ let
 in
 {
   options.services.podgrab = with lib; {
-    enable = mkEnableOption "Podgrab, a self-hosted podcast manager";
+    enable = mkEnableOption (lib.mdDoc "Podgrab, a self-hosted podcast manager");
 
     passwordFile = mkOption {
       type = with types; nullOr str;
@@ -12,7 +12,7 @@ in
       example = "/run/secrets/password.env";
       description = lib.mdDoc ''
         The path to a file containing the PASSWORD environment variable
-        definition for Podgrab's authentification.
+        definition for Podgrab's authentication.
       '';
     };
 
@@ -36,7 +36,7 @@ in
       };
       serviceConfig = {
         DynamicUser = true;
-        EnvironmentFile = lib.optional (cfg.passwordFile != null) [
+        EnvironmentFile = lib.optionals (cfg.passwordFile != null) [
           cfg.passwordFile
         ];
         ExecStart = "${pkgs.podgrab}/bin/podgrab";
diff --git a/nixpkgs/nixos/modules/services/misc/polaris.nix b/nixpkgs/nixos/modules/services/misc/polaris.nix
index b5f7f17e664c..70f097f02840 100644
--- a/nixpkgs/nixos/modules/services/misc/polaris.nix
+++ b/nixpkgs/nixos/modules/services/misc/polaris.nix
@@ -11,9 +11,9 @@ in
 {
   options = {
     services.polaris = {
-      enable = mkEnableOption "Polaris Music Server";
+      enable = mkEnableOption (lib.mdDoc "Polaris Music Server");
 
-      package = mkPackageOption pkgs "polaris" { };
+      package = mkPackageOptionMD pkgs "polaris" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/portunus.nix b/nixpkgs/nixos/modules/services/misc/portunus.nix
index a2247272fa26..d18881986970 100644
--- a/nixpkgs/nixos/modules/services/misc/portunus.nix
+++ b/nixpkgs/nixos/modules/services/misc/portunus.nix
@@ -8,18 +8,18 @@ let
 in
 {
   options.services.portunus = {
-    enable = mkEnableOption "Portunus, a self-contained user/group management and authentication service for LDAP";
+    enable = mkEnableOption (lib.mdDoc "Portunus, a self-contained user/group management and authentication service for LDAP");
 
     domain = mkOption {
       type = types.str;
       example = "sso.example.com";
-      description = "Subdomain which gets reverse proxied to Portunus webserver.";
+      description = lib.mdDoc "Subdomain which gets reverse proxied to Portunus webserver.";
     };
 
     port = mkOption {
       type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         Port where the Portunus webserver should listen on.
 
         This must be put behind a TLS-capable reverse proxy because Portunus only listens on localhost.
@@ -29,56 +29,56 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.portunus;
-      defaultText = "pkgs.portunus";
-      description = "The Portunus package to use.";
+      defaultText = lib.literalExpression "pkgs.portunus";
+      description = lib.mdDoc "The Portunus package to use.";
     };
 
     seedPath = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to a portunus seed file in json format.
-        See <link xlink:href="https://github.com/majewsky/portunus#seeding-users-and-groups-from-static-configuration"/> for available options.
+        See <https://github.com/majewsky/portunus#seeding-users-and-groups-from-static-configuration> for available options.
       '';
     };
 
     stateDir = mkOption {
       type = types.path;
       default = "/var/lib/portunus";
-      description = "Path where Portunus stores its state.";
+      description = lib.mdDoc "Path where Portunus stores its state.";
     };
 
     user = mkOption {
       type = types.str;
       default = "portunus";
-      description = "User account under which Portunus runs its webserver.";
+      description = lib.mdDoc "User account under which Portunus runs its webserver.";
     };
 
     group = mkOption {
       type = types.str;
       default = "portunus";
-      description = "Group account under which Portunus runs its webserver.";
+      description = lib.mdDoc "Group account under which Portunus runs its webserver.";
     };
 
     dex = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         Dex ldap connector.
 
         To activate dex, first a search user must be created in the Portunus web ui
-        and then the password must to be set as the <literal>DEX_SEARCH_USER_PASSWORD</literal> environment variable
-        in the <xref linkend="opt-services.dex.environmentFile"/> setting.
-      '';
+        and then the password must to be set as the `DEX_SEARCH_USER_PASSWORD` environment variable
+        in the [](#opt-services.dex.environmentFile) setting.
+      '');
 
       oidcClients = mkOption {
         type = types.listOf (types.submodule {
           options = {
             callbackURL = mkOption {
               type = types.str;
-              description = "URL where the OIDC client should redirect";
+              description = lib.mdDoc "URL where the OIDC client should redirect";
             };
             id = mkOption {
               type = types.str;
-              description = "ID of the OIDC client";
+              description = lib.mdDoc "ID of the OIDC client";
             };
           };
         });
@@ -89,34 +89,35 @@ in
             id = "service";
           }
         ];
-        description = ''
+        description = lib.mdDoc ''
           List of OIDC clients.
 
-          The OIDC secret must be set as the <literal>DEX_CLIENT_''${id}</literal> environment variable
-          in the <xref linkend="opt-services.dex.environmentFile"/> setting.
+          The OIDC secret must be set as the `DEX_CLIENT_''${id}` environment variable
+          in the [](#opt-services.dex.environmentFile) setting.
         '';
       };
 
       port = mkOption {
         type = types.port;
         default = 5556;
-        description = "Port where dex should listen on.";
+        description = lib.mdDoc "Port where dex should listen on.";
       };
     };
 
     ldap = {
       package = mkOption {
         type = types.package;
-        default = pkgs.openldap;
-        defaultText = "pkgs.openldap";
-        description = "The OpenLDAP package to use.";
+        # needs openldap built with a libxcrypt that support crypt sha256 until https://github.com/majewsky/portunus/issues/2 is solved
+        default = pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; };
+        defaultText = lib.literalExpression "pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; }";
+        description = lib.mdDoc "The OpenLDAP package to use.";
       };
 
       searchUserName = mkOption {
         type = types.str;
         default = "";
         example = "admin";
-        description = ''
+        description = lib.mdDoc ''
           The login name of the search user.
           This user account must be configured in Portunus either manually or via seeding.
         '';
@@ -125,7 +126,7 @@ in
       suffix = mkOption {
         type = types.str;
         example = "dc=example,dc=org";
-        description = ''
+        description = lib.mdDoc ''
           The DN of the topmost entry in your LDAP directory.
           Please refer to the Portunus documentation for more information on how this impacts the structure of the LDAP directory.
         '';
@@ -134,25 +135,25 @@ in
       tls = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Wether to enable LDAPS protocol.
-          This also adds two entries to the <literal>/etc/hosts</literal> file to point <xref linkend="opt-services.portunus.domain"/> to localhost,
+        description = lib.mdDoc ''
+          Whether to enable LDAPS protocol.
+          This also adds two entries to the `/etc/hosts` file to point [](#opt-services.portunus.domain) to localhost,
           so that CLIs and programs can use ldaps protocol and verify the certificate without opening the firewall port for the protocol.
 
-          This requires a TLS certificate for <xref linkend="opt-services.portunus.domain"/> to be configured via <xref linkend="opt-security.acme.certs"/>.
+          This requires a TLS certificate for [](#opt-services.portunus.domain) to be configured via [](#opt-security.acme.certs).
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "openldap";
-        description = "User account under which Portunus runs its LDAP server.";
+        description = lib.mdDoc "User account under which Portunus runs its LDAP server.";
       };
 
       group = mkOption {
         type = types.str;
         default = "openldap";
-        description = "Group account under which Portunus runs its LDAP server.";
+        description = lib.mdDoc "Group account under which Portunus runs its LDAP server.";
       };
     };
   };
@@ -212,9 +213,9 @@ in
 
         staticClients = forEach cfg.dex.oidcClients (client: {
           inherit (client) id;
-          redirectURIs = [ client.callbackURI ];
+          redirectURIs = [ client.callbackURL ];
           name = "OIDC for ${client.id}";
-          secret = "$DEX_CLIENT_${client.id}";
+          secretEnv = "DEX_CLIENT_${client.id}";
         });
       };
     };
@@ -238,7 +239,7 @@ in
           PORTUNUS_SERVER_BINARY = "${cfg.package}/bin/portunus-server";
           PORTUNUS_SERVER_GROUP = cfg.group;
           PORTUNUS_SERVER_USER = cfg.user;
-          PORTUNUS_SERVER_HTTP_LISTEN = "[::]:${toString cfg.port}";
+          PORTUNUS_SERVER_HTTP_LISTEN = "127.0.0.1:${toString cfg.port}";
           PORTUNUS_SERVER_STATE_DIR = cfg.stateDir;
           PORTUNUS_SLAPD_BINARY = "${cfg.ldap.package}/libexec/slapd";
           PORTUNUS_SLAPD_GROUP = cfg.ldap.group;
@@ -284,5 +285,5 @@ in
     ];
   };
 
-  meta.maintainers = [ majewsky ] ++ teams.c3d2.members;
+  meta.maintainers = [ maintainers.majewsky ] ++ teams.c3d2.members;
 }
diff --git a/nixpkgs/nixos/modules/services/misc/prowlarr.nix b/nixpkgs/nixos/modules/services/misc/prowlarr.nix
index 6152ee4a7697..77b8ec989479 100644
--- a/nixpkgs/nixos/modules/services/misc/prowlarr.nix
+++ b/nixpkgs/nixos/modules/services/misc/prowlarr.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.prowlarr = {
-      enable = mkEnableOption "Prowlarr";
+      enable = mkEnableOption (lib.mdDoc "Prowlarr");
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/misc/pufferpanel.nix b/nixpkgs/nixos/modules/services/misc/pufferpanel.nix
new file mode 100644
index 000000000000..2022406c8325
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/pufferpanel.nix
@@ -0,0 +1,176 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.pufferpanel;
+in
+{
+  options.services.pufferpanel = {
+    enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to enable PufferPanel game management server.
+
+        Note that [PufferPanel templates] and binaries downloaded by PufferPanel
+        expect [FHS environment]. It is possible to set {option}`package` option
+        to use PufferPanel wrapper with FHS environment. For example, to use
+        `Download Game from Steam` and `Download Java` template operations:
+        ```Nix
+        { lib, pkgs, ... }: {
+          services.pufferpanel = {
+            enable = true;
+            extraPackages = with pkgs; [ bash curl gawk gnutar gzip ];
+            package = pkgs.buildFHSEnv {
+              name = "pufferpanel-fhs";
+              runScript = lib.getExe pkgs.pufferpanel;
+              targetPkgs = pkgs': with pkgs'; [ icu openssl zlib ];
+            };
+          };
+        }
+        ```
+
+        [PufferPanel templates]: https://github.com/PufferPanel/templates
+        [FHS environment]: https://wikipedia.org/wiki/Filesystem_Hierarchy_Standard
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs "pufferpanel" { };
+
+    extraGroups = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [ "podman" ];
+      description = lib.mdDoc ''
+        Additional groups for the systemd service.
+      '';
+    };
+
+    extraPackages = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
+      default = [ ];
+      example = lib.literalExpression "[ pkgs.jre ]";
+      description = lib.mdDoc ''
+        Packages to add to the PATH environment variable. Both the {file}`bin`
+        and {file}`sbin` subdirectories of each package are added.
+      '';
+    };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      default = { };
+      example = lib.literalExpression ''
+        {
+          PUFFER_WEB_HOST = ":8080";
+          PUFFER_DAEMON_SFTP_HOST = ":5657";
+          PUFFER_DAEMON_CONSOLE_BUFFER = "1000";
+          PUFFER_DAEMON_CONSOLE_FORWARD = "true";
+          PUFFER_PANEL_REGISTRATIONENABLED = "false";
+        }
+      '';
+      description = lib.mdDoc ''
+        Environment variables to set for the service. Secrets should be
+        specified using {option}`environmentFile`.
+
+        Refer to the [PufferPanel source code][] for the list of available
+        configuration options. Variable name is an upper-cased configuration
+        entry name with underscores instead of dots, prefixed with `PUFFER_`.
+        For example, `panel.settings.companyName` entry can be set using
+        {env}`PUFFER_PANEL_SETTINGS_COMPANYNAME`.
+
+        When running with panel enabled (configured with `PUFFER_PANEL_ENABLE`
+        environment variable), it is recommended disable registration using
+        `PUFFER_PANEL_REGISTRATIONENABLED` environment variable (registration is
+        enabled by default). To create the initial administrator user, run
+        {command}`pufferpanel --workDir /var/lib/pufferpanel user add --admin`.
+
+        Some options override corresponding settings set via web interface (e.g.
+        `PUFFER_PANEL_REGISTRATIONENABLED`). Those options can be temporarily
+        toggled or set in settings but do not persist between restarts.
+
+        [PufferPanel source code]: https://github.com/PufferPanel/PufferPanel/blob/master/config/entries.go
+      '';
+    };
+
+    environmentFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        File to load environment variables from. Loaded variables override
+        values set in {option}`environment`.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.pufferpanel = {
+      description = "PufferPanel game management server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = cfg.extraPackages;
+      environment = cfg.environment;
+
+      # Note that we export environment variables for service directories if the
+      # value is not set. An empty environment variable is considered to be set.
+      # E.g.
+      #   export PUFFER_LOGS=${PUFFER_LOGS-$LOGS_DIRECTORY}
+      # would set PUFFER_LOGS to $LOGS_DIRECTORY if PUFFER_LOGS environment
+      # variable is not defined.
+      script = ''
+        ${lib.concatLines (lib.mapAttrsToList (name: value: ''
+          export ${name}="''${${name}-${value}}"
+        '') {
+          PUFFER_LOGS = "$LOGS_DIRECTORY";
+          PUFFER_DAEMON_DATA_CACHE = "$CACHE_DIRECTORY";
+          PUFFER_DAEMON_DATA_SERVERS = "$STATE_DIRECTORY/servers";
+          PUFFER_DAEMON_DATA_BINARIES = "$STATE_DIRECTORY/binaries";
+        })}
+        exec ${lib.getExe cfg.package} run --workDir "$STATE_DIRECTORY"
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+
+        UMask = "0077";
+
+        SupplementaryGroups = cfg.extraGroups;
+
+        StateDirectory = "pufferpanel";
+        StateDirectoryMode = "0700";
+        CacheDirectory = "pufferpanel";
+        CacheDirectoryMode = "0700";
+        LogsDirectory = "pufferpanel";
+        LogsDirectoryMode = "0700";
+
+        EnvironmentFile = cfg.environmentFile;
+
+        # Command "pufferpanel shutdown --pid $MAINPID" sends SIGTERM (code 15)
+        # to the main process and waits for termination. This is essentially
+        # KillMode=mixed we are using here. See
+        # https://freedesktop.org/software/systemd/man/systemd.kill.html#KillMode=
+        KillMode = "mixed";
+
+        DynamicUser = true;
+        ProtectHome = true;
+        ProtectProc = "invisible";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        PrivateUsers = true;
+        PrivateDevices = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = [ "user" "mnt" ]; # allow buildFHSEnv
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        LockPersonality = true;
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        CapabilityBoundingSet = [ "" ];
+      };
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.tie ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/pykms.nix b/nixpkgs/nixos/modules/services/misc/pykms.nix
index d24cd1bfa05b..be3accc0d7e5 100644
--- a/nixpkgs/nixos/modules/services/misc/pykms.nix
+++ b/nixpkgs/nixos/modules/services/misc/pykms.nix
@@ -28,7 +28,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 1688;
         description = lib.mdDoc "The port on which to listen.";
       };
@@ -85,7 +85,7 @@ in
         WorkingDirectory = libDir;
         SyslogIdentifier = "pykms";
         Restart = "on-failure";
-        MemoryLimit = cfg.memoryLimit;
+        MemoryMax = cfg.memoryLimit;
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/misc/radarr.nix b/nixpkgs/nixos/modules/services/misc/radarr.nix
index a2d7b734f709..834b092c0d14 100644
--- a/nixpkgs/nixos/modules/services/misc/radarr.nix
+++ b/nixpkgs/nixos/modules/services/misc/radarr.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.radarr = {
-      enable = mkEnableOption "Radarr";
+      enable = mkEnableOption (lib.mdDoc "Radarr");
 
       package = mkOption {
         description = lib.mdDoc "Radarr package to use";
diff --git a/nixpkgs/nixos/modules/services/misc/readarr.nix b/nixpkgs/nixos/modules/services/misc/readarr.nix
new file mode 100644
index 000000000000..dd4fef6e598d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/readarr.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.readarr;
+in
+{
+  options = {
+    services.readarr = {
+      enable = mkEnableOption (lib.mdDoc "Readarr");
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/readarr/";
+        description = lib.mdDoc "The directory where Readarr stores its data files.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.readarr;
+        defaultText = literalExpression "pkgs.readarr";
+        description = lib.mdDoc "The Readarr package to use";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open ports in the firewall for Readarr
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "readarr";
+        description = lib.mdDoc ''
+          User account under which Readarr runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "readarr";
+        description = lib.mdDoc ''
+          Group under which Readarr runs.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.readarr = {
+      description = "Readarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Readarr -nobrowser -data='${cfg.dataDir}'";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8787 ];
+    };
+
+    users.users = mkIf (cfg.user == "readarr") {
+      readarr = {
+        description = "Readarr service";
+        home = cfg.dataDir;
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "readarr") {
+      readarr = { };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/redmine.nix b/nixpkgs/nixos/modules/services/misc/redmine.nix
index 13b62f435579..a296fd3816bb 100644
--- a/nixpkgs/nixos/modules/services/misc/redmine.nix
+++ b/nixpkgs/nixos/modules/services/misc/redmine.nix
@@ -49,7 +49,7 @@ in
   # interface
   options = {
     services.redmine = {
-      enable = mkEnableOption "Redmine";
+      enable = mkEnableOption (lib.mdDoc "Redmine");
 
       package = mkOption {
         type = types.package;
@@ -161,7 +161,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = if cfg.database.type == "postgresql" then 5432 else 3306;
           defaultText = literalExpression "3306";
           description = lib.mdDoc "Database host port.";
@@ -206,6 +206,57 @@ in
           description = lib.mdDoc "Create the database and database user locally.";
         };
       };
+
+      components = {
+        subversion = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "Subversion integration.";
+        };
+
+        mercurial = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "Mercurial integration.";
+        };
+
+        git = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "git integration.";
+        };
+
+        cvs = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "cvs integration.";
+        };
+
+        breezy = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "bazaar integration.";
+        };
+
+        imagemagick = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "Allows exporting Gant diagrams as PNG.";
+        };
+
+        ghostscript = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc "Allows exporting Gant diagrams as PDF.";
+        };
+
+        minimagick_font_path = mkOption {
+          type = types.str;
+          default = "";
+          description = lib.mdDoc "MiniMagick font path";
+          example = "/run/current-system/sw/share/X11/fonts/LiberationSans-Regular.ttf";
+        };
+      };
     };
   };
 
@@ -225,16 +276,21 @@ in
       { assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
         message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
       }
+      { assertion = cfg.components.imagemagick -> cfg.components.minimagick_font_path != "";
+        message = "services.redmine.components.minimagick_font_path must be configured with a path to a font file if services.redmine.components.imagemagick is set to true.";
+      }
     ];
 
     services.redmine.settings = {
       production = {
-        scm_subversion_command = "${pkgs.subversion}/bin/svn";
-        scm_mercurial_command = "${pkgs.mercurial}/bin/hg";
-        scm_git_command = "${pkgs.git}/bin/git";
-        scm_cvs_command = "${pkgs.cvs}/bin/cvs";
-        scm_bazaar_command = "${pkgs.breezy}/bin/bzr";
-        scm_darcs_command = "${pkgs.darcs}/bin/darcs";
+        scm_subversion_command = optionalString cfg.components.subversion "${pkgs.subversion}/bin/svn";
+        scm_mercurial_command = optionalString cfg.components.mercurial "${pkgs.mercurial}/bin/hg";
+        scm_git_command = optionalString cfg.components.git "${pkgs.git}/bin/git";
+        scm_cvs_command = optionalString cfg.components.cvs "${pkgs.cvs}/bin/cvs";
+        scm_bazaar_command = optionalString cfg.components.breezy "${pkgs.breezy}/bin/bzr";
+        imagemagick_convert_command = optionalString cfg.components.imagemagick "${pkgs.imagemagick}/bin/convert";
+        gs_command = optionalString cfg.components.ghostscript "${pkgs.ghostscript}/bin/gs";
+        minimagick_font_path = "${cfg.components.minimagick_font_path}";
       };
     };
 
@@ -296,14 +352,15 @@ in
       environment.REDMINE_LANG = "en";
       environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
       path = with pkgs; [
-        imagemagick
-        breezy
-        cvs
-        darcs
-        git
-        mercurial
-        subversion
-      ];
+      ]
+      ++ optional cfg.components.subversion subversion
+      ++ optional cfg.components.mercurial mercurial
+      ++ optional cfg.components.git git
+      ++ optional cfg.components.cvs cvs
+      ++ optional cfg.components.breezy breezy
+      ++ optional cfg.components.imagemagick imagemagick
+      ++ optional cfg.components.ghostscript ghostscript;
+
       preStart = ''
         rm -rf "${cfg.stateDir}/plugins/"*
         rm -rf "${cfg.stateDir}/public/themes/"*
@@ -362,7 +419,7 @@ in
         Group = cfg.group;
         TimeoutSec = "300";
         WorkingDirectory = "${cfg.package}/share/redmine";
-        ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
+        ExecStart="${bundle} exec rails server -u webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
       };
 
     };
diff --git a/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
index 7d8a4cb2b44c..30623a321338 100644
--- a/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
+++ b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
@@ -35,12 +35,12 @@ let
 in {
   options = {
     services.rippleDataApi = {
-      enable = mkEnableOption "ripple data api";
+      enable = mkEnableOption (lib.mdDoc "ripple data api");
 
       port = mkOption {
         description = lib.mdDoc "Ripple data api port";
         default = 5993;
-        type = types.int;
+        type = types.port;
       };
 
       importMode = mkOption {
@@ -77,7 +77,7 @@ in {
         port = mkOption {
           description = lib.mdDoc "Ripple data api redis port.";
           default = 5984;
-          type = types.int;
+          type = types.port;
         };
       };
 
@@ -91,7 +91,7 @@ in {
         port = mkOption {
           description = lib.mdDoc "Ripple data api couchdb port.";
           default = 5984;
-          type = types.int;
+          type = types.port;
         };
 
         db = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/rippled.nix b/nixpkgs/nixos/modules/services/misc/rippled.nix
index 8b6704c1be77..d14b6421b742 100644
--- a/nixpkgs/nixos/modules/services/misc/rippled.nix
+++ b/nixpkgs/nixos/modules/services/misc/rippled.nix
@@ -98,7 +98,7 @@ let
 
       port = mkOption {
         description = lib.mdDoc "Port where rippled listens.";
-        type = types.int;
+        type = types.port;
       };
 
       protocol = mkOption {
@@ -207,7 +207,7 @@ in
 
   options = {
     services.rippled = {
-      enable = mkEnableOption "rippled";
+      enable = mkEnableOption (lib.mdDoc "rippled");
 
       package = mkOption {
         description = lib.mdDoc "Which rippled package to use.";
@@ -375,7 +375,7 @@ in
       };
 
       statsd = {
-        enable = mkEnableOption "statsd monitoring for rippled";
+        enable = mkEnableOption (lib.mdDoc "statsd monitoring for rippled");
 
         address = mkOption {
           description = lib.mdDoc "The UDP address and port of the listening StatsD server.";
@@ -401,7 +401,7 @@ in
       config = mkOption {
         internal = true;
         default = pkgs.writeText "rippled.conf" rippledCfg;
-        defaultText = literalDocBook "generated config file";
+        defaultText = literalMD "generated config file";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/misc/rmfakecloud.nix b/nixpkgs/nixos/modules/services/misc/rmfakecloud.nix
index 2feb663f7cb6..1cdfdeceabcd 100644
--- a/nixpkgs/nixos/modules/services/misc/rmfakecloud.nix
+++ b/nixpkgs/nixos/modules/services/misc/rmfakecloud.nix
@@ -9,7 +9,7 @@ let
 in {
   options = {
     services.rmfakecloud = {
-      enable = mkEnableOption "rmfakecloud remarkable self-hosted cloud";
+      enable = mkEnableOption (lib.mdDoc "rmfakecloud remarkable self-hosted cloud");
 
       package = mkOption {
         type = types.package;
@@ -50,7 +50,7 @@ in {
         type = with types; attrsOf str;
         default = { };
         example = { DATADIR = "/custom/path/for/rmfakecloud/data"; };
-        description = ''
+        description = lib.mdDoc ''
           Extra settings in the form of a set of key-value pairs.
           For tokens and secrets, use `environmentFile` instead.
 
@@ -138,7 +138,7 @@ in {
         SystemCallArchitectures = "native";
         WorkingDirectory = serviceDataDir;
         StateDirectory = baseNameOf serviceDataDir;
-        UMask = 0027;
+        UMask = "0027";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/misc/rshim.nix b/nixpkgs/nixos/modules/services/misc/rshim.nix
new file mode 100644
index 000000000000..0fef2cc228c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/rshim.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.rshim;
+
+  rshimCommand = [ "${cfg.package}/bin/rshim" ]
+    ++ lib.optionals (cfg.backend != null) [ "--backend ${cfg.backend}" ]
+    ++ lib.optionals (cfg.device != null) [ "--device ${cfg.device}" ]
+    ++ lib.optionals (cfg.index != null) [ "--index ${builtins.toString cfg.index}" ]
+    ++ [ "--log-level ${builtins.toString cfg.log-level}" ]
+  ;
+in
+{
+  options.services.rshim = {
+    enable = lib.mkEnableOption (lib.mdDoc "User-space rshim driver for the BlueField SoC");
+
+    package = lib.mkPackageOptionMD pkgs "rshim-user-space" { };
+
+    backend = lib.mkOption {
+      type = with lib.types; nullOr (enum [ "usb" "pcie" "pcie_lf" ]);
+      description = lib.mdDoc ''
+        Specify the backend to attach. If not specified, the driver will scan
+        all rshim backends unless the `device` option is given with a device
+        name specified.
+      '';
+      default = null;
+      example = "pcie";
+    };
+
+    device = lib.mkOption {
+      type = with lib.types; nullOr str;
+      description = lib.mdDoc ''
+        Specify the device name to attach. The backend driver can be deduced
+        from the device name, thus the `backend` option is not needed.
+      '';
+      default = null;
+      example = "pcie-04:00.2";
+    };
+
+    index = lib.mkOption {
+      type = with lib.types; nullOr int;
+      description = lib.mdDoc ''
+        Specify the index to create device path `/dev/rshim<index>`. It's also
+        used to create network interface name `tmfifo_net<index>`. This option
+        is needed when multiple rshim instances are running.
+      '';
+      default = null;
+      example = 1;
+    };
+
+    log-level = lib.mkOption {
+      type = lib.types.int;
+      description = lib.mdDoc ''
+        Specify the log level (0:none, 1:error, 2:warning, 3:notice, 4:debug).
+      '';
+      default = 2;
+      example = 4;
+    };
+
+    config = lib.mkOption {
+      type = with lib.types; attrsOf (oneOf [ int str ]);
+      description = lib.mdDoc ''
+        Structural setting for the rshim configuration file
+        (`/etc/rshim.conf`). It can be used to specify the static mapping
+        between rshim devices and rshim names. It can also be used to ignore
+        some rshim devices.
+      '';
+      default = { };
+      example = {
+        DISPLAY_LEVEL = 0;
+        rshim0 = "usb-2-1.7";
+        none = "usb-1-1.4";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.etc = lib.mkIf (cfg.config != { }) {
+      "rshim.conf".text = lib.generators.toKeyValue
+        { mkKeyValue = lib.generators.mkKeyValueDefault { } " "; }
+        cfg.config;
+    };
+
+    systemd.services.rshim = {
+      after = [ "network.target" ];
+      serviceConfig = {
+        Restart = "always";
+        Type = "forking";
+        ExecStart = [
+          (lib.concatStringsSep " \\\n" rshimCommand)
+        ];
+        KillMode = "control-group";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/safeeyes.nix b/nixpkgs/nixos/modules/services/misc/safeeyes.nix
index 638218d8bb00..9dfa2001bcb7 100644
--- a/nixpkgs/nixos/modules/services/misc/safeeyes.nix
+++ b/nixpkgs/nixos/modules/services/misc/safeeyes.nix
@@ -16,7 +16,7 @@ in
 
     services.safeeyes = {
 
-      enable = mkEnableOption "the safeeyes OSGi service";
+      enable = mkEnableOption (lib.mdDoc "the safeeyes OSGi service");
 
     };
 
@@ -34,8 +34,6 @@ in
       wantedBy = [ "graphical-session.target" ];
       partOf   = [ "graphical-session.target" ];
 
-      path = [ pkgs.alsa-utils ];
-
       startLimitIntervalSec = 350;
       startLimitBurst = 10;
       serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/misc/sdrplay.nix b/nixpkgs/nixos/modules/services/misc/sdrplay.nix
index 2801108f0828..2d5333e3885b 100644
--- a/nixpkgs/nixos/modules/services/misc/sdrplay.nix
+++ b/nixpkgs/nixos/modules/services/misc/sdrplay.nix
@@ -5,13 +5,13 @@ with lib;
     enable = mkOption {
       default = false;
       example = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the SDRplay API service and udev rules.
 
-        <note><para>
-          To enable integration with SoapySDR and GUI applications like gqrx create an overlay containing
-          <literal>soapysdr-with-plugins = super.soapysdr.override { extraPackages = [ super.soapysdrplay ]; };</literal>
-        </para></note>
+        ::: {.note}
+        To enable integration with SoapySDR and GUI applications like gqrx create an overlay containing
+        `soapysdr-with-plugins = super.soapysdr.override { extraPackages = [ super.soapysdrplay ]; };`
+        :::
       '';
       type = lib.types.bool;
     };
diff --git a/nixpkgs/nixos/modules/services/misc/serviio.nix b/nixpkgs/nixos/modules/services/misc/serviio.nix
index 57efebb2c03f..18e64030d79d 100644
--- a/nixpkgs/nixos/modules/services/misc/serviio.nix
+++ b/nixpkgs/nixos/modules/services/misc/serviio.nix
@@ -80,7 +80,7 @@ in {
         23424 # mediabrowser
       ];
       allowedUDPPorts = [
-        1900 # UPnP service discovey
+        1900 # UPnP service discovery
       ];
     };
   };
diff --git a/nixpkgs/nixos/modules/services/misc/signald.nix b/nixpkgs/nixos/modules/services/misc/signald.nix
index 8a1d2c4ad388..32ba154506ce 100644
--- a/nixpkgs/nixos/modules/services/misc/signald.nix
+++ b/nixpkgs/nixos/modules/services/misc/signald.nix
@@ -8,7 +8,7 @@ let
 in
 {
   options.services.signald = {
-    enable = mkEnableOption "the signald service";
+    enable = mkEnableOption (lib.mdDoc "the signald service");
 
     user = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/siproxd.nix b/nixpkgs/nixos/modules/services/misc/siproxd.nix
index f1a1ed4d29b3..3890962b7cfb 100644
--- a/nixpkgs/nixos/modules/services/misc/siproxd.nix
+++ b/nixpkgs/nixos/modules/services/misc/siproxd.nix
@@ -20,7 +20,7 @@ let
     ${optionalString (cfg.hostsAllowReg != []) "hosts_allow_reg = ${concatStringsSep "," cfg.hostsAllowReg}"}
     ${optionalString (cfg.hostsAllowSip != []) "hosts_allow_sip = ${concatStringsSep "," cfg.hostsAllowSip}"}
     ${optionalString (cfg.hostsDenySip != []) "hosts_deny_sip  = ${concatStringsSep "," cfg.hostsDenySip}"}
-    ${if (cfg.passwordFile != "") then "proxy_auth_pwfile = ${cfg.passwordFile}" else ""}
+    ${optionalString (cfg.passwordFile != "") "proxy_auth_pwfile = ${cfg.passwordFile}"}
     ${cfg.extraConfig}
   '';
 
@@ -60,7 +60,7 @@ in
         default = [ ];
         example = [ "192.168.1.0/24" "192.168.2.0/24" ];
         description = lib.mdDoc ''
-          Acess control list for incoming SIP registrations.
+          Access control list for incoming SIP registrations.
         '';
       };
 
@@ -69,7 +69,7 @@ in
         default = [ ];
         example = [ "123.45.0.0/16" "123.46.0.0/16" ];
         description = lib.mdDoc ''
-          Acess control list for incoming SIP traffic.
+          Access control list for incoming SIP traffic.
         '';
       };
 
@@ -78,7 +78,7 @@ in
         default = [ ];
         example = [ "10.0.0.0/8" "11.0.0.0/8" ];
         description = lib.mdDoc ''
-          Acess control list for denying incoming
+          Access control list for denying incoming
           SIP registrations and traffic.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/misc/snapper.nix b/nixpkgs/nixos/modules/services/misc/snapper.nix
index cfdfa2830ce9..569433c3c71d 100644
--- a/nixpkgs/nixos/modules/services/misc/snapper.nix
+++ b/nixpkgs/nixos/modules/services/misc/snapper.nix
@@ -4,6 +4,81 @@ with lib;
 
 let
   cfg = config.services.snapper;
+
+  mkValue = v:
+    if isList v then "\"${concatMapStringsSep " " (escape [ "\\" " " ]) v}\""
+    else if v == true then "yes"
+    else if v == false then "no"
+    else if isString v then "\"${v}\""
+    else builtins.toJSON v;
+
+  mkKeyValue = k: v: "${k}=${mkValue v}";
+
+  # "it's recommended to always specify the filesystem type"  -- man snapper-configs
+  defaultOf = k: if k == "FSTYPE" then null else configOptions.${k}.default or null;
+
+  safeStr = types.strMatching "[^\n\"]*" // {
+    description = "string without line breaks or quotes";
+    descriptionClass = "conjunction";
+  };
+
+  configOptions = {
+    SUBVOLUME = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path of the subvolume or mount point.
+        This path is a subvolume and has to contain a subvolume named
+        .snapshots.
+        See also man:snapper(8) section PERMISSIONS.
+      '';
+    };
+
+    FSTYPE = mkOption {
+      type = types.enum [ "btrfs" ];
+      default = "btrfs";
+      description = lib.mdDoc ''
+        Filesystem type. Only btrfs is stable and tested.
+      '';
+    };
+
+    ALLOW_GROUPS = mkOption {
+      type = types.listOf safeStr;
+      default = [];
+      description = lib.mdDoc ''
+        List of groups allowed to operate with the config.
+
+        Also see the PERMISSIONS section in man:snapper(8).
+      '';
+    };
+
+    ALLOW_USERS = mkOption {
+      type = types.listOf safeStr;
+      default = [];
+      example = [ "alice" ];
+      description = lib.mdDoc ''
+        List of users allowed to operate with the config. "root" is always
+        implicitly included.
+
+        Also see the PERMISSIONS section in man:snapper(8).
+      '';
+    };
+
+    TIMELINE_CLEANUP = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Defines whether the timeline cleanup algorithm should be run for the config.
+      '';
+    };
+
+    TIMELINE_CREATE = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Defines whether hourly snapshots should be created.
+      '';
+    };
+  };
 in
 
 {
@@ -52,49 +127,23 @@ in
       example = literalExpression ''
         {
           home = {
-            subvolume = "/home";
-            extraConfig = '''
-              ALLOW_USERS="alice"
-              TIMELINE_CREATE=yes
-              TIMELINE_CLEANUP=yes
-            ''';
+            SUBVOLUME = "/home";
+            ALLOW_USERS = [ "alice" ];
+            TIMELINE_CREATE = true;
+            TIMELINE_CLEANUP = true;
           };
         }
       '';
 
       description = lib.mdDoc ''
-        Subvolume configuration
+        Subvolume configuration. Any option mentioned in man:snapper-configs(5)
+        is valid here, even if NixOS doesn't document it.
       '';
 
       type = types.attrsOf (types.submodule {
-        options = {
-          subvolume = mkOption {
-            type = types.path;
-            description = lib.mdDoc ''
-              Path of the subvolume or mount point.
-              This path is a subvolume and has to contain a subvolume named
-              .snapshots.
-              See also man:snapper(8) section PERMISSIONS.
-            '';
-          };
-
-          fstype = mkOption {
-            type = types.enum [ "btrfs" ];
-            default = "btrfs";
-            description = lib.mdDoc ''
-              Filesystem type. Only btrfs is stable and tested.
-            '';
-          };
+        freeformType = types.attrsOf (types.oneOf [ (types.listOf safeStr) types.bool safeStr types.number ]);
 
-          extraConfig = mkOption {
-            type = types.lines;
-            default = "";
-            description = lib.mdDoc ''
-              Additional configuration next to SUBVOLUME and FSTYPE.
-              See man:snapper-configs(5).
-            '';
-          };
-        };
+        options = configOptions;
       });
     };
   };
@@ -117,11 +166,7 @@ in
 
       }
       // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
-        text = ''
-          ${subvolume.extraConfig}
-          FSTYPE="${subvolume.fstype}"
-          SUBVOLUME="${subvolume.subvolume}"
-        '';
+        text = lib.generators.toKeyValue { inherit mkKeyValue; } (filterAttrs (k: v: v != defaultOf k) subvolume);
       })) cfg.configs)
       // (lib.optionalAttrs (cfg.filters != null) {
         "snapper/filters/default.txt".text = cfg.filters;
@@ -175,11 +220,34 @@ in
       description = "Take snapper snapshot of root on boot";
       inherit documentation;
       serviceConfig.ExecStart = "${pkgs.snapper}/bin/snapper --config root create --cleanup-algorithm number --description boot";
-      serviceConfig.type = "oneshot";
+      serviceConfig.Type = "oneshot";
       requires = [ "local-fs.target" ];
       wantedBy = [ "multi-user.target" ];
       unitConfig.ConditionPathExists = "/etc/snapper/configs/root";
     };
 
+    assertions =
+      concatMap
+        (name:
+          let
+            sub = cfg.configs.${name};
+          in
+          [ { assertion = !(sub ? extraConfig);
+              message = ''
+                The option definition `services.snapper.configs.${name}.extraConfig' no longer has any effect; please remove it.
+                The contents of this option should be migrated to attributes on `services.snapper.configs.${name}'.
+              '';
+            }
+          ] ++
+          map
+            (attr: {
+              assertion = !(hasAttr attr sub);
+              message = ''
+                The option definition `services.snapper.configs.${name}.${attr}' has been renamed to `services.snapper.configs.${name}.${toUpper attr}'.
+              '';
+            })
+            [ "fstype" "subvolume" ]
+        )
+        (attrNames cfg.configs);
   });
 }
diff --git a/nixpkgs/nixos/modules/services/misc/sonarr.nix b/nixpkgs/nixos/modules/services/misc/sonarr.nix
index a956a14d0072..65c51d9677d9 100644
--- a/nixpkgs/nixos/modules/services/misc/sonarr.nix
+++ b/nixpkgs/nixos/modules/services/misc/sonarr.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.sonarr = {
-      enable = mkEnableOption "Sonarr";
+      enable = mkEnableOption (lib.mdDoc "Sonarr");
 
       dataDir = mkOption {
         type = types.str;
@@ -35,6 +35,15 @@ in
         default = "sonarr";
         description = lib.mdDoc "Group under which Sonaar runs.";
       };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.sonarr;
+        defaultText = literalExpression "pkgs.sonarr";
+        description = lib.mdDoc ''
+          Sonarr package to use.
+        '';
+      };
     };
   };
 
@@ -52,7 +61,7 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${pkgs.sonarr}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
+        ExecStart = "${cfg.package}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/default.md b/nixpkgs/nixos/modules/services/misc/sourcehut/default.md
new file mode 100644
index 000000000000..44d58aa0bef3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/default.md
@@ -0,0 +1,93 @@
+# Sourcehut {#module-services-sourcehut}
+
+[Sourcehut](https://sr.ht.com/) is an open-source,
+self-hostable software development platform. The server setup can be automated using
+[services.sourcehut](#opt-services.sourcehut.enable).
+
+## Basic usage {#module-services-sourcehut-basic-usage}
+
+Sourcehut is a Python and Go based set of applications.
+This NixOS module also provides basic configuration integrating Sourcehut into locally running
+`services.nginx`, `services.redis.servers.sourcehut`, `services.postfix`
+and `services.postgresql` services.
+
+A very basic configuration may look like this:
+```
+{ pkgs, ... }:
+let
+  fqdn =
+    let
+      join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
+    in join config.networking.hostName config.networking.domain;
+in {
+
+  networking = {
+    hostName = "srht";
+    domain = "tld";
+    firewall.allowedTCPPorts = [ 22 80 443 ];
+  };
+
+  services.sourcehut = {
+    enable = true;
+    git.enable = true;
+    man.enable = true;
+    meta.enable = true;
+    nginx.enable = true;
+    postfix.enable = true;
+    postgresql.enable = true;
+    redis.enable = true;
+    settings = {
+        "sr.ht" = {
+          environment = "production";
+          global-domain = fqdn;
+          origin = "https://${fqdn}";
+          # Produce keys with srht-keygen from sourcehut.coresrht.
+          network-key = "/run/keys/path/to/network-key";
+          service-key = "/run/keys/path/to/service-key";
+        };
+        webhooks.private-key= "/run/keys/path/to/webhook-key";
+    };
+  };
+
+  security.acme.certs."${fqdn}".extraDomainNames = [
+    "meta.${fqdn}"
+    "man.${fqdn}"
+    "git.${fqdn}"
+  ];
+
+  services.nginx = {
+    enable = true;
+    # only recommendedProxySettings are strictly required, but the rest make sense as well.
+    recommendedTlsSettings = true;
+    recommendedOptimisation = true;
+    recommendedGzipSettings = true;
+    recommendedProxySettings = true;
+
+    # Settings to setup what certificates are used for which endpoint.
+    virtualHosts = {
+      "${fqdn}".enableACME = true;
+      "meta.${fqdn}".useACMEHost = fqdn:
+      "man.${fqdn}".useACMEHost = fqdn:
+      "git.${fqdn}".useACMEHost = fqdn:
+    };
+  };
+}
+```
+
+  The `hostName` option is used internally to configure the nginx
+reverse-proxy. The `settings` attribute set is
+used by the configuration generator and the result is placed in `/etc/sr.ht/config.ini`.
+
+## Configuration {#module-services-sourcehut-configuration}
+
+All configuration parameters are also stored in
+`/etc/sr.ht/config.ini` which is generated by
+the module and linked from the store to ensure that all values from `config.ini`
+can be modified by the module.
+
+## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-sourcehut-httpd}
+
+By default, `nginx` is used as reverse-proxy for `sourcehut`.
+However, it's possible to use e.g. `httpd` by explicitly disabling
+`nginx` using [](#opt-services.nginx.enable) and fixing the
+`settings`.
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix
index 113c53f7395d..d4391bc49e31 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix
@@ -67,7 +67,7 @@ let
       type = types.str;
       default = "postgresql:///localhost?user=${srv}srht&host=/run/postgresql";
     };
-    migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade" // { default = true; };
+    migrate-on-upgrade = mkEnableOption (lib.mdDoc "automatic migrations on package upgrade") // { default = true; };
     oauth-client-id = mkOption {
       description = lib.mdDoc "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
       type = types.str;
@@ -88,7 +88,6 @@ let
     # Sourcehut services
     srht
     buildsrht
-    dispatchsrht
     gitsrht
     hgsrht
     hubsrht
@@ -101,21 +100,21 @@ let
     todosrht
   ]);
   mkOptionNullOrStr = description: mkOption {
-    inherit description;
+    description = lib.mdDoc description;
     type = with types; nullOr str;
     default = null;
   };
 in
 {
   options.services.sourcehut = {
-    enable = mkEnableOption ''
-      sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
-      task dispatching, wiki and account management services
-    '';
+    enable = mkEnableOption (lib.mdDoc ''
+      sourcehut - git hosting, continuous integration, mailing list, ticket tracking, wiki
+      and account management services
+    '');
 
     services = mkOption {
       type = with types; listOf (enum
-        [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
+        [ "builds" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
       defaultText = "locally enabled services";
       description = lib.mdDoc ''
         Services that may be displayed as links in the title bar of the Web interface.
@@ -132,18 +131,18 @@ in
       internal = true;
       type = types.package;
       default = python;
-      description = ''
+      description = lib.mdDoc ''
         The python package to use. It should contain references to the *srht modules and also
         gunicorn.
       '';
     };
 
     minio = {
-      enable = mkEnableOption ''local minio integration'';
+      enable = mkEnableOption (lib.mdDoc ''local minio integration'');
     };
 
     nginx = {
-      enable = mkEnableOption ''local nginx integration'';
+      enable = mkEnableOption (lib.mdDoc ''local nginx integration'');
       virtualHost = mkOption {
         type = types.attrs;
         default = {};
@@ -152,15 +151,15 @@ in
     };
 
     postfix = {
-      enable = mkEnableOption ''local postfix integration'';
+      enable = mkEnableOption (lib.mdDoc ''local postfix integration'');
     };
 
     postgresql = {
-      enable = mkEnableOption ''local postgresql integration'';
+      enable = mkEnableOption (lib.mdDoc ''local postgresql integration'');
     };
 
     redis = {
-      enable = mkEnableOption ''local redis integration in a dedicated redis-server'';
+      enable = mkEnableOption (lib.mdDoc ''local redis integration in a dedicated redis-server'');
     };
 
     settings = mkOption {
@@ -301,34 +300,8 @@ in
           };
         };
 
-        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 = lib.mdDoc "Canonical upstream.";
-            default = "gitlab.com";
-          };
-          repo-cache = mkOption {
-            type = types.str;
-            description = lib.mdDoc "Repository cache directory.";
-            default = "./repo-cache";
-          };
-          "gitlab.com" = mkOption {
-            type = with types; nullOr str;
-            description = lib.mdDoc "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";
+          allow-free = mkEnableOption (lib.mdDoc "nonpaying users to submit builds");
           redis = mkOption {
             description = lib.mdDoc "The Redis connection used for the Celery worker.";
             type = types.str;
@@ -465,7 +438,7 @@ in
         };
 
         options."lists.sr.ht" = commonServiceSettings "lists" // {
-          allow-new-lists = mkEnableOption "Allow creation of new lists.";
+          allow-new-lists = mkEnableOption (lib.mdDoc "Allow creation of new lists");
           notify-from = mkOption {
             description = lib.mdDoc "Outgoing email for notifications generated by users.";
             type = types.str;
@@ -532,14 +505,14 @@ in
             description = lib.mdDoc "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)}'';
+            defaultText = lib.literalMD ''`"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.meta.port)` + 100)}"`'';
           };
           webhooks = mkOption {
             description = lib.mdDoc "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";
+          welcome-emails = mkEnableOption (lib.mdDoc "sending stock sourcehut welcome emails after signup");
         };
         options."meta.sr.ht::api" = {
           internal-ipnet = mkOption {
@@ -560,7 +533,7 @@ in
           example = { "git.sr.ht" = 12345; };
         };
         options."meta.sr.ht::billing" = {
-          enabled = mkEnableOption "the billing system";
+          enabled = mkEnableOption (lib.mdDoc "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)
@@ -570,7 +543,7 @@ in
           };
         };
         options."meta.sr.ht::settings" = {
-          registration = mkEnableOption "public registration";
+          registration = mkEnableOption (lib.mdDoc "public registration");
           onboarding-redirect = mkOption {
             description = lib.mdDoc "Where to redirect new users upon registration.";
             type = types.str;
@@ -601,9 +574,9 @@ in
             default = 1024;
           };
           user-domain = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Configures the user domain, if enabled.
-              All users are given &lt;username&gt;.this.domain.
+              All users are given \<username\>.this.domain.
             '';
             type = with types; nullOr str;
             default = null;
@@ -668,17 +641,17 @@ in
     };
 
     builds = {
-      enableWorker = mkEnableOption ''
+      enableWorker = mkEnableOption (lib.mdDoc ''
         worker for builds.sr.ht
 
-        <warning><para>
+        ::: {.warning}
         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>
-      '';
+        See <https://man.sr.ht/builds.sr.ht/configuration.md#security-model>.
+        :::
+      '');
 
       images = mkOption {
         type = with types; attrsOf (attrsOf (attrsOf package));
@@ -1021,11 +994,6 @@ in
       ];
     })
 
-    (import ./service.nix "dispatch" {
-      inherit configIniOfService;
-      port = 5005;
-    })
-
     (import ./service.nix "git" (let
       baseService = {
         path = [ cfg.git.package ];
@@ -1416,8 +1384,12 @@ in
     (mkRenamedOptionModule [ "services" "sourcehut" "address" ]
                            [ "services" "sourcehut" "listenAddress" ])
 
+    (mkRemovedOptionModule [ "services" "sourcehut" "dispatch" ] ''
+        dispatch is deprecated. See https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/
+        for more information.
+    '')
   ];
 
-  meta.doc = ./sourcehut.xml;
-  meta.maintainers = with maintainers; [ julm tomberek ];
+  meta.doc = ./default.md;
+  meta.maintainers = with maintainers; [ tomberek ];
 }
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix
index 243a9cef5901..aae13e0cc2c9 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix
@@ -71,7 +71,7 @@ let
       # 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.
+        # Replace values beginning 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
@@ -117,7 +117,7 @@ let
 in
 {
   options.services.sourcehut.${srv} = {
-    enable = mkEnableOption "${srv} service";
+    enable = mkEnableOption (lib.mdDoc "${srv} service");
 
     user = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml b/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml
deleted file mode 100644
index 41094f65a94d..000000000000
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml
+++ /dev/null
@@ -1,119 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-sourcehut">
- <title>Sourcehut</title>
- <para>
-  <link xlink:href="https://sr.ht.com/">Sourcehut</link> is an open-source,
-  self-hostable software development platform. The server setup can be automated using
-  <link linkend="opt-services.sourcehut.enable">services.sourcehut</link>.
- </para>
-
- <section xml:id="module-services-sourcehut-basic-usage">
-  <title>Basic usage</title>
-  <para>
-   Sourcehut is a Python and Go based set of applications.
-   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.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> services.
-  </para>
-
-  <para>
-   A very basic configuration may look like this:
-<programlisting>
-{ pkgs, ... }:
-let
-  fqdn =
-    let
-      join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
-    in join config.networking.hostName config.networking.domain;
-in {
-
-  networking = {
-    <link linkend="opt-networking.hostName">hostName</link> = "srht";
-    <link linkend="opt-networking.domain">domain</link> = "tld";
-    <link linkend="opt-networking.firewall.allowedTCPPorts">firewall.allowedTCPPorts</link> = [ 22 80 443 ];
-  };
-
-  services.sourcehut = {
-    <link linkend="opt-services.sourcehut.enable">enable</link> = true;
-    <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 = "/run/keys/path/to/network-key";
-          service-key = "/run/keys/path/to/service-key";
-        };
-        webhooks.private-key= "/run/keys/path/to/webhook-key";
-    };
-  };
-
-  <link linkend="opt-security.acme.certs._name_.extraDomainNames">security.acme.certs."${fqdn}".extraDomainNames</link> = [
-    "meta.${fqdn}"
-    "man.${fqdn}"
-    "git.${fqdn}"
-  ];
-
-  services.nginx = {
-    <link linkend="opt-services.nginx.enable">enable</link> = true;
-    # only recommendedProxySettings are strictly required, but the rest make sense as well.
-    <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
-    <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
-    <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
-    <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
-
-    # Settings to setup what certificates are used for which endpoint.
-    <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
-      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">"${fqdn}".enableACME</link> = true;
-      <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">"meta.${fqdn}".useACMEHost</link> = fqdn:
-      <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">"man.${fqdn}".useACMEHost</link> = fqdn:
-      <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">"git.${fqdn}".useACMEHost</link> = fqdn:
-    };
-  };
-}
-</programlisting>
-  </para>
-
-  <para>
-   The <literal>hostName</literal> option is used internally to configure the nginx
-   reverse-proxy. The <literal>settings</literal> attribute set is
-   used by the configuration generator and the result is placed in <literal>/etc/sr.ht/config.ini</literal>.
-  </para>
- </section>
-
- <section xml:id="module-services-sourcehut-configuration">
-  <title>Configuration</title>
-
-  <para>
-   All configuration parameters are also stored in
-   <literal>/etc/sr.ht/config.ini</literal> which is generated by
-   the module and linked from the store to ensure that all values from <literal>config.ini</literal>
-   can be modified by the module.
-  </para>
-
- </section>
-
- <section xml:id="module-services-sourcehut-httpd">
-  <title>Using an alternative webserver as reverse-proxy (e.g. <literal>httpd</literal>)</title>
-  <para>
-   By default, <package>nginx</package> is used as reverse-proxy for <package>sourcehut</package>.
-   However, it's possible to use e.g. <package>httpd</package> by explicitly disabling
-   <package>nginx</package> using <xref linkend="opt-services.nginx.enable" /> and fixing the
-   <literal>settings</literal>.
-  </para>
-</section>
-
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
index 2dd9fcf68ab0..bde64847d89e 100644
--- a/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
+++ b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
@@ -7,7 +7,7 @@ in
 {
   options = {
     services.spice-vdagentd = {
-      enable = mkEnableOption "Spice guest vdagent daemon";
+      enable = mkEnableOption (lib.mdDoc "Spice guest vdagent daemon");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/misc/spice-webdavd.nix b/nixpkgs/nixos/modules/services/misc/spice-webdavd.nix
index bfb5b262ee1a..6c817e429ac6 100644
--- a/nixpkgs/nixos/modules/services/misc/spice-webdavd.nix
+++ b/nixpkgs/nixos/modules/services/misc/spice-webdavd.nix
@@ -7,13 +7,13 @@ in
 {
   options = {
     services.spice-webdavd = {
-      enable = mkEnableOption "the spice guest webdav proxy daemon";
+      enable = mkEnableOption (lib.mdDoc "the spice guest webdav proxy daemon");
 
       package = mkOption {
         default = pkgs.phodav;
         defaultText = literalExpression "pkgs.phodav";
         type = types.package;
-        description = "spice-webdavd provider package to use.";
+        description = lib.mdDoc "spice-webdavd provider package to use.";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/misc/ssm-agent.nix b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
index 5f2b47bae474..d1f371c2bd61 100644
--- a/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
+++ b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
@@ -17,7 +17,7 @@ let
   '';
 in {
   options.services.ssm-agent = {
-    enable = mkEnableOption "AWS SSM agent";
+    enable = mkEnableOption (lib.mdDoc "AWS SSM agent");
 
     package = mkOption {
       type = types.path;
diff --git a/nixpkgs/nixos/modules/services/misc/sssd.nix b/nixpkgs/nixos/modules/services/misc/sssd.nix
index 60d4a799d5d2..7c7a3b464a83 100644
--- a/nixpkgs/nixos/modules/services/misc/sssd.nix
+++ b/nixpkgs/nixos/modules/services/misc/sssd.nix
@@ -10,7 +10,7 @@ let
 in {
   options = {
     services.sssd = {
-      enable = mkEnableOption "the System Security Services Daemon";
+      enable = mkEnableOption (lib.mdDoc "the System Security Services Daemon");
 
       config = mkOption {
         type = types.lines;
@@ -54,31 +54,33 @@ in {
       environmentFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
-          Environment file as defined in <citerefentry>
-          <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
-          </citerefentry>.
+        description = lib.mdDoc ''
+          Environment file as defined in {manpage}`systemd.exec(5)`.
 
           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.
 
-          <programlisting>
+          ```
             # snippet of sssd-related config
             [domain/LDAP]
             ldap_default_authtok = $SSSD_LDAP_DEFAULT_AUTHTOK
-          </programlisting>
+          ```
 
-          <programlisting>
+          ```
             # contents of the environment file
             SSSD_LDAP_DEFAULT_AUTHTOK=verysecretpassword
-          </programlisting>
+          ```
         '';
       };
     };
   };
   config = mkMerge [
     (mkIf cfg.enable {
+      # For `sssctl` to work.
+      environment.etc."sssd/sssd.conf".source = settingsFile;
+      environment.etc."sssd/conf.d".source = "${dataDir}/conf.d";
+
       systemd.services.sssd = {
         description = "System Security Services Daemon";
         wantedBy    = [ "multi-user.target" ];
@@ -103,6 +105,7 @@ in {
           EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
         };
         preStart = ''
+          mkdir -p "${dataDir}/conf.d"
           [ -f ${settingsFile} ] && rm -f ${settingsFile}
           old_umask=$(umask)
           umask 0177
diff --git a/nixpkgs/nixos/modules/services/misc/subsonic.nix b/nixpkgs/nixos/modules/services/misc/subsonic.nix
index d657ae2b998a..0862d5782595 100644
--- a/nixpkgs/nixos/modules/services/misc/subsonic.nix
+++ b/nixpkgs/nixos/modules/services/misc/subsonic.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.subsonic = {
-      enable = mkEnableOption "Subsonic daemon";
+      enable = mkEnableOption (lib.mdDoc "Subsonic daemon");
 
       home = mkOption {
         type = types.path;
diff --git a/nixpkgs/nixos/modules/services/misc/sundtek.nix b/nixpkgs/nixos/modules/services/misc/sundtek.nix
index e3234518c940..e85d7c5b92b9 100644
--- a/nixpkgs/nixos/modules/services/misc/sundtek.nix
+++ b/nixpkgs/nixos/modules/services/misc/sundtek.nix
@@ -8,7 +8,7 @@ let
 in
 {
   options.services.sundtek = {
-    enable = mkEnableOption "Sundtek driver";
+    enable = mkEnableOption (lib.mdDoc "Sundtek driver");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/misc/synergy.nix b/nixpkgs/nixos/modules/services/misc/synergy.nix
index c02d80b35c69..0cbdc7599c0f 100644
--- a/nixpkgs/nixos/modules/services/misc/synergy.nix
+++ b/nixpkgs/nixos/modules/services/misc/synergy.nix
@@ -19,7 +19,7 @@ in
       # !!! All these option descriptions needs to be cleaned up.
 
       client = {
-        enable = mkEnableOption "the Synergy client (receive keyboard and mouse events from a Synergy server)";
+        enable = mkEnableOption (lib.mdDoc "the Synergy client (receive keyboard and mouse events from a Synergy server)");
 
         screenName = mkOption {
           default = "";
@@ -45,7 +45,7 @@ in
       };
 
       server = {
-        enable = mkEnableOption "the Synergy server (send keyboard and mouse events)";
+        enable = mkEnableOption (lib.mdDoc "the Synergy server (send keyboard and mouse events)");
 
         configFile = mkOption {
           type = types.path;
@@ -115,7 +115,7 @@ in
         description = "Synergy server";
         wantedBy = optional cfgS.autoStart "graphical-session.target";
         path = [ pkgs.synergy ];
-        serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergys -c ${cfgS.configFile} -f${optionalString (cfgS.address != "") " -a ${cfgS.address}"}${optionalString (cfgS.screenName != "") " -n ${cfgS.screenName}"}${optionalString cfgS.tls.enable " --enable-crypto"}${optionalString (cfgS.tls.cert != null) (" --tls-cert=${cfgS.tls.cert}")}'';
+        serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergys -c ${cfgS.configFile} -f${optionalString (cfgS.address != "") " -a ${cfgS.address}"}${optionalString (cfgS.screenName != "") " -n ${cfgS.screenName}"}${optionalString cfgS.tls.enable " --enable-crypto"}${optionalString (cfgS.tls.cert != null) (" --tls-cert ${cfgS.tls.cert}")}'';
         serviceConfig.Restart = "on-failure";
       };
     })
diff --git a/nixpkgs/nixos/modules/services/misc/sysprof.nix b/nixpkgs/nixos/modules/services/misc/sysprof.nix
index ab91a8b586a2..25c5b0fabf61 100644
--- a/nixpkgs/nixos/modules/services/misc/sysprof.nix
+++ b/nixpkgs/nixos/modules/services/misc/sysprof.nix
@@ -3,7 +3,7 @@
 {
   options = {
     services.sysprof = {
-      enable = lib.mkEnableOption "sysprof profiling daemon";
+      enable = lib.mkEnableOption (lib.mdDoc "sysprof profiling daemon");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/misc/tandoor-recipes.nix b/nixpkgs/nixos/modules/services/misc/tandoor-recipes.nix
new file mode 100644
index 000000000000..63d3e3d2a857
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tandoor-recipes.nix
@@ -0,0 +1,145 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.tandoor-recipes;
+  pkg = cfg.package;
+
+  # SECRET_KEY through an env file
+  env = {
+    GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
+    DEBUG = "0";
+    DEBUG_TOOLBAR = "0";
+    MEDIA_ROOT = "/var/lib/tandoor-recipes";
+  } // optionalAttrs (config.time.timeZone != null) {
+    TIMEZONE = config.time.timeZone;
+  } // (
+    lib.mapAttrs (_: toString) cfg.extraConfig
+  );
+
+  manage =
+    let
+      setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
+    in
+    pkgs.writeShellScript "manage" ''
+      ${setupEnv}
+      exec ${pkg}/bin/tandoor-recipes "$@"
+    '';
+in
+{
+  meta.maintainers = with maintainers; [ ambroisie ];
+
+  options.services.tandoor-recipes = {
+    enable = mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable Tandoor Recipes.
+
+        When started, the Tandoor Recipes database is automatically created if
+        it doesn't exist and updated if the package has changed. Both tasks are
+        achieved by running a Django migration.
+
+        A script to manage the instance (by wrapping Django's manage.py) is linked to
+        `/var/lib/tandoor-recipes/tandoor-recipes-manage`.
+      '';
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "Web interface address.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = lib.mdDoc "Web interface port.";
+    };
+
+    extraConfig = mkOption {
+      type = types.attrs;
+      default = { };
+      description = lib.mdDoc ''
+        Extra tandoor recipes config options.
+
+        See [the example dot-env file](https://raw.githubusercontent.com/vabene1111/recipes/master/.env.template)
+        for available options.
+      '';
+      example = {
+        ENABLE_SIGNUP = "1";
+      };
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.tandoor-recipes;
+      defaultText = literalExpression "pkgs.tandoor-recipes";
+      description = lib.mdDoc "The Tandoor Recipes package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.tandoor-recipes = {
+      description = "Tandoor Recipes server";
+
+      serviceConfig = {
+        ExecStart = ''
+          ${pkg.python.pkgs.gunicorn}/bin/gunicorn recipes.wsgi
+        '';
+        Restart = "on-failure";
+
+        User = "tandoor_recipes";
+        DynamicUser = true;
+        StateDirectory = "tandoor-recipes";
+        WorkingDirectory = "/var/lib/tandoor-recipes";
+        RuntimeDirectory = "tandoor-recipes";
+
+        BindReadOnlyPaths = [
+          "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt"
+          builtins.storeDir
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/hosts"
+          "-/etc/localtime"
+          "-/run/postgresql"
+        ];
+        CapabilityBoundingSet = "";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        # gunicorn needs setuid
+        SystemCallFilter = [ "@system-service" "~@privileged" "@resources" "@setuid" "@keyring" ];
+        UMask = "0066";
+      } // lib.optionalAttrs (cfg.port < 1024) {
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+      };
+
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        ln -sf ${manage} tandoor-recipes-manage
+
+        # Let django migrate the DB as needed
+        ${pkg}/bin/tandoor-recipes migrate
+      '';
+
+      environment = env // {
+        PYTHONPATH = "${pkg.python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/tandoor-recipes";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/default.md b/nixpkgs/nixos/modules/services/misc/taskserver/default.md
new file mode 100644
index 000000000000..ee3b3908e2ae
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/default.md
@@ -0,0 +1,93 @@
+# Taskserver {#module-services-taskserver}
+
+Taskserver is the server component of
+[Taskwarrior](https://taskwarrior.org/), a free and
+open source todo list application.
+
+*Upstream documentation:* <https://taskwarrior.org/docs/#taskd>
+
+## Configuration {#module-services-taskserver-configuration}
+
+Taskserver does all of its authentication via TLS using client certificates,
+so you either need to roll your own CA or purchase a certificate from a
+known CA, which allows creation of client certificates. These certificates
+are usually advertised as "server certificates".
+
+So in order to make it easier to handle your own CA, there is a helper tool
+called {command}`nixos-taskserver` which manages the custom CA along
+with Taskserver organisations, users and groups.
+
+While the client certificates in Taskserver only authenticate whether a user
+is allowed to connect, every user has its own UUID which identifies it as an
+entity.
+
+With {command}`nixos-taskserver` the client certificate is created
+along with the UUID of the user, so it handles all of the credentials needed
+in order to setup the Taskwarrior client to work with a Taskserver.
+
+## The nixos-taskserver tool {#module-services-taskserver-nixos-taskserver-tool}
+
+Because Taskserver by default only provides scripts to setup users
+imperatively, the {command}`nixos-taskserver` tool is used for
+addition and deletion of organisations along with users and groups defined
+by [](#opt-services.taskserver.organisations) and as well for
+imperative set up.
+
+The tool is designed to not interfere if the command is used to manually set
+up some organisations, users or groups.
+
+For example if you add a new organisation using {command}`nixos-taskserver
+org add foo`, the organisation is not modified and deleted no
+matter what you define in
+{option}`services.taskserver.organisations`, even if you're adding
+the same organisation in that option.
+
+The tool is modelled to imitate the official {command}`taskd`
+command, documentation for each subcommand can be shown by using the
+{option}`--help` switch.
+
+## Declarative/automatic CA management {#module-services-taskserver-declarative-ca-management}
+
+Everything is done according to what you specify in the module options,
+however in order to set up a Taskwarrior client for synchronisation with a
+Taskserver instance, you have to transfer the keys and certificates to the
+client machine.
+
+This is done using {command}`nixos-taskserver user export $orgname
+$username` which is printing a shell script fragment to stdout
+which can either be used verbatim or adjusted to import the user on the
+client machine.
+
+For example, let's say you have the following configuration:
+```ShellSession
+{
+  services.taskserver.enable = true;
+  services.taskserver.fqdn = "server";
+  services.taskserver.listenHost = "::";
+  services.taskserver.organisations.my-company.users = [ "alice" ];
+}
+```
+This creates an organisation called `my-company` with the
+user `alice`.
+
+Now in order to import the `alice` user to another machine
+`alicebox`, all we need to do is something like this:
+```ShellSession
+$ ssh server nixos-taskserver user export my-company alice | sh
+```
+Of course, if no SSH daemon is available on the server you can also copy
+&amp; paste it directly into a shell.
+
+After this step the user should be set up and you can start synchronising
+your tasks for the first time with {command}`task sync init` on
+`alicebox`.
+
+Subsequent synchronisation requests merely require the command {command}`task
+sync` after that stage.
+
+## Manual CA management {#module-services-taskserver-manual-ca-management}
+
+If you set any options within
+[service.taskserver.pki.manual](#opt-services.taskserver.pki.manual.ca.cert).*,
+{command}`nixos-taskserver` won't issue certificates, but you can
+still use it for adding or removing user accounts.
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/default.nix b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
index ad4ab93a872a..775b3b6d2eae 100644
--- a/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
@@ -10,10 +10,12 @@ let
   mkManualPkiOption = desc: mkOption {
     type = types.nullOr types.path;
     default = null;
-    description = desc + ''
-      <note><para>
+    description = lib.mdDoc ''
+      ${desc}
+
+      ::: {.note}
       Setting this option will prevent automatic CA creation and handling.
-      </para></note>
+      :::
     '';
   };
 
@@ -35,13 +37,13 @@ let
     '';
   };
 
-  mkAutoDesc = preamble: ''
+  mkAutoDesc = preamble: lib.mdDoc ''
     ${preamble}
 
-    <note><para>
+    ::: {.note}
     This option is for the automatically handled CA and will be ignored if any
-    of the <option>services.taskserver.pki.manual.*</option> options are set.
-    </para></note>
+    of the {option}`services.taskserver.pki.manual.*` options are set.
+    :::
   '';
 
   mkExpireOption = desc: mkOption {
@@ -50,7 +52,7 @@ let
     example = 365;
     apply = val: if val == null then -1 else val;
     description = mkAutoDesc ''
-      The expiration time of ${desc} in days or <literal>null</literal> for no
+      The expiration time of ${desc} in days or `null` for no
       expiration time.
     '';
   };
@@ -140,11 +142,11 @@ in {
         default = false;
         description = let
           url = "https://nixos.org/manual/nixos/stable/index.html#module-services-taskserver";
-        in ''
+        in lib.mdDoc ''
           Whether to enable the Taskwarrior server.
 
-          More instructions about NixOS in conjuction with Taskserver can be
-          found <link xlink:href="${url}">in the NixOS manual</link>.
+          More instructions about NixOS in conjunction with Taskserver can be
+          found [in the NixOS manual](${url}).
         '';
       };
 
@@ -172,9 +174,9 @@ in {
         example = "NORMAL:-VERS-SSL3.0";
         description = let
           url = "https://gnutls.org/manual/html_node/Priority-Strings.html";
-        in ''
+        in lib.mdDoc ''
           List of GnuTLS ciphers to use. See the GnuTLS documentation about
-          priority strings at <link xlink:href="${url}"/> for full details.
+          priority strings at <${url}> for full details.
         '';
       };
 
@@ -249,7 +251,7 @@ in {
           client id (such as `task 2.3.0`).
 
           The values `all` or `none` have
-          special meaning. Overidden by any entry in the option
+          special meaning. Overridden by any entry in the option
           {option}`services.taskserver.disallowedClientIDs`.
         '';
       };
@@ -564,5 +566,5 @@ in {
     })
   ];
 
-  meta.doc = ./doc.xml;
+  meta.doc = ./default.md;
 }
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
deleted file mode 100644
index f6ead7c37857..000000000000
--- a/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
+++ /dev/null
@@ -1,135 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-    xmlns:xlink="http://www.w3.org/1999/xlink"
-    version="5.0"
-    xml:id="module-services-taskserver">
- <title>Taskserver</title>
- <para>
-  Taskserver is the server component of
-  <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
-  open source todo list application.
- </para>
- <para>
-  <emphasis>Upstream documentation:</emphasis>
-  <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
- </para>
- <section xml:id="module-services-taskserver-configuration">
-  <title>Configuration</title>
-
-  <para>
-   Taskserver does all of its authentication via TLS using client certificates,
-   so you either need to roll your own CA or purchase a certificate from a
-   known CA, which allows creation of client certificates. These certificates
-   are usually advertised as <quote>server certificates</quote>.
-  </para>
-
-  <para>
-   So in order to make it easier to handle your own CA, there is a helper tool
-   called <command>nixos-taskserver</command> which manages the custom CA along
-   with Taskserver organisations, users and groups.
-  </para>
-
-  <para>
-   While the client certificates in Taskserver only authenticate whether a user
-   is allowed to connect, every user has its own UUID which identifies it as an
-   entity.
-  </para>
-
-  <para>
-   With <command>nixos-taskserver</command> the client certificate is created
-   along with the UUID of the user, so it handles all of the credentials needed
-   in order to setup the Taskwarrior client to work with a Taskserver.
-  </para>
- </section>
- <section xml:id="module-services-taskserver-nixos-taskserver-tool">
-  <title>The nixos-taskserver tool</title>
-
-  <para>
-   Because Taskserver by default only provides scripts to setup users
-   imperatively, the <command>nixos-taskserver</command> tool is used for
-   addition and deletion of organisations along with users and groups defined
-   by <xref linkend="opt-services.taskserver.organisations"/> and as well for
-   imperative set up.
-  </para>
-
-  <para>
-   The tool is designed to not interfere if the command is used to manually set
-   up some organisations, users or groups.
-  </para>
-
-  <para>
-   For example if you add a new organisation using <command>nixos-taskserver
-   org add foo</command>, the organisation is not modified and deleted no
-   matter what you define in
-   <option>services.taskserver.organisations</option>, even if you're adding
-   the same organisation in that option.
-  </para>
-
-  <para>
-   The tool is modelled to imitate the official <command>taskd</command>
-   command, documentation for each subcommand can be shown by using the
-   <option>--help</option> switch.
-  </para>
- </section>
- <section xml:id="module-services-taskserver-declarative-ca-management">
-  <title>Declarative/automatic CA management</title>
-
-  <para>
-   Everything is done according to what you specify in the module options,
-   however in order to set up a Taskwarrior client for synchronisation with a
-   Taskserver instance, you have to transfer the keys and certificates to the
-   client machine.
-  </para>
-
-  <para>
-   This is done using <command>nixos-taskserver user export $orgname
-   $username</command> which is printing a shell script fragment to stdout
-   which can either be used verbatim or adjusted to import the user on the
-   client machine.
-  </para>
-
-  <para>
-   For example, let's say you have the following configuration:
-<screen>
-{
-  <xref linkend="opt-services.taskserver.enable"/> = true;
-  <xref linkend="opt-services.taskserver.fqdn"/> = "server";
-  <xref linkend="opt-services.taskserver.listenHost"/> = "::";
-  <link linkend="opt-services.taskserver.organisations._name_.users">services.taskserver.organisations.my-company.users</link> = [ "alice" ];
-}
-</screen>
-   This creates an organisation called <literal>my-company</literal> with the
-   user <literal>alice</literal>.
-  </para>
-
-  <para>
-   Now in order to import the <literal>alice</literal> user to another machine
-   <literal>alicebox</literal>, all we need to do is something like this:
-<screen>
-<prompt>$ </prompt>ssh server nixos-taskserver user export my-company alice | sh
-</screen>
-   Of course, if no SSH daemon is available on the server you can also copy
-   &amp; paste it directly into a shell.
-  </para>
-
-  <para>
-   After this step the user should be set up and you can start synchronising
-   your tasks for the first time with <command>task sync init</command> on
-   <literal>alicebox</literal>.
-  </para>
-
-  <para>
-   Subsequent synchronisation requests merely require the command <command>task
-   sync</command> after that stage.
-  </para>
- </section>
- <section xml:id="module-services-taskserver-manual-ca-management">
-  <title>Manual CA management</title>
-
-  <para>
-   If you set any options within
-   <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
-   <command>nixos-taskserver</command> won't issue certificates, but you can
-   still use it for adding or removing user accounts.
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/tautulli.nix b/nixpkgs/nixos/modules/services/misc/tautulli.nix
index 78f9429c9aa3..b29e9dc0c8d5 100644
--- a/nixpkgs/nixos/modules/services/misc/tautulli.nix
+++ b/nixpkgs/nixos/modules/services/misc/tautulli.nix
@@ -12,7 +12,7 @@ in
 
   options = {
     services.tautulli = {
-      enable = mkEnableOption "Tautulli Plex Monitor";
+      enable = mkEnableOption (lib.mdDoc "Tautulli Plex Monitor");
 
       dataDir = mkOption {
         type = types.str;
@@ -27,7 +27,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8181;
         description = lib.mdDoc "TCP port where Tautulli listens.";
       };
diff --git a/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix b/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
index 7052be23d76f..849f53ca2d48 100644
--- a/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
+++ b/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
@@ -14,7 +14,7 @@ in {
 
   options.services.tiddlywiki = {
 
-    enable = mkEnableOption "TiddlyWiki nodejs server";
+    enable = mkEnableOption (lib.mdDoc "TiddlyWiki nodejs server");
 
     listenOptions = mkOption {
       type = types.attrs;
diff --git a/nixpkgs/nixos/modules/services/misc/tp-auto-kbbl.nix b/nixpkgs/nixos/modules/services/misc/tp-auto-kbbl.nix
index 54dec0b3fea3..8d92d3d93677 100644
--- a/nixpkgs/nixos/modules/services/misc/tp-auto-kbbl.nix
+++ b/nixpkgs/nixos/modules/services/misc/tp-auto-kbbl.nix
@@ -9,7 +9,7 @@ in {
 
   options = {
     services.tp-auto-kbbl = {
-      enable = mkEnableOption "Auto toggle keyboard back-lighting on Thinkpads (and maybe other laptops) for Linux";
+      enable = mkEnableOption (lib.mdDoc "Auto toggle keyboard back-lighting on Thinkpads (and maybe other laptops) for Linux");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/uhub.nix b/nixpkgs/nixos/modules/services/misc/uhub.nix
index c3eda0db44d5..80266b024e35 100644
--- a/nixpkgs/nixos/modules/services/misc/uhub.nix
+++ b/nixpkgs/nixos/modules/services/misc/uhub.nix
@@ -19,7 +19,7 @@ in {
       type = types.attrsOf (types.submodule {
         options = {
 
-          enable = mkEnableOption "hub instance" // { default = true; };
+          enable = mkEnableOption (lib.mdDoc "hub instance") // { default = true; };
 
           enableTLS = mkOption {
             type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.md b/nixpkgs/nixos/modules/services/misc/weechat.md
new file mode 100644
index 000000000000..21f41be5b4a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/weechat.md
@@ -0,0 +1,46 @@
+# WeeChat {#module-services-weechat}
+
+[WeeChat](https://weechat.org/) is a fast and
+extensible IRC client.
+
+## Basic Usage {#module-services-weechat-basic-usage}
+
+By default, the module creates a
+[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/)
+unit which runs the chat client in a detached
+[`screen`](https://www.gnu.org/software/screen/)
+session.
+
+This can be done by enabling the `weechat` service:
+```
+{ ... }:
+
+{
+  services.weechat.enable = true;
+}
+```
+
+The service is managed by a dedicated user named `weechat`
+in the state directory `/var/lib/weechat`.
+
+## Re-attaching to WeeChat {#module-services-weechat-reattach}
+
+WeeChat runs in a screen session owned by a dedicated user. To explicitly
+allow your another user to attach to this session, the
+`screenrc` needs to be tweaked by adding
+[multiuser](https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser)
+support:
+```
+{
+  programs.screen.screenrc = ''
+    multiuser on
+    acladd normal_user
+  '';
+}
+```
+Now, the session can be re-attached like this:
+```
+screen -x weechat/weechat-screen
+```
+
+*The session name can be changed using [services.weechat.sessionName.](options.html#opt-services.weechat.sessionName)*
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.nix b/nixpkgs/nixos/modules/services/misc/weechat.nix
index b1de30ae2b82..338493e3cd37 100644
--- a/nixpkgs/nixos/modules/services/misc/weechat.nix
+++ b/nixpkgs/nixos/modules/services/misc/weechat.nix
@@ -8,14 +8,14 @@ in
 
 {
   options.services.weechat = {
-    enable = mkEnableOption "weechat";
+    enable = mkEnableOption (lib.mdDoc "weechat");
     root = mkOption {
       description = lib.mdDoc "Weechat state directory.";
       type = types.str;
       default = "/var/lib/weechat";
     };
     sessionName = mkOption {
-      description = lib.mdDoc "Name of the `screen' session for weechat.";
+      description = lib.mdDoc "Name of the `screen` session for weechat.";
       default = "weechat-screen";
       type = types.str;
     };
@@ -59,5 +59,5 @@ in
       };
   };
 
-  meta.doc = ./weechat.xml;
+  meta.doc = ./weechat.md;
 }
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.xml b/nixpkgs/nixos/modules/services/misc/weechat.xml
deleted file mode 100644
index 7255edfb9da3..000000000000
--- a/nixpkgs/nixos/modules/services/misc/weechat.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-weechat">
- <title>WeeChat</title>
- <para>
-  <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
-  extensible IRC client.
- </para>
- <section xml:id="module-services-weechat-basic-usage">
-  <title>Basic Usage</title>
-
-  <para>
-   By default, the module creates a
-   <literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal>
-   unit which runs the chat client in a detached
-   <literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal>
-   session.
-  </para>
-
-  <para>
-   This can be done by enabling the <literal>weechat</literal> service:
-<programlisting>
-{ ... }:
-
-{
-  <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true;
-}
-</programlisting>
-  </para>
-
-  <para>
-   The service is managed by a dedicated user named <literal>weechat</literal>
-   in the state directory <literal>/var/lib/weechat</literal>.
-  </para>
- </section>
- <section xml:id="module-services-weechat-reattach">
-  <title>Re-attaching to WeeChat</title>
-
-  <para>
-   WeeChat runs in a screen session owned by a dedicated user. To explicitly
-   allow your another user to attach to this session, the
-   <literal>screenrc</literal> needs to be tweaked by adding
-   <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link>
-   support:
-<programlisting>
-{
-  <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = ''
-    multiuser on
-    acladd normal_user
-  '';
-}
-</programlisting>
-   Now, the session can be re-attached like this:
-<programlisting>
-screen -x weechat/weechat-screen
-</programlisting>
-  </para>
-
-  <para>
-   <emphasis>The session name can be changed using
-   <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/xmr-stak.nix b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
index c218f747f281..6e123cf0380c 100644
--- a/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
+++ b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
@@ -15,9 +15,9 @@ in
 {
   options = {
     services.xmr-stak = {
-      enable = mkEnableOption "xmr-stak miner";
-      openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)";
-      cudaSupport = mkEnableOption "support for CUDA (NVidia graphics cards)";
+      enable = mkEnableOption (lib.mdDoc "xmr-stak miner");
+      openclSupport = mkEnableOption (lib.mdDoc "support for OpenCL (AMD/ATI graphics cards)");
+      cudaSupport = mkEnableOption (lib.mdDoc "support for CUDA (NVidia graphics cards)");
 
       extraArgs = mkOption {
         type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/misc/xmrig.nix b/nixpkgs/nixos/modules/services/misc/xmrig.nix
index a98b2292f559..d2aa3df45d53 100644
--- a/nixpkgs/nixos/modules/services/misc/xmrig.nix
+++ b/nixpkgs/nixos/modules/services/misc/xmrig.nix
@@ -13,7 +13,7 @@ with lib;
 {
   options = {
     services.xmrig = {
-      enable = mkEnableOption "XMRig Mining Software";
+      enable = mkEnableOption (lib.mdDoc "XMRig Mining Software");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/misc/zoneminder.nix b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
index ef3f6c1a0fd9..11722979851c 100644
--- a/nixpkgs/nixos/modules/services/misc/zoneminder.nix
+++ b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
@@ -66,7 +66,7 @@ let
 in {
   options = {
     services.zoneminder = with lib; {
-      enable = lib.mkEnableOption ''
+      enable = lib.mkEnableOption (lib.mdDoc ''
         ZoneMinder
 
         If you intend to run the database locally, you should set
@@ -75,12 +75,12 @@ in {
         and database user as well as populate the database yourself.
         Additionally, you will need to run `zmupdate.pl` yourself when
         upgrading to a newer version.
-      '';
+      '');
 
       webserver = mkOption {
         type = types.enum [ "nginx" "none" ];
         default = "nginx";
-        description = ''
+        description = lib.mdDoc ''
           The webserver to configure for the PHP frontend.
 
           Set it to `none` if you want to configure it yourself. PRs are welcome
@@ -97,7 +97,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8095;
         description = lib.mdDoc ''
           The port on which to listen.
@@ -283,7 +283,8 @@ in {
       phpfpm = lib.mkIf useNginx {
         pools.zoneminder = {
           inherit user group;
-          phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.apcu ]);
+          phpPackage = pkgs.php.withExtensions (
+            { enabled, all }: enabled ++ [ all.apcu all.sysvsem ]);
           phpOptions = ''
             date.timezone = "${config.time.timeZone}"
           '';
@@ -326,6 +327,15 @@ in {
           fi
 
           ${zoneminder}/bin/zmupdate.pl -nointeractive
+          ${zoneminder}/bin/zmupdate.pl --nointeractive -f
+
+          # Update ZM's Nix store path in the configuration table. Do nothing if the config doesn't
+          # contain ZM's Nix store path.
+          ${config.services.mysql.package}/bin/mysql -u zoneminder zm << EOF
+            UPDATE Config
+              SET Value = REGEXP_REPLACE(Value, "^/nix/store/[^-/]+-zoneminder-[^/]+", "${pkgs.zoneminder}")
+              WHERE Name = "ZM_FONT_FILE_LOCATION";
+          EOF
         '';
         serviceConfig = {
           User = user;
diff --git a/nixpkgs/nixos/modules/services/misc/zookeeper.nix b/nixpkgs/nixos/modules/services/misc/zookeeper.nix
index 17d4a00f28f1..fb51be698e72 100644
--- a/nixpkgs/nixos/modules/services/misc/zookeeper.nix
+++ b/nixpkgs/nixos/modules/services/misc/zookeeper.nix
@@ -24,16 +24,12 @@ let
 in {
 
   options.services.zookeeper = {
-    enable = mkOption {
-      description = lib.mdDoc "Whether to enable Zookeeper.";
-      default = false;
-      type = types.bool;
-    };
+    enable = mkEnableOption (lib.mdDoc "Zookeeper");
 
     port = mkOption {
       description = lib.mdDoc "Zookeeper Client port.";
       default = 2181;
-      type = types.int;
+      type = types.port;
     };
 
     id = mkOption {
diff --git a/nixpkgs/nixos/modules/services/monitoring/alerta.nix b/nixpkgs/nixos/modules/services/monitoring/alerta.nix
index c0caa0dc3beb..6c7ebec4191c 100644
--- a/nixpkgs/nixos/modules/services/monitoring/alerta.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/alerta.nix
@@ -21,10 +21,10 @@ let
 in
 {
   options.services.alerta = {
-    enable = mkEnableOption "alerta";
+    enable = mkEnableOption (lib.mdDoc "alerta");
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 5000;
       description = lib.mdDoc "Port of Alerta";
     };
diff --git a/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix b/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
index d4216b44cdc8..666479c78a84 100644
--- a/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
@@ -62,6 +62,21 @@ let
 
   );
 
+  # Ensure the CLI uses our generated configFile
+  wrappedBinaries = pkgs.runCommandLocal "apcupsd-wrapped-binaries"
+    { nativeBuildInputs = [ pkgs.makeWrapper ]; }
+    ''
+      for p in "${lib.getBin pkgs.apcupsd}/bin/"*; do
+          bname=$(basename "$p")
+          makeWrapper "$p" "$out/bin/$bname" --add-flags "-f ${configFile}"
+      done
+    '';
+
+  apcupsdWrapped = pkgs.symlinkJoin {
+    name = "apcupsd-wrapped";
+    # Put wrappers first so they "win"
+    paths = [ wrappedBinaries pkgs.apcupsd ];
+  };
 in
 
 {
@@ -138,7 +153,7 @@ in
     } ];
 
     # Give users access to the "apcaccess" tool
-    environment.systemPackages = [ pkgs.apcupsd ];
+    environment.systemPackages = [ apcupsdWrapped ];
 
     # NOTE 1: apcupsd runs as root because it needs permission to run
     # "shutdown"
diff --git a/nixpkgs/nixos/modules/services/monitoring/arbtt.nix b/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
index 8bf4f78cc727..f07ecc5d5dd0 100644
--- a/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
@@ -7,13 +7,7 @@ let
 in {
   options = {
     services.arbtt = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable the arbtt statistics capture service.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "Arbtt statistics capture service");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/monitoring/bosun.nix b/nixpkgs/nixos/modules/services/monitoring/bosun.nix
index 27966e089eb9..dc75fda6ed8a 100644
--- a/nixpkgs/nixos/modules/services/monitoring/bosun.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/bosun.nix
@@ -22,13 +22,7 @@ in {
 
     services.bosun = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to run bosun.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "bosun");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
index d5e440310162..68e6e8e40b31 100644
--- a/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
@@ -8,11 +8,7 @@ let
 in {
   options = {
     services.cadvisor = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = lib.mdDoc "Whether to enable cadvisor service.";
-      };
+      enable = mkEnableOption (lib.mdDoc "Cadvisor service");
 
       listenAddress = mkOption {
         default = "127.0.0.1";
@@ -22,7 +18,7 @@ in {
 
       port = mkOption {
         default = 8080;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc "Cadvisor listening port";
       };
 
@@ -127,7 +123,7 @@ in {
             ${escapeShellArgs cfg.extraOptions} \
             ${optionalString (cfg.storageDriver != null) ''
               -storage_driver "${cfg.storageDriver}" \
-              -storage_driver_user "${cfg.storageDriverHost}" \
+              -storage_driver_host "${cfg.storageDriverHost}" \
               -storage_driver_db "${cfg.storageDriverDb}" \
               -storage_driver_user "${cfg.storageDriverUser}" \
               -storage_driver_password "$(cat "${cfg.storageDriverPasswordFile}")" \
diff --git a/nixpkgs/nixos/modules/services/monitoring/cockpit.nix b/nixpkgs/nixos/modules/services/monitoring/cockpit.nix
new file mode 100644
index 000000000000..2947b4d80120
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/cockpit.nix
@@ -0,0 +1,231 @@
+{ pkgs, config, lib, ... }:
+
+let
+  cfg = config.services.cockpit;
+  inherit (lib) types mkEnableOption mkOption mkIf mdDoc literalMD mkPackageOptionMD;
+  settingsFormat = pkgs.formats.ini {};
+in {
+  options = {
+    services.cockpit = {
+      enable = mkEnableOption (mdDoc "Cockpit");
+
+      package = mkPackageOptionMD pkgs "Cockpit" {
+        default = [ "cockpit" ];
+      };
+
+      settings = lib.mkOption {
+        type = settingsFormat.type;
+
+        default = {};
+
+        description = mdDoc ''
+          Settings for cockpit that will be saved in /etc/cockpit/cockpit.conf.
+
+          See the [documentation](https://cockpit-project.org/guide/latest/cockpit.conf.5.html), that is also available with `man cockpit.conf.5` for details.
+        '';
+      };
+
+      port = mkOption {
+        description = mdDoc "Port where cockpit will listen.";
+        type = types.port;
+        default = 9090;
+      };
+
+      openFirewall = mkOption {
+        description = mdDoc "Open port for cockpit.";
+        type = types.bool;
+        default = false;
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+
+    # expose cockpit-bridge system-wide
+    environment.systemPackages = [ cfg.package ];
+
+    # allow cockpit to find its plugins
+    environment.pathsToLink = [ "/share/cockpit" ];
+
+    # generate cockpit settings
+    environment.etc."cockpit/cockpit.conf".source = settingsFormat.generate "cockpit.conf" cfg.settings;
+
+    security.pam.services.cockpit = {};
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+    # units are in reverse sort order if you ls $out/lib/systemd/system
+    # all these units are basically verbatim translated from upstream
+
+    # Translation from $out/lib/systemd/system/systemd-cockpithttps.slice
+    systemd.slices.system-cockpithttps = {
+      description = "Resource limits for all cockpit-ws-https@.service instances";
+      sliceConfig = {
+        TasksMax = 200;
+        MemoryHigh = "75%";
+        MemoryMax = "90%";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.socket
+    systemd.sockets."cockpit-wsinstance-https@" = {
+      unitConfig = {
+        Description = "Socket for Cockpit Web Service https instance %I";
+        BindsTo = [ "cockpit.service" "cockpit-wsinstance-https@%i.service" ];
+        # clean up the socket after the service exits, to prevent fd leak
+        # this also effectively prevents a DoS by starting arbitrarily many sockets, as
+        # the services are resource-limited by system-cockpithttps.slice
+        Documentation = "man:cockpit-ws(8)";
+      };
+      socketConfig = {
+        ListenStream = "/run/cockpit/wsinstance/https@%i.sock";
+        SocketUser = "root";
+        SocketMode = "0600";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.service
+    systemd.services."cockpit-wsinstance-https@" = {
+      description = "Cockpit Web Service https instance %I";
+      bindsTo = [ "cockpit.service"];
+      path = [ cfg.package ];
+      documentation = [ "man:cockpit-ws(8)" ];
+      serviceConfig = {
+        Slice = "system-cockpithttps.slice";
+        ExecStart = "${cfg.package}/libexec/cockpit-ws --for-tls-proxy --port=0";
+        User = "root";
+        Group = "";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.socket
+    systemd.sockets.cockpit-wsinstance-http = {
+      unitConfig = {
+        Description = "Socket for Cockpit Web Service http instance";
+        BindsTo = "cockpit.service";
+        Documentation = "man:cockpit-ws(8)";
+      };
+      socketConfig = {
+        ListenStream = "/run/cockpit/wsinstance/http.sock";
+        SocketUser = "root";
+        SocketMode = "0600";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory.socket
+    systemd.sockets.cockpit-wsinstance-https-factory = {
+      unitConfig = {
+        Description = "Socket for Cockpit Web Service https instance factory";
+        BindsTo = "cockpit.service";
+        Documentation = "man:cockpit-ws(8)";
+      };
+      socketConfig = {
+        ListenStream = "/run/cockpit/wsinstance/https-factory.sock";
+        Accept = true;
+        SocketUser = "root";
+        SocketMode = "0600";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory@.service
+    systemd.services."cockpit-wsinstance-https-factory@" = {
+      description = "Cockpit Web Service https instance factory";
+      documentation = [ "man:cockpit-ws(8)" ];
+      path = [ cfg.package ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/libexec/cockpit-wsinstance-factory";
+        User = "root";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.service
+    systemd.services."cockpit-wsinstance-http" = {
+      description = "Cockpit Web Service http instance";
+      bindsTo = [ "cockpit.service" ];
+      path = [ cfg.package ];
+      documentation = [ "man:cockpit-ws(8)" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/libexec/cockpit-ws --no-tls --port=0";
+        User = "root";
+        Group = "";
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit.socket
+    systemd.sockets."cockpit" = {
+      unitConfig = {
+        Description = "Cockpit Web Service Socket";
+        Documentation = "man:cockpit-ws(8)";
+        Wants = "cockpit-motd.service";
+      };
+      socketConfig = {
+        ListenStream = cfg.port;
+        ExecStartPost = [
+          "-${cfg.package}/share/cockpit/motd/update-motd \"\" localhost"
+          "-${pkgs.coreutils}/bin/ln -snf active.motd /run/cockpit/motd"
+        ];
+        ExecStopPost = "-${pkgs.coreutils}/bin/ln -snf inactive.motd /run/cockpit/motd";
+      };
+      wantedBy = [ "sockets.target" ];
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit.service
+    systemd.services."cockpit" = {
+      description = "Cockpit Web Service";
+      documentation = [ "man:cockpit-ws(8)" ];
+      restartIfChanged = true;
+      path = with pkgs; [ coreutils cfg.package ];
+      requires = [ "cockpit.socket" "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ];
+      after = [ "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ];
+      environment = {
+        G_MESSAGES_DEBUG = "cockpit-ws,cockpit-bridge";
+      };
+      serviceConfig = {
+        RuntimeDirectory="cockpit/tls";
+        ExecStartPre = [
+          # cockpit-tls runs in a more constrained environment, these + means that these commands
+          # will run with full privilege instead of inside that constrained environment
+          # See https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= for details
+          "+${cfg.package}/libexec/cockpit-certificate-ensure --for-cockpit-tls"
+        ];
+        ExecStart = "${cfg.package}/libexec/cockpit-tls";
+        User = "root";
+        Group = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        MemoryDenyWriteExecute = true;
+      };
+    };
+
+    # Translation from $out/lib/systemd/system/cockpit-motd.service
+    # This part basically implements a motd state machine:
+    # - If cockpit.socket is enabled then /run/cockpit/motd points to /run/cockpit/active.motd
+    # - If cockpit.socket is disabled then /run/cockpit/motd points to /run/cockpit/inactive.motd
+    # - As cockpit.socket is disabled by default, /run/cockpit/motd points to /run/cockpit/inactive.motd
+    # /run/cockpit/active.motd is generated dynamically by cockpit-motd.service
+    systemd.services."cockpit-motd" = {
+      path = with pkgs; [ nettools ];
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${cfg.package}/share/cockpit/motd/update-motd";
+      };
+      description = "Cockpit motd updater service";
+      documentation = [ "man:cockpit-ws(8)" ];
+      wants = [ "network.target" ];
+      after = [ "network.target" "cockpit.socket" ];
+    };
+
+    systemd.tmpfiles.rules = [ # From $out/lib/tmpfiles.d/cockpit-tmpfiles.conf
+      "C /run/cockpit/inactive.motd 0640 root root - ${cfg.package}/share/cockpit/motd/inactive.motd"
+      "f /run/cockpit/active.motd   0640 root root -"
+      "L+ /run/cockpit/motd - - - - inactive.motd"
+      "d /etc/cockpit/ws-certs.d 0600 root root 0"
+    ];
+  };
+
+  meta.maintainers = pkgs.cockpit.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
index 5c62d509511d..5d525995c67a 100644
--- a/nixpkgs/nixos/modules/services/monitoring/collectd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
@@ -29,7 +29,7 @@ let
 
 in {
   options.services.collectd = with types; {
-    enable = mkEnableOption "collectd agent";
+    enable = mkEnableOption (lib.mdDoc "collectd agent");
 
     validateConfig = mkOption {
       default = true;
@@ -52,7 +52,7 @@ in {
 
     buildMinimalPackage = mkOption {
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Build a minimal collectd package with only the configured `services.collectd.plugins`
       '';
       type = bool;
diff --git a/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix b/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
index 88ca3a9227d2..fd420b0c8a06 100644
--- a/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
@@ -12,7 +12,7 @@ in {
   ###### interface
 
   options = {
-    services.das_watchdog.enable = mkEnableOption "realtime watchdog";
+    services.das_watchdog.enable = mkEnableOption (lib.mdDoc "realtime watchdog");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
index 9b984fafc12b..58a0faed962c 100644
--- a/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
@@ -49,18 +49,12 @@ let
   };
 in {
   options.services.datadog-agent = {
-    enable = mkOption {
-      description = lib.mdDoc ''
-        Whether to enable the datadog-agent v7 monitoring service
-      '';
-      default = false;
-      type = types.bool;
-    };
+    enable = mkEnableOption (lib.mdDoc "Datadog-agent v7 monitoring service");
 
     package = mkOption {
       default = pkgs.datadog-agent;
       defaultText = literalExpression "pkgs.datadog-agent";
-      description = ''
+      description = lib.mdDoc ''
         Which DataDog v7 agent package to use. Note that the provided
         package is expected to have an overridable `pythonPackages`-attribute
         which configures the Python environment with the Datadog
@@ -168,7 +162,7 @@ in {
     };
 
     checks = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Configuration for all Datadog checks. Keys of this attribute
         set will be used as the name of the check to create the
         appropriate configuration in `conf.d/$check.d/conf.yaml`.
@@ -241,7 +235,7 @@ in {
 
     systemd.services = let
       makeService = attrs: recursiveUpdate {
-        path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.iproute2 ];
+        path = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute2 ];
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
           User = "datadog";
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
deleted file mode 100644
index 045128197421..000000000000
--- a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated using update-dd-agent-default, please re-run after updating dd-agent. DO NOT EDIT MANUALLY.
-[
-  "auto_conf"
-  "agent_metrics.yaml.default"
-  "disk.yaml.default"
-  "network.yaml.default"
-  "ntp.yaml.default"
-]
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
deleted file mode 100644
index 8c0070c4853a..000000000000
--- a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
+++ /dev/null
@@ -1,236 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.dd-agent;
-
-  ddConf = pkgs.writeText "datadog.conf" ''
-    [Main]
-    dd_url: https://app.datadoghq.com
-    skip_ssl_validation: no
-    api_key: ${cfg.api_key}
-    ${optionalString (cfg.hostname != null) "hostname: ${cfg.hostname}"}
-
-    collector_log_file: /var/log/datadog/collector.log
-    forwarder_log_file: /var/log/datadog/forwarder.log
-    dogstatsd_log_file: /var/log/datadog/dogstatsd.log
-    pup_log_file:       /var/log/datadog/pup.log
-
-    # proxy_host: my-proxy.com
-    # proxy_port: 3128
-    # proxy_user: user
-    # proxy_password: password
-
-    # tags: mytag0, mytag1
-    ${optionalString (cfg.tags != null ) "tags: ${concatStringsSep ", " cfg.tags }"}
-
-    # collect_ec2_tags: no
-    # recent_point_threshold: 30
-    # use_mount: no
-    # listen_port: 17123
-    # graphite_listen_port: 17124
-    # non_local_traffic: no
-    # use_curl_http_client: False
-    # bind_host: localhost
-
-    # use_pup: no
-    # pup_port: 17125
-    # pup_interface: localhost
-    # pup_url: http://localhost:17125
-
-    # dogstatsd_port : 8125
-    # dogstatsd_interval : 10
-    # dogstatsd_normalize : yes
-    # statsd_forward_host: address_of_own_statsd_server
-    # statsd_forward_port: 8125
-
-    # device_blacklist_re: .*\/dev\/mapper\/lxc-box.*
-
-    # ganglia_host: localhost
-    # ganglia_port: 8651
-  '';
-
-  diskConfig = pkgs.writeText "disk.yaml" ''
-    init_config:
-
-    instances:
-      - use_mount: no
-  '';
-
-  networkConfig = pkgs.writeText "network.yaml" ''
-    init_config:
-
-    instances:
-      # Network check only supports one configured instance
-      - collect_connection_state: false
-        excluded_interfaces:
-          - lo
-          - lo0
-  '';
-
-  postgresqlConfig = pkgs.writeText "postgres.yaml" cfg.postgresqlConfig;
-  nginxConfig = pkgs.writeText "nginx.yaml" cfg.nginxConfig;
-  mongoConfig = pkgs.writeText "mongo.yaml" cfg.mongoConfig;
-  jmxConfig = pkgs.writeText "jmx.yaml" cfg.jmxConfig;
-  processConfig = pkgs.writeText "process.yaml" cfg.processConfig;
-
-  etcfiles =
-    let
-      defaultConfd = import ./dd-agent-defaults.nix;
-    in
-      listToAttrs (map (f: {
-        name = "dd-agent/conf.d/${f}";
-        value.source = "${pkgs.dd-agent}/agent/conf.d-system/${f}";
-      }) defaultConfd) //
-      {
-        "dd-agent/datadog.conf".source = ddConf;
-        "dd-agent/conf.d/disk.yaml".source = diskConfig;
-        "dd-agent/conf.d/network.yaml".source = networkConfig;
-      } //
-      (optionalAttrs (cfg.postgresqlConfig != null)
-      {
-        "dd-agent/conf.d/postgres.yaml".source = postgresqlConfig;
-      }) //
-      (optionalAttrs (cfg.nginxConfig != null)
-      {
-        "dd-agent/conf.d/nginx.yaml".source = nginxConfig;
-      }) //
-      (optionalAttrs (cfg.mongoConfig != null)
-      {
-        "dd-agent/conf.d/mongo.yaml".source = mongoConfig;
-      }) //
-      (optionalAttrs (cfg.processConfig != null)
-      {
-        "dd-agent/conf.d/process.yaml".source = processConfig;
-      }) //
-      (optionalAttrs (cfg.jmxConfig != null)
-      {
-        "dd-agent/conf.d/jmx.yaml".source = jmxConfig;
-      });
-
-in {
-  options.services.dd-agent = {
-    enable = mkOption {
-      description = lib.mdDoc ''
-        Whether to enable the dd-agent v5 monitoring service.
-        For datadog-agent v6, see {option}`services.datadog-agent.enable`.
-      '';
-      default = false;
-      type = types.bool;
-    };
-
-    api_key = mkOption {
-      description = lib.mdDoc ''
-        The Datadog API key to associate the agent with your account.
-
-        Warning: this key is stored in cleartext within the world-readable
-        Nix store! Consider using the new v6
-        {option}`services.datadog-agent` module instead.
-      '';
-      example = "ae0aa6a8f08efa988ba0a17578f009ab";
-      type = types.str;
-    };
-
-    tags = mkOption {
-      description = lib.mdDoc "The tags to mark this Datadog agent";
-      example = [ "test" "service" ];
-      default = null;
-      type = types.nullOr (types.listOf types.str);
-    };
-
-    hostname = mkOption {
-      description = lib.mdDoc "The hostname to show in the Datadog dashboard (optional)";
-      default = null;
-      example = "mymachine.mydomain";
-      type = types.nullOr types.str;
-    };
-
-    postgresqlConfig = mkOption {
-      description = lib.mdDoc "Datadog PostgreSQL integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    nginxConfig = mkOption {
-      description = lib.mdDoc "Datadog nginx integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    mongoConfig = mkOption {
-      description = lib.mdDoc "MongoDB integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    jmxConfig = mkOption {
-      description = lib.mdDoc "JMX integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    processConfig = mkOption {
-      description = lib.mdDoc ''
-        Process integration configuration
-        See <https://docs.datadoghq.com/integrations/process/>
-      '';
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.dd-agent pkgs.sysstat pkgs.procps ];
-
-    users.users.datadog = {
-      description = "Datadog Agent User";
-      uid = config.ids.uids.datadog;
-      group = "datadog";
-      home = "/var/log/datadog/";
-      createHome = true;
-    };
-
-    users.groups.datadog.gid = config.ids.gids.datadog;
-
-    systemd.services = let
-      makeService = attrs: recursiveUpdate {
-        path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.gohai ];
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = "datadog";
-          Group = "datadog";
-          Restart = "always";
-          RestartSec = 2;
-          PrivateTmp = true;
-        };
-        restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig processConfig ];
-      } attrs;
-    in {
-      dd-agent = makeService {
-        description = "Datadog agent monitor";
-        serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-agent foreground";
-      };
-
-      dogstatsd = makeService {
-        description = "Datadog statsd";
-        environment.TMPDIR = "/run/dogstatsd";
-        serviceConfig = {
-          ExecStart = "${pkgs.dd-agent}/bin/dogstatsd start";
-          Type = "forking";
-          PIDFile = "/run/dogstatsd/dogstatsd.pid";
-          RuntimeDirectory = "dogstatsd";
-        };
-      };
-
-      dd-jmxfetch = lib.mkIf (cfg.jmxConfig != null) {
-        description = "Datadog JMX Fetcher";
-        path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ];
-        serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-jmxfetch";
-      };
-    };
-
-    environment.etc = etcfiles;
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults b/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
deleted file mode 100755
index 76724173171a..000000000000
--- a/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-dd=$(nix-build --no-out-link -A dd-agent ../../../..)
-echo '# Generated using update-dd-agent-default, please re-run after updating dd-agent. DO NOT EDIT MANUALLY.' > dd-agent-defaults.nix
-echo '[' >> dd-agent-defaults.nix
-echo '  "auto_conf"' >> dd-agent-defaults.nix
-for f in $(find $dd/agent/conf.d-system -maxdepth 1 -type f | grep -v '\.example' | sort); do
-  echo "  \"$(basename $f)\"" >> dd-agent-defaults.nix
-done
-echo ']' >> dd-agent-defaults.nix
diff --git a/nixpkgs/nixos/modules/services/monitoring/do-agent.nix b/nixpkgs/nixos/modules/services/monitoring/do-agent.nix
index 4dfb6236727b..c1788c640c23 100644
--- a/nixpkgs/nixos/modules/services/monitoring/do-agent.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/do-agent.nix
@@ -8,7 +8,7 @@ let
 in
 {
   options.services.do-agent = {
-    enable = mkEnableOption "do-agent, the DigitalOcean droplet metrics agent";
+    enable = mkEnableOption (lib.mdDoc "do-agent, the DigitalOcean droplet metrics agent");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix b/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
index 6b440e9fa45b..7b28e8de1229 100644
--- a/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
@@ -22,7 +22,7 @@ in {
 
     services.fusionInventory = {
 
-      enable = mkEnableOption "Fusion Inventory Agent";
+      enable = mkEnableOption (lib.mdDoc "Fusion Inventory Agent");
 
       servers = mkOption {
         type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana-agent.nix b/nixpkgs/nixos/modules/services/monitoring/grafana-agent.nix
index 8190f44c72f7..13604ff77c68 100644
--- a/nixpkgs/nixos/modules/services/monitoring/grafana-agent.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana-agent.nix
@@ -11,14 +11,9 @@ in
   };
 
   options.services.grafana-agent = {
-    enable = mkEnableOption "grafana-agent";
+    enable = mkEnableOption (lib.mdDoc "grafana-agent");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.grafana-agent;
-      defaultText = "pkgs.grafana-agent";
-      description = lib.mdDoc "The grafana-agent package to use.";
-    };
+    package = mkPackageOptionMD pkgs "grafana-agent" { };
 
     credentials = mkOption {
       description = lib.mdDoc ''
@@ -37,11 +32,22 @@ in
       };
     };
 
+    extraFlags = mkOption {
+      type = with types; listOf str;
+      default = [ ];
+      example = [ "-enable-features=integrations-next" "-disable-reporting" ];
+      description = lib.mdDoc ''
+        Extra command-line flags passed to {command}`grafana-agent`.
+
+        See <https://grafana.com/docs/agent/latest/static/configuration/flags/>
+      '';
+    };
+
     settings = mkOption {
-      description = ''
-        Configuration for <package>grafana-agent</package>.
+      description = lib.mdDoc ''
+        Configuration for {command}`grafana-agent`.
 
-        See https://grafana.com/docs/agent/latest/configuration/
+        See <https://grafana.com/docs/agent/latest/configuration/>
       '';
 
       type = types.submodule {
@@ -49,17 +55,18 @@ in
       };
 
       default = { };
-      defaultText = ''
-        metrics = {
-          wal_directory = "\''${STATE_DIRECTORY}";
-          global.scrape_interval = "5s";
-        };
-        integrations = {
-          agent.enabled = true;
-          agent.scrape_integration = true;
-          node_exporter.enabled = true;
-          replace_instance_label = true;
-        };
+      defaultText = lib.literalExpression ''
+        {
+          metrics = {
+            wal_directory = "\''${STATE_DIRECTORY}";
+            global.scrape_interval = "5s";
+          };
+          integrations = {
+            agent.enabled = true;
+            agent.scrape_integration = true;
+            node_exporter.enabled = true;
+          };
+        }
       '';
       example = {
         metrics.global.remote_write = [{
@@ -114,7 +121,6 @@ in
         agent.enabled = mkDefault true;
         agent.scrape_integration = mkDefault true;
         node_exporter.enabled = mkDefault true;
-        replace_instance_label = mkDefault true;
       };
     };
 
@@ -138,7 +144,7 @@ in
         # We can't use Environment=HOSTNAME=%H, as it doesn't include the domain part.
         export HOSTNAME=$(< /proc/sys/kernel/hostname)
 
-        exec ${cfg.package}/bin/agent -config.expand-env -config.file ${configFile}
+        exec ${lib.getExe cfg.package} -config.expand-env -config.file ${configFile} ${escapeShellArgs cfg.extraFlags}
       '';
       serviceConfig = {
         Restart = "always";
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana-image-renderer.nix b/nixpkgs/nixos/modules/services/monitoring/grafana-image-renderer.nix
index 4820b1946987..36258866646a 100644
--- a/nixpkgs/nixos/modules/services/monitoring/grafana-image-renderer.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana-image-renderer.nix
@@ -10,7 +10,7 @@ let
   configFile = format.generate "grafana-image-renderer-config.json" cfg.settings;
 in {
   options.services.grafana-image-renderer = {
-    enable = mkEnableOption "grafana-image-renderer";
+    enable = mkEnableOption (lib.mdDoc "grafana-image-renderer");
 
     chromium = mkOption {
       type = types.package;
@@ -19,9 +19,9 @@ in {
       '';
     };
 
-    verbose = mkEnableOption "verbosity for the service";
+    verbose = mkEnableOption (lib.mdDoc "verbosity for the service");
 
-    provisionGrafana = mkEnableOption "Grafana configuration for grafana-image-renderer";
+    provisionGrafana = mkEnableOption (lib.mdDoc "Grafana configuration for grafana-image-renderer");
 
     settings = mkOption {
       type = types.submodule {
@@ -62,25 +62,23 @@ in {
             mode = mkOption {
               default = "default";
               type = types.enum [ "default" "reusable" "clustered" ];
-              description = ''
-                Rendering mode of <package>grafana-image-renderer</package>:
-                <itemizedlist>
-                <listitem><para><literal>default:</literal> Creates on browser-instance
-                  per rendering request.</para></listitem>
-                <listitem><para><literal>reusable:</literal> One browser instance
-                  will be started and reused for each rendering request.</para></listitem>
-                <listitem><para><literal>clustered:</literal> allows to precisely
+              description = lib.mdDoc ''
+                Rendering mode of `grafana-image-renderer`:
+
+                - `default:` Creates on browser-instance
+                  per rendering request.
+                - `reusable:` One browser instance
+                  will be started and reused for each rendering request.
+                - `clustered:` allows to precisely
                   configure how many browser-instances are supposed to be used. The values
-                  for that mode can be declared in <literal>rendering.clustering</literal>.
-                  </para></listitem>
-                </itemizedlist>
+                  for that mode can be declared in `rendering.clustering`.
               '';
             };
             args = mkOption {
               type = types.listOf types.str;
               default = [ "--no-sandbox" ];
-              description = ''
-                List of CLI flags passed to <package>chromium</package>.
+              description = lib.mdDoc ''
+                List of CLI flags passed to `chromium`.
               '';
             };
           };
@@ -89,10 +87,10 @@ in {
 
       default = {};
 
-      description = ''
-        Configuration attributes for <package>grafana-image-renderer</package>.
+      description = lib.mdDoc ''
+        Configuration attributes for `grafana-image-renderer`.
 
-        See <link xlink:href="https://github.com/grafana/grafana-image-renderer/blob/ce1f81438e5f69c7fd7c73ce08bab624c4c92e25/default.json"/>
+        See <https://github.com/grafana/grafana-image-renderer/blob/ce1f81438e5f69c7fd7c73ce08bab624c4c92e25/default.json>
         for supported values.
       '';
     };
@@ -108,9 +106,9 @@ in {
       }
     ];
 
-    services.grafana.extraOptions = mkIf cfg.provisionGrafana {
-      RENDERING_SERVER_URL = "http://localhost:${toString cfg.settings.service.port}/render";
-      RENDERING_CALLBACK_URL = "http://localhost:${toString config.services.grafana.port}";
+    services.grafana.settings.rendering = mkIf cfg.provisionGrafana {
+      server_url = "http://localhost:${toString cfg.settings.service.port}/render";
+      callback_url = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
     };
 
     services.grafana-image-renderer.chromium = mkDefault pkgs.chromium;
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix b/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
index 7a27b5cbce31..eac304d63aa1 100644
--- a/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -7,7 +7,7 @@ let
 
 in {
   options.services.grafana_reporter = {
-    enable = mkEnableOption "grafana_reporter";
+    enable = mkEnableOption (lib.mdDoc "grafana_reporter");
 
     grafana = {
       protocol = mkOption {
@@ -23,7 +23,7 @@ in {
       port = mkOption {
         description = lib.mdDoc "Grafana port.";
         default = 3000;
-        type = types.int;
+        type = types.port;
       };
 
     };
@@ -36,7 +36,7 @@ in {
     port = mkOption {
       description = lib.mdDoc "Listening port.";
       default = 8686;
-      type = types.int;
+      type = types.port;
     };
 
     templateDir = mkOption {
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana.nix b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
index 456fe92eea38..adffadf3fc47 100644
--- a/nixpkgs/nixos/modules/services/monitoring/grafana.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
@@ -5,106 +5,93 @@ with lib;
 let
   cfg = config.services.grafana;
   opt = options.services.grafana;
+  provisioningSettingsFormat = pkgs.formats.yaml {};
   declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins);
-  useMysql = cfg.database.type == "mysql";
-  usePostgresql = cfg.database.type == "postgres";
-
-  envOptions = {
-    PATHS_DATA = cfg.dataDir;
-    PATHS_PLUGINS = if builtins.isNull cfg.declarativePlugins then "${cfg.dataDir}/plugins" else declarativePlugins;
-    PATHS_LOGS = "${cfg.dataDir}/log";
-
-    SERVER_SERVE_FROM_SUBPATH = boolToString cfg.server.serveFromSubPath;
-    SERVER_PROTOCOL = cfg.protocol;
-    SERVER_HTTP_ADDR = cfg.addr;
-    SERVER_HTTP_PORT = cfg.port;
-    SERVER_SOCKET = cfg.socket;
-    SERVER_DOMAIN = cfg.domain;
-    SERVER_ROOT_URL = cfg.rootUrl;
-    SERVER_STATIC_ROOT_PATH = cfg.staticRootPath;
-    SERVER_CERT_FILE = cfg.certFile;
-    SERVER_CERT_KEY = cfg.certKey;
-
-    DATABASE_TYPE = cfg.database.type;
-    DATABASE_HOST = cfg.database.host;
-    DATABASE_NAME = cfg.database.name;
-    DATABASE_USER = cfg.database.user;
-    DATABASE_PASSWORD = cfg.database.password;
-    DATABASE_PATH = cfg.database.path;
-    DATABASE_CONN_MAX_LIFETIME = cfg.database.connMaxLifetime;
-
-    SECURITY_ADMIN_USER = cfg.security.adminUser;
-    SECURITY_ADMIN_PASSWORD = cfg.security.adminPassword;
-    SECURITY_SECRET_KEY = cfg.security.secretKey;
-
-    USERS_ALLOW_SIGN_UP = boolToString cfg.users.allowSignUp;
-    USERS_ALLOW_ORG_CREATE = boolToString cfg.users.allowOrgCreate;
-    USERS_AUTO_ASSIGN_ORG = boolToString cfg.users.autoAssignOrg;
-    USERS_AUTO_ASSIGN_ORG_ROLE = cfg.users.autoAssignOrgRole;
-
-    AUTH_DISABLE_LOGIN_FORM = boolToString cfg.auth.disableLoginForm;
-
-    AUTH_ANONYMOUS_ENABLED = boolToString cfg.auth.anonymous.enable;
-    AUTH_ANONYMOUS_ORG_NAME = cfg.auth.anonymous.org_name;
-    AUTH_ANONYMOUS_ORG_ROLE = cfg.auth.anonymous.org_role;
-
-    AUTH_AZUREAD_NAME = "Azure AD";
-    AUTH_AZUREAD_ENABLED = boolToString cfg.auth.azuread.enable;
-    AUTH_AZUREAD_ALLOW_SIGN_UP = boolToString cfg.auth.azuread.allowSignUp;
-    AUTH_AZUREAD_CLIENT_ID = cfg.auth.azuread.clientId;
-    AUTH_AZUREAD_SCOPES = "openid email profile";
-    AUTH_AZUREAD_AUTH_URL = "https://login.microsoftonline.com/${cfg.auth.azuread.tenantId}/oauth2/v2.0/authorize";
-    AUTH_AZUREAD_TOKEN_URL = "https://login.microsoftonline.com/${cfg.auth.azuread.tenantId}/oauth2/v2.0/token";
-    AUTH_AZUREAD_ALLOWED_DOMAINS = cfg.auth.azuread.allowedDomains;
-    AUTH_AZUREAD_ALLOWED_GROUPS = cfg.auth.azuread.allowedGroups;
-    AUTH_AZUREAD_ROLE_ATTRIBUTE_STRICT = false;
-
-    AUTH_GOOGLE_ENABLED = boolToString cfg.auth.google.enable;
-    AUTH_GOOGLE_ALLOW_SIGN_UP = boolToString cfg.auth.google.allowSignUp;
-    AUTH_GOOGLE_CLIENT_ID = cfg.auth.google.clientId;
-
-    ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable;
-
-    SMTP_ENABLED = boolToString cfg.smtp.enable;
-    SMTP_HOST = cfg.smtp.host;
-    SMTP_USER = cfg.smtp.user;
-    SMTP_PASSWORD = cfg.smtp.password;
-    SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
-  } // cfg.extraOptions;
-
-  datasourceConfiguration = {
-    apiVersion = 1;
-    datasources = cfg.provision.datasources;
-  };
-
-  datasourceFile = pkgs.writeText "datasource.yaml" (builtins.toJSON datasourceConfiguration);
-
-  dashboardConfiguration = {
-    apiVersion = 1;
-    providers = cfg.provision.dashboards;
-  };
-
-  dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration);
+  useMysql = cfg.settings.database.type == "mysql";
+  usePostgresql = cfg.settings.database.type == "postgres";
+
+  settingsFormatIni = pkgs.formats.ini {};
+  configFile = settingsFormatIni.generate "config.ini" cfg.settings;
+
+  mkProvisionCfg = name: attr: provisionCfg:
+    if provisionCfg.path != null
+      then provisionCfg.path
+    else
+      provisioningSettingsFormat.generate "${name}.yaml"
+        (if provisionCfg.settings != null
+          then provisionCfg.settings
+          else {
+            apiVersion = 1;
+            ${attr} = [];
+          });
+
+  datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources;
+  dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards;
 
   notifierConfiguration = {
     apiVersion = 1;
     notifiers = cfg.provision.notifiers;
   };
 
-  notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
-
-  provisionConfDir =  pkgs.runCommand "grafana-provisioning" { } ''
-    mkdir -p $out/{datasources,dashboards,notifiers}
-    ln -sf ${datasourceFile} $out/datasources/datasource.yaml
-    ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml
-    ln -sf ${notifierFile} $out/notifiers/notifier.yaml
+  notifierFileOrDir = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
+
+  generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null)
+                                        then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings
+                                        else cfg.provision.alerting."${x}".path;
+  rulesFileOrDir = generateAlertingProvisioningYaml "rules";
+  contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints";
+  policiesFileOrDir = generateAlertingProvisioningYaml "policies";
+  templatesFileOrDir = generateAlertingProvisioningYaml "templates";
+  muteTimingsFileOrDir = generateAlertingProvisioningYaml "muteTimings";
+
+  ln = { src, dir, filename }: ''
+    if [[ -d "${src}" ]]; then
+      pushd $out/${dir} &>/dev/null
+        lndir "${src}"
+      popd &>/dev/null
+    else
+      ln -sf ${src} $out/${dir}/${filename}.yaml
+    fi
+  '';
+  provisionConfDir = pkgs.runCommand "grafana-provisioning" { nativeBuildInputs = [ pkgs.xorg.lndir ]; } ''
+    mkdir -p $out/{datasources,dashboards,notifiers,alerting}
+    ${ln { src = datasourceFileOrDir;    dir = "datasources"; filename = "datasource"; }}
+    ${ln { src = dashboardFileOrDir;     dir = "dashboards";  filename = "dashboard"; }}
+    ${ln { src = notifierFileOrDir;      dir = "notifiers";   filename = "notifier"; }}
+    ${ln { src = rulesFileOrDir;         dir = "alerting";    filename = "rules"; }}
+    ${ln { src = contactPointsFileOrDir; dir = "alerting";    filename = "contactPoints"; }}
+    ${ln { src = policiesFileOrDir;      dir = "alerting";    filename = "policies"; }}
+    ${ln { src = templatesFileOrDir;     dir = "alerting";    filename = "templates"; }}
+    ${ln { src = muteTimingsFileOrDir;   dir = "alerting";    filename = "muteTimings"; }}
   '';
 
   # Get a submodule without any embedded metadata:
   _filter = x: filterAttrs (k: v: k != "_module") x;
 
+  # FIXME(@Ma27) remove before 23.05. This is just a helper-type
+  # because `mkRenamedOptionModule` doesn't work if `foo.bar` is renamed
+  # to `foo.bar.baz`.
+  submodule' = module: types.coercedTo
+    (mkOptionType {
+      name = "grafana-provision-submodule";
+      description = "Wrapper-type for backwards compat of Grafana's declarative provisioning";
+      check = x:
+        if builtins.isList x then
+          throw ''
+            Provisioning dashboards and datasources declaratively by
+            setting `dashboards` or `datasources` to a list is not supported
+            anymore. Use `services.grafana.provision.datasources.settings.datasources`
+            (or `services.grafana.provision.dashboards.settings.providers`) instead.
+          ''
+        else isAttrs x || isFunction x;
+    })
+    id
+    (types.submodule module);
+
   # http://docs.grafana.org/administration/provisioning/#datasources
   grafanaTypes.datasourceConfig = types.submodule {
+    freeformType = provisioningSettingsFormat.type;
+
     options = {
       name = mkOption {
         type = types.str;
@@ -119,11 +106,6 @@ let
         default = "proxy";
         description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required.";
       };
-      orgId = mkOption {
-        type = types.int;
-        default = 1;
-        description = lib.mdDoc "Org id. will default to orgId 1 if not specified.";
-      };
       uid = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -131,114 +113,51 @@ let
       };
       url = mkOption {
         type = types.str;
+        default = "localhost";
         description = lib.mdDoc "Url of the datasource.";
       };
-      password = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc "Database password, if used.";
-      };
-      user = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc "Database user, if used.";
-      };
-      database = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc "Database name, if used.";
-      };
-      basicAuth = mkOption {
-        type = types.nullOr types.bool;
-        default = null;
-        description = lib.mdDoc "Enable/disable basic auth.";
-      };
-      basicAuthUser = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc "Basic auth username.";
-      };
-      basicAuthPassword = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc "Basic auth password.";
-      };
-      withCredentials = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Enable/disable with credentials headers.";
-      };
-      isDefault = mkOption {
+      editable = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Mark as default datasource. Max one per org.";
+        description = lib.mdDoc "Allow users to edit datasources from the UI.";
       };
       jsonData = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = lib.mdDoc "Datasource specific configuration.";
+        description = lib.mdDoc "Extra data for datasource plugins.";
       };
       secureJsonData = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = lib.mdDoc "Datasource specific secure configuration.";
-      };
-      version = mkOption {
-        type = types.int;
-        default = 1;
-        description = lib.mdDoc "Version.";
-      };
-      editable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Allow users to edit datasources from the UI.";
+        description = lib.mdDoc ''
+          Datasource specific secure configuration. Please note that the contents of this option
+          will end up in a world-readable Nix store. Use the file provider
+          pointing at a reasonably secured file in the local filesystem
+          to work around that. Look at the documentation for details:
+          <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+        '';
       };
     };
   };
 
   # http://docs.grafana.org/administration/provisioning/#dashboards
   grafanaTypes.dashboardConfig = types.submodule {
+    freeformType = provisioningSettingsFormat.type;
+
     options = {
       name = mkOption {
         type = types.str;
         default = "default";
-        description = lib.mdDoc "Provider name.";
-      };
-      orgId = mkOption {
-        type = types.int;
-        default = 1;
-        description = lib.mdDoc "Organization ID.";
-      };
-      folder = mkOption {
-        type = types.str;
-        default = "";
-        description = lib.mdDoc "Add dashboards to the specified folder.";
+        description = lib.mdDoc "A unique provider name.";
       };
       type = mkOption {
         type = types.str;
         default = "file";
         description = lib.mdDoc "Dashboard provider type.";
       };
-      disableDeletion = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Disable deletion when JSON file is removed.";
-      };
-      updateIntervalSeconds = mkOption {
-        type = types.int;
-        default = 10;
-        description = lib.mdDoc "How often Grafana will scan for changed dashboards.";
-      };
-      options = {
-        path = mkOption {
-          type = types.path;
-          description = lib.mdDoc "Path grafana will watch for dashboards.";
-        };
-        foldersFromFilesStructure = mkOption {
-          type = types.bool;
-          default = false;
-          description = lib.mdDoc "Use folder names from filesystem to create folders in Grafana.";
-        };
+      options.path = mkOption {
+        type = types.path;
+        description = lib.mdDoc "Path grafana will watch for dashboards. Required when using the 'file' type.";
       };
     };
   };
@@ -296,75 +215,88 @@ let
       secure_settings = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = lib.mdDoc "Secure settings for the notifier type.";
+        description = lib.mdDoc ''
+          Secure settings for the notifier type. Please note that the contents of this option
+          will end up in a world-readable Nix store. Use the file provider
+          pointing at a reasonably secured file in the local filesystem
+          to work around that. Look at the documentation for details:
+          <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+        '';
       };
     };
   };
 in {
-  options.services.grafana = {
-    enable = mkEnableOption "grafana";
-
-    protocol = mkOption {
-      description = lib.mdDoc "Which protocol to listen.";
-      default = "http";
-      type = types.enum ["http" "https" "socket"];
-    };
-
-    addr = mkOption {
-      description = lib.mdDoc "Listening address.";
-      default = "127.0.0.1";
-      type = types.str;
-    };
-
-    port = mkOption {
-      description = lib.mdDoc "Listening port.";
-      default = 3000;
-      type = types.port;
-    };
-
-    socket = mkOption {
-      description = lib.mdDoc "Listening socket.";
-      default = "/run/grafana/grafana.sock";
-      type = types.str;
-    };
-
-    domain = mkOption {
-      description = lib.mdDoc "The public facing domain name used to access grafana from a browser.";
-      default = "localhost";
-      type = types.str;
-    };
-
-    rootUrl = mkOption {
-      description = lib.mdDoc "Full public facing url.";
-      default = "%(protocol)s://%(domain)s:%(http_port)s/";
-      type = types.str;
-    };
-
-    certFile = mkOption {
-      description = lib.mdDoc "Cert file for ssl.";
-      default = "";
-      type = types.str;
-    };
+  imports = [
+    (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ])
+    (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ])
+    (mkRenamedOptionModule [ "services" "grafana" "port" ] [ "services" "grafana" "settings" "server" "http_port" ])
+    (mkRenamedOptionModule [ "services" "grafana" "domain" ] [ "services" "grafana" "settings" "server" "domain" ])
+    (mkRenamedOptionModule [ "services" "grafana" "rootUrl" ] [ "services" "grafana" "settings" "server" "root_url" ])
+    (mkRenamedOptionModule [ "services" "grafana" "staticRootPath" ] [ "services" "grafana" "settings" "server" "static_root_path" ])
+    (mkRenamedOptionModule [ "services" "grafana" "certFile" ] [ "services" "grafana" "settings" "server" "cert_file" ])
+    (mkRenamedOptionModule [ "services" "grafana" "certKey" ] [ "services" "grafana" "settings" "server" "cert_key" ])
+    (mkRenamedOptionModule [ "services" "grafana" "socket" ] [ "services" "grafana" "settings" "server" "socket" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "type" ] [ "services" "grafana" "settings" "database" "type" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "host" ] [ "services" "grafana" "settings" "database" "host" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "name" ] [ "services" "grafana" "settings" "database" "name" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "user" ] [ "services" "grafana" "settings" "database" "user" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "password" ] [ "services" "grafana" "settings" "database" "password" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "path" ] [ "services" "grafana" "settings" "database" "path" ])
+    (mkRenamedOptionModule [ "services" "grafana" "database" "connMaxLifetime" ] [ "services" "grafana" "settings" "database" "conn_max_lifetime" ])
+    (mkRenamedOptionModule [ "services" "grafana" "security" "adminUser" ] [ "services" "grafana" "settings" "security" "admin_user" ])
+    (mkRenamedOptionModule [ "services" "grafana" "security" "adminPassword" ] [ "services" "grafana" "settings" "security" "admin_password" ])
+    (mkRenamedOptionModule [ "services" "grafana" "security" "secretKey" ] [ "services" "grafana" "settings" "security" "secret_key" ])
+    (mkRenamedOptionModule [ "services" "grafana" "server" "serveFromSubPath" ] [ "services" "grafana" "settings" "server" "serve_from_sub_path" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "enable" ] [ "services" "grafana" "settings" "smtp" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "user" ] [ "services" "grafana" "settings" "smtp" "user" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "password" ] [ "services" "grafana" "settings" "smtp" "password" ])
+    (mkRenamedOptionModule [ "services" "grafana" "smtp" "fromAddress" ] [ "services" "grafana" "settings" "smtp" "from_address" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "allowSignUp" ] [ "services" "grafana" "settings" "users" "allow_sign_up" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "allowOrgCreate" ] [ "services" "grafana" "settings" "users" "allow_org_create" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrg" ] [ "services" "grafana" "settings" "users" "auto_assign_org" ])
+    (mkRenamedOptionModule [ "services" "grafana" "users" "autoAssignOrgRole" ] [ "services" "grafana" "settings" "users" "auto_assign_org_role" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "disableLoginForm" ] [ "services" "grafana" "settings" "auth" "disable_login_form" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "enable" ] [ "services" "grafana" "settings" "auth.anonymous" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_name" ] [ "services" "grafana" "settings" "auth.anonymous" "org_name" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "anonymous" "org_role" ] [ "services" "grafana" "settings" "auth.anonymous" "org_role" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "enable" ] [ "services" "grafana" "settings" "auth.azuread" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowSignUp" ] [ "services" "grafana" "settings" "auth.azuread" "allow_sign_up" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "clientId" ] [ "services" "grafana" "settings" "auth.azuread" "client_id" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedDomains" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_domains" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "azuread" "allowedGroups" ] [ "services" "grafana" "settings" "auth.azuread" "allowed_groups" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "enable" ] [ "services" "grafana" "settings" "auth.google" "enabled" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "allowSignUp" ] [ "services" "grafana" "settings" "auth.google" "allow_sign_up" ])
+    (mkRenamedOptionModule [ "services" "grafana" "auth" "google" "clientId" ] [ "services" "grafana" "settings" "auth.google" "client_id" ])
+    (mkRenamedOptionModule [ "services" "grafana" "analytics" "reporting" "enable" ] [ "services" "grafana" "settings" "analytics" "reporting_enabled" ])
+
+    (mkRemovedOptionModule [ "services" "grafana" "database" "passwordFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.database.password' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "security" "adminPasswordFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.security.admin_password' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "security" "secretKeyFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.security.secret_key' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "smtp" "passwordFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.smtp.password' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "clientSecretFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.azuread.client_secret' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] ''
+      This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead.
+    '')
+    (mkRemovedOptionModule [ "services" "grafana" "extraOptions" ] ''
+      This option has been removed. Use 'services.grafana.settings' instead. For a detailed migration guide, please
+      review the release notes of NixOS 22.11.
+    '')
+
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
+  ];
 
-    certKey = mkOption {
-      description = lib.mdDoc "Cert key for ssl.";
-      default = "";
-      type = types.str;
-    };
-
-    staticRootPath = mkOption {
-      description = lib.mdDoc "Root path for static assets.";
-      default = "${cfg.package}/share/grafana/public";
-      defaultText = literalExpression ''"''${package}/share/grafana/public"'';
-      type = types.str;
-    };
-
-    package = mkOption {
-      description = lib.mdDoc "Package to use.";
-      default = pkgs.grafana;
-      defaultText = literalExpression "pkgs.grafana";
-      type = types.package;
-    };
+  options.services.grafana = {
+    enable = mkEnableOption (lib.mdDoc "grafana");
 
     declarativePlugins = mkOption {
       type = with types; nullOr (listOf path);
@@ -377,348 +309,944 @@ in {
       apply = x: if isList x then lib.unique x else x;
     };
 
+    package = mkOption {
+      description = lib.mdDoc "Package to use.";
+      default = pkgs.grafana;
+      defaultText = literalExpression "pkgs.grafana";
+      type = types.package;
+    };
+
     dataDir = mkOption {
       description = lib.mdDoc "Data directory.";
       default = "/var/lib/grafana";
       type = types.path;
     };
 
-    database = {
-      type = mkOption {
-        description = lib.mdDoc "Database type.";
-        default = "sqlite3";
-        type = types.enum ["mysql" "sqlite3" "postgres"];
-      };
-
-      host = mkOption {
-        description = lib.mdDoc "Database host.";
-        default = "127.0.0.1:3306";
-        type = types.str;
-      };
-
-      name = mkOption {
-        description = lib.mdDoc "Database name.";
-        default = "grafana";
-        type = types.str;
+    settings = mkOption {
+      description = lib.mdDoc ''
+        Grafana settings. See <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/>
+        for available options. INI format is used.
+      '';
+      type = types.submodule {
+        freeformType = settingsFormatIni.type;
+
+        options = {
+          paths = {
+            plugins = mkOption {
+              description = lib.mdDoc "Directory where grafana will automatically scan and look for plugins";
+              default = if (cfg.declarativePlugins == null) then "${cfg.dataDir}/plugins" else declarativePlugins;
+              defaultText = literalExpression "if (cfg.declarativePlugins == null) then \"\${cfg.dataDir}/plugins\" else declarativePlugins";
+              type = types.path;
+            };
+
+            provisioning = mkOption {
+              description = lib.mdDoc ''
+                Folder that contains provisioning config files that grafana will apply on startup and while running.
+                Don't change the value of this option if you are planning to use `services.grafana.provision` options.
+              '';
+              default = provisionConfDir;
+              defaultText = "directory with links to files generated from services.grafana.provision";
+              type = types.path;
+            };
+          };
+
+          server = {
+            protocol = mkOption {
+              description = lib.mdDoc "Which protocol to listen.";
+              default = "http";
+              type = types.enum ["http" "https" "h2" "socket"];
+            };
+
+            http_addr = mkOption {
+              type = types.str;
+              default = "127.0.0.1";
+              description = lib.mdDoc ''
+                Listening address.
+
+                ::: {.note}
+                This setting intentionally varies from upstream's default to be a bit more secure by default.
+                :::
+              '';
+            };
+
+            http_port = mkOption {
+              description = lib.mdDoc "Listening port.";
+              default = 3000;
+              type = types.port;
+            };
+
+            domain = mkOption {
+              description = lib.mdDoc "The public facing domain name used to access grafana from a browser.";
+              default = "localhost";
+              type = types.str;
+            };
+
+            root_url = mkOption {
+              description = lib.mdDoc "Full public facing url.";
+              default = "%(protocol)s://%(domain)s:%(http_port)s/";
+              type = types.str;
+            };
+
+            static_root_path = mkOption {
+              description = lib.mdDoc "Root path for static assets.";
+              default = "${cfg.package}/share/grafana/public";
+              defaultText = literalExpression ''"''${package}/share/grafana/public"'';
+              type = types.str;
+            };
+
+            enable_gzip = mkOption {
+              description = lib.mdDoc ''
+                Set this option to true to enable HTTP compression, this can improve transfer speed and bandwidth utilization.
+                It is recommended that most users set it to true. By default it is set to false for compatibility reasons.
+              '';
+              default = false;
+              type = types.bool;
+            };
+
+            cert_file = mkOption {
+              description = lib.mdDoc "Cert file for ssl.";
+              default = "";
+              type = types.str;
+            };
+
+            cert_key = mkOption {
+              description = lib.mdDoc "Cert key for ssl.";
+              default = "";
+              type = types.str;
+            };
+
+            socket = mkOption {
+              description = lib.mdDoc "Path where the socket should be created when protocol=socket. Make sure that Grafana has appropriate permissions before you change this setting.";
+              default = "/run/grafana/grafana.sock";
+              type = types.str;
+            };
+          };
+
+          database = {
+            type = mkOption {
+              description = lib.mdDoc "Database type.";
+              default = "sqlite3";
+              type = types.enum ["mysql" "sqlite3" "postgres"];
+            };
+
+            host = mkOption {
+              description = lib.mdDoc "Database host.";
+              default = "127.0.0.1:3306";
+              type = types.str;
+            };
+
+            name = mkOption {
+              description = lib.mdDoc "Database name.";
+              default = "grafana";
+              type = types.str;
+            };
+
+            user = mkOption {
+              description = lib.mdDoc "Database user.";
+              default = "root";
+              type = types.str;
+            };
+
+            password = mkOption {
+              description = lib.mdDoc ''
+                Database password. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "";
+              type = types.str;
+            };
+
+            path = mkOption {
+              description = lib.mdDoc "Only applicable to sqlite3 database. The file path where the database will be stored.";
+              default = "${cfg.dataDir}/data/grafana.db";
+              defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
+              type = types.path;
+            };
+          };
+
+          security = {
+            admin_user = mkOption {
+              description = lib.mdDoc "Default admin username.";
+              default = "admin";
+              type = types.str;
+            };
+
+            admin_password = mkOption {
+              description = lib.mdDoc ''
+                Default admin password. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "admin";
+              type = types.str;
+            };
+
+            secret_key = mkOption {
+              description = lib.mdDoc ''
+                Secret key used for signing. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "SW2YcwTIb9zpOOhoPsMm";
+              type = types.str;
+            };
+          };
+
+          smtp = {
+            enabled = mkOption {
+              description = lib.mdDoc "Whether to enable SMTP.";
+              default = false;
+              type = types.bool;
+            };
+            host = mkOption {
+              description = lib.mdDoc "Host to connect to.";
+              default = "localhost:25";
+              type = types.str;
+            };
+            user = mkOption {
+              description = lib.mdDoc "User used for authentication.";
+              default = "";
+              type = types.str;
+            };
+            password = mkOption {
+              description = lib.mdDoc ''
+                Password used for authentication. Please note that the contents of this option
+                will end up in a world-readable Nix store. Use the file provider
+                pointing at a reasonably secured file in the local filesystem
+                to work around that. Look at the documentation for details:
+                <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
+              '';
+              default = "";
+              type = types.str;
+            };
+            from_address = mkOption {
+              description = lib.mdDoc "Email address used for sending.";
+              default = "admin@grafana.localhost";
+              type = types.str;
+            };
+          };
+
+          users = {
+            allow_sign_up = mkOption {
+              description = lib.mdDoc "Disable user signup / registration.";
+              default = false;
+              type = types.bool;
+            };
+
+            allow_org_create = mkOption {
+              description = lib.mdDoc "Whether user is allowed to create organizations.";
+              default = false;
+              type = types.bool;
+            };
+
+            auto_assign_org = mkOption {
+              description = lib.mdDoc "Whether to automatically assign new users to default org.";
+              default = true;
+              type = types.bool;
+            };
+
+            auto_assign_org_role = mkOption {
+              description = lib.mdDoc "Default role new users will be auto assigned.";
+              default = "Viewer";
+              type = types.enum ["Viewer" "Editor" "Admin"];
+            };
+          };
+
+          analytics.reporting_enabled = mkOption {
+            description = lib.mdDoc "Whether to allow anonymous usage reporting to stats.grafana.net.";
+            default = true;
+            type = types.bool;
+          };
+        };
       };
+    };
 
-      user = mkOption {
-        description = lib.mdDoc "Database user.";
-        default = "root";
-        type = types.str;
-      };
+    provision = {
+      enable = mkEnableOption (lib.mdDoc "provision");
 
-      password = mkOption {
+      datasources = mkOption {
         description = lib.mdDoc ''
-          Database password.
-          This option is mutual exclusive with the passwordFile option.
+          Declaratively provision Grafana's datasources.
         '';
-        default = "";
-        type = types.str;
-      };
+        default = {};
+        type = submodule' {
+          options.settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana datasource configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.datasources.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = lib.mdDoc "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                datasources = mkOption {
+                  description = lib.mdDoc "List of datasources to insert/update.";
+                  default = [];
+                  type = types.listOf grafanaTypes.datasourceConfig;
+                };
+
+                deleteDatasources = mkOption {
+                  description = lib.mdDoc "List of datasources that should be deleted from the database.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the datasource to delete.";
+                      type = types.str;
+                    };
+
+                    options.orgId = mkOption {
+                      description = lib.mdDoc "Organization ID of the datasource to delete.";
+                      type = types.int;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                datasources = [{
+                  name = "Graphite";
+                  type = "graphite";
+                }];
+
+                deleteDatasources = [{
+                  name = "Graphite";
+                  orgId = 1;
+                }];
+              }
+            '';
+          };
 
-      passwordFile = mkOption {
-        description = lib.mdDoc ''
-          File that containts the database password.
-          This option is mutual exclusive with the password option.
-        '';
-        default = null;
-        type = types.nullOr types.path;
+          options.path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML datasource configuration. Can't be used with
+              [](#opt-services.grafana.provision.datasources.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+        };
       };
 
-      path = mkOption {
-        description = lib.mdDoc "Database path.";
-        default = "${cfg.dataDir}/data/grafana.db";
-        defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
-        type = types.path;
-      };
 
-      connMaxLifetime = mkOption {
+      dashboards = mkOption {
         description = lib.mdDoc ''
-          Sets the maximum amount of time (in seconds) a connection may be reused.
-          For MySQL this setting should be shorter than the `wait_timeout' variable.
+          Declaratively provision Grafana's dashboards.
         '';
-        default = "unlimited";
-        example = 14400;
-        type = types.either types.int (types.enum [ "unlimited" ]);
-      };
-    };
+        default = {};
+        type = submodule' {
+          options.settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana dashboard configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.dashboards.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options.apiVersion = mkOption {
+                description = lib.mdDoc "Config file version.";
+                default = 1;
+                type = types.int;
+              };
+
+              options.providers = mkOption {
+                description = lib.mdDoc "List of dashboards to insert/update.";
+                default = [];
+                type = types.listOf grafanaTypes.dashboardConfig;
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                providers = [{
+                    name = "default";
+                    options.path = "/var/lib/grafana/dashboards";
+                }];
+              }
+            '';
+          };
 
-    provision = {
-      enable = mkEnableOption "provision";
-      datasources = mkOption {
-        description = lib.mdDoc "Grafana datasources configuration.";
-        default = [];
-        type = types.listOf grafanaTypes.datasourceConfig;
-        apply = x: map _filter x;
-      };
-      dashboards = mkOption {
-        description = lib.mdDoc "Grafana dashboard configuration.";
-        default = [];
-        type = types.listOf grafanaTypes.dashboardConfig;
-        apply = x: map _filter x;
+          options.path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML dashboard configuration. Can't be used with
+              [](#opt-services.grafana.provision.dashboards.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+        };
       };
+
+
       notifiers = mkOption {
         description = lib.mdDoc "Grafana notifier configuration.";
         default = [];
         type = types.listOf grafanaTypes.notifierConfig;
         apply = x: map _filter x;
       };
-    };
-
-    security = {
-      adminUser = mkOption {
-        description = lib.mdDoc "Default admin username.";
-        default = "admin";
-        type = types.str;
-      };
-
-      adminPassword = mkOption {
-        description = lib.mdDoc ''
-          Default admin password.
-          This option is mutual exclusive with the adminPasswordFile option.
-        '';
-        default = "admin";
-        type = types.str;
-      };
-
-      adminPasswordFile = mkOption {
-        description = lib.mdDoc ''
-          Default admin password.
-          This option is mutual exclusive with the `adminPassword` option.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      secretKey = mkOption {
-        description = lib.mdDoc "Secret key used for signing.";
-        default = "SW2YcwTIb9zpOOhoPsMm";
-        type = types.str;
-      };
-
-      secretKeyFile = mkOption {
-        description = lib.mdDoc "Secret key used for signing.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-    };
-
-    server = {
-      serveFromSubPath = mkOption {
-        description = lib.mdDoc "Serve Grafana from subpath specified in rootUrl setting";
-        default = false;
-        type = types.bool;
-      };
-    };
-
-    smtp = {
-      enable = mkEnableOption "smtp";
-      host = mkOption {
-        description = lib.mdDoc "Host to connect to.";
-        default = "localhost:25";
-        type = types.str;
-      };
-      user = mkOption {
-        description = lib.mdDoc "User used for authentication.";
-        default = "";
-        type = types.str;
-      };
-      password = mkOption {
-        description = lib.mdDoc ''
-          Password used for authentication.
-          This option is mutual exclusive with the passwordFile option.
-        '';
-        default = "";
-        type = types.str;
-      };
-      passwordFile = mkOption {
-        description = lib.mdDoc ''
-          Password used for authentication.
-          This option is mutual exclusive with the password option.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
-      fromAddress = mkOption {
-        description = lib.mdDoc "Email address used for sending.";
-        default = "admin@grafana.localhost";
-        type = types.str;
-      };
-    };
-
-    users = {
-      allowSignUp = mkOption {
-        description = lib.mdDoc "Disable user signup / registration.";
-        default = false;
-        type = types.bool;
-      };
-
-      allowOrgCreate = mkOption {
-        description = lib.mdDoc "Whether user is allowed to create organizations.";
-        default = false;
-        type = types.bool;
-      };
-
-      autoAssignOrg = mkOption {
-        description = lib.mdDoc "Whether to automatically assign new users to default org.";
-        default = true;
-        type = types.bool;
-      };
-
-      autoAssignOrgRole = mkOption {
-        description = lib.mdDoc "Default role new users will be auto assigned.";
-        default = "Viewer";
-        type = types.enum ["Viewer" "Editor"];
-      };
-    };
 
-    auth = {
-      disableLoginForm = mkOption {
-        description = lib.mdDoc "Set to true to disable (hide) the login form, useful if you use OAuth";
-        default = false;
-        type = types.bool;
-      };
 
-      anonymous = {
-        enable = mkOption {
-          description = lib.mdDoc "Whether to allow anonymous access.";
-          default = false;
-          type = types.bool;
-        };
-        org_name = mkOption {
-          description = lib.mdDoc "Which organization to allow anonymous access to.";
-          default = "Main Org.";
-          type = types.str;
-        };
-        org_role = mkOption {
-          description = lib.mdDoc "Which role anonymous users have in the organization.";
-          default = "Viewer";
-          type = types.str;
-        };
-      };
-      azuread = {
-        enable = mkOption {
-          description = lib.mdDoc "Whether to allow Azure AD OAuth.";
-          default = false;
-          type = types.bool;
-        };
-        allowSignUp = mkOption {
-          description = lib.mdDoc "Whether to allow sign up with Azure AD OAuth.";
-          default = false;
-          type = types.bool;
-        };
-        clientId = mkOption {
-          description = lib.mdDoc "Azure AD OAuth client ID.";
-          default = "";
-          type = types.str;
-        };
-        clientSecretFile = mkOption {
-          description = lib.mdDoc "Azure AD OAuth client secret.";
-          default = null;
-          type = types.nullOr types.path;
-        };
-        tenantId = mkOption {
-          description = lib.mdDoc ''
-            Tenant id used to create auth and token url. Default to "common"
-            , let user sign in with any tenant.
+      alerting = {
+        rules = {
+          path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML rules configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.rules.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
             '';
-          default = "common";
-          type = types.str;
-        };
-        allowedDomains = mkOption {
-          description = lib.mdDoc ''
-            To limit access to authenticated users who are members of one or more groups,
-            set allowedGroups to a comma- or space-separated list of group object IDs.
-            You can find object IDs for a specific group on the Azure portal.
-          '';
-          default = "";
-          type = types.str;
-        };
-        allowedGroups = mkOption {
-          description = lib.mdDoc ''
-            Limits access to users who belong to specific domains.
-            Separate domains with space or comma.
-          '';
-          default = "";
-          type = types.str;
-        };
-      };
-      google = {
-        enable = mkOption {
-          description = lib.mdDoc "Whether to allow Google OAuth2.";
-          default = false;
-          type = types.bool;
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana rules configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.rules.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#rules>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = lib.mdDoc "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                groups = mkOption {
+                  description = lib.mdDoc "List of rule groups to import or update.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the rule group. Required.";
+                      type = types.str;
+                    };
+
+                    options.folder = mkOption {
+                      description = lib.mdDoc "Name of the folder the rule group will be stored in. Required.";
+                      type = types.str;
+                    };
+
+                    options.interval = mkOption {
+                      description = lib.mdDoc "Interval that the rule group should be evaluated at. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteRules = mkOption {
+                  description = lib.mdDoc "List of alert rule UIDs that should be deleted.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = lib.mdDoc "Organization ID, default = 1";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.uid = mkOption {
+                      description = lib.mdDoc "Unique identifier for the rule. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                groups = [{
+                  orgId = 1;
+                  name = "my_rule_group";
+                  folder = "my_first_folder";
+                  interval = "60s";
+                  rules = [{
+                    uid = "my_id_1";
+                    title = "my_first_rule";
+                    condition = "A";
+                    data = [{
+                      refId = "A";
+                      datasourceUid = "-100";
+                      model = {
+                        conditions = [{
+                          evaluator = {
+                            params = [ 3 ];
+                            type = "git";
+                          };
+                          operator.type = "and";
+                          query.params = [ "A" ];
+                          reducer.type = "last";
+                          type = "query";
+                        }];
+                        datasource = {
+                          type = "__expr__";
+                          uid = "-100";
+                        };
+                        expression = "1==0";
+                        intervalMs = 1000;
+                        maxDataPoints = 43200;
+                        refId = "A";
+                        type = "math";
+                      };
+                    }];
+                    dashboardUid = "my_dashboard";
+                    panelId = 123;
+                    noDataState = "Alerting";
+                    for = "60s";
+                    annotations.some_key = "some_value";
+                    labels.team = "sre_team1";
+                  }];
+                }];
+
+                deleteRules = [{
+                  orgId = 1;
+                  uid = "my_id_1";
+                }];
+              }
+            '';
+          };
         };
-        allowSignUp = mkOption {
-          description = lib.mdDoc "Whether to allow sign up with Google OAuth2.";
-          default = false;
-          type = types.bool;
+
+        contactPoints = {
+          path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML contact points configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.contactPoints.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana contact points configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.contactPoints.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#contact-points>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = lib.mdDoc "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                contactPoints = mkOption {
+                  description = lib.mdDoc "List of contact points to import or update.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the contact point. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteContactPoints = mkOption {
+                  description = lib.mdDoc "List of receivers that should be deleted.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = lib.mdDoc "Organization ID, default = 1.";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.uid = mkOption {
+                      description = lib.mdDoc "Unique identifier for the receiver. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                contactPoints = [{
+                  orgId = 1;
+                  name = "cp_1";
+                  receivers = [{
+                    uid = "first_uid";
+                    type = "prometheus-alertmanager";
+                    settings.url = "http://test:9000";
+                  }];
+                }];
+
+                deleteContactPoints = [{
+                  orgId = 1;
+                  uid = "first_uid";
+                }];
+              }
+            '';
+          };
         };
-        clientId = mkOption {
-          description = lib.mdDoc "Google OAuth2 client ID.";
-          default = "";
-          type = types.str;
+
+        policies = {
+          path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML notification policies configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.policies.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana notification policies configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.policies.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#notification-policies>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = lib.mdDoc "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                policies = mkOption {
+                  description = lib.mdDoc "List of contact points to import or update.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+                  });
+                };
+
+                resetPolicies = mkOption {
+                  description = lib.mdDoc "List of orgIds that should be reset to the default policy.";
+                  default = [];
+                  type = types.listOf types.int;
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                policies = [{
+                  orgId = 1;
+                  receiver = "grafana-default-email";
+                  group_by = [ "..." ];
+                  matchers = [
+                    "alertname = Watchdog"
+                    "severity =~ \"warning|critical\""
+                  ];
+                  mute_time_intervals = [
+                    "abc"
+                  ];
+                  group_wait = "30s";
+                  group_interval = "5m";
+                  repeat_interval = "4h";
+                }];
+
+                resetPolicies = [
+                  1
+                ];
+              }
+            '';
+          };
         };
-        clientSecretFile = mkOption {
-          description = lib.mdDoc "Google OAuth2 client secret.";
-          default = null;
-          type = types.nullOr types.path;
+
+        templates = {
+          path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML templates configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.templates.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana templates configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.templates.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#templates>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = lib.mdDoc "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                templates = mkOption {
+                  description = lib.mdDoc "List of templates to import or update.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the template, must be unique. Required.";
+                      type = types.str;
+                    };
+
+                    options.template = mkOption {
+                      description = lib.mdDoc "Alerting with a custom text template";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteTemplates = mkOption {
+                  description = lib.mdDoc "List of alert rule UIDs that should be deleted.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = lib.mdDoc "Organization ID, default = 1.";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the template, must be unique. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                templates = [{
+                  orgId = 1;
+                  name = "my_first_template";
+                  template = "Alerting with a custom text template";
+                }];
+
+                deleteTemplates = [{
+                  orgId = 1;
+                  name = "my_first_template";
+                }];
+              }
+            '';
+          };
         };
-      };
-    };
 
-    analytics.reporting = {
-      enable = mkOption {
-        description = lib.mdDoc "Whether to allow anonymous usage reporting to stats.grafana.net.";
-        default = true;
-        type = types.bool;
+        muteTimings = {
+          path = mkOption {
+            description = lib.mdDoc ''
+              Path to YAML mute timings configuration. Can't be used with
+              [](#opt-services.grafana.provision.alerting.muteTimings.settings) simultaneously.
+              Can be either a directory or a single YAML file. Will end up in the store.
+            '';
+            default = null;
+            type = types.nullOr types.path;
+          };
+
+          settings = mkOption {
+            description = lib.mdDoc ''
+              Grafana mute timings configuration in Nix. Can't be used with
+              [](#opt-services.grafana.provision.alerting.muteTimings.path) simultaneously. See
+              <https://grafana.com/docs/grafana/latest/administration/provisioning/#mute-timings>
+              for supported options.
+            '';
+            default = null;
+            type = types.nullOr (types.submodule {
+              options = {
+                apiVersion = mkOption {
+                  description = lib.mdDoc "Config file version.";
+                  default = 1;
+                  type = types.int;
+                };
+
+                muteTimes = mkOption {
+                  description = lib.mdDoc "List of mute time intervals to import or update.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
+
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the mute time interval, must be unique. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+
+                deleteMuteTimes = mkOption {
+                  description = lib.mdDoc "List of mute time intervals that should be deleted.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    options.orgId = mkOption {
+                      description = lib.mdDoc "Organization ID, default = 1.";
+                      default = 1;
+                      type = types.int;
+                    };
+
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the mute time interval, must be unique. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
+
+                muteTimes = [{
+                  orgId = 1;
+                  name = "mti_1";
+                  time_intervals = [{
+                    times = [{
+                      start_time = "06:00";
+                      end_time = "23:59";
+                    }];
+                    weekdays = [
+                      "monday:wednesday"
+                      "saturday"
+                      "sunday"
+                    ];
+                    months = [
+                      "1:3"
+                      "may:august"
+                      "december"
+                    ];
+                    years = [
+                      "2020:2022"
+                      "2030"
+                    ];
+                    days_of_month = [
+                      "1:5"
+                      "-3:-1"
+                    ];
+                  }];
+                }];
+
+                deleteMuteTimes = [{
+                  orgId = 1;
+                  name = "mti_1";
+                }];
+              }
+            '';
+          };
+        };
       };
     };
-
-    extraOptions = mkOption {
-      description = lib.mdDoc ''
-        Extra configuration options passed as env variables as specified in
-        [documentation](http://docs.grafana.org/installation/configuration/),
-        but without GF_ prefix
-      '';
-      default = {};
-      type = with types; attrsOf (either str path);
-    };
   };
 
   config = mkIf cfg.enable {
-    warnings = flatten [
-      (optional (
-        cfg.database.password != opt.database.password.default ||
-        cfg.security.adminPassword != opt.security.adminPassword.default
-      ) "Grafana passwords will be stored as plaintext in the Nix store!")
-      (optional (
-        any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
-      ) "Datasource passwords will be stored as plaintext in the Nix store!")
+    warnings = let
+      doesntUseFileProvider = opt: defaultValue:
+        let
+          regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$";
+        in builtins.match regex opt == null;
+    in
+      # Ensure that no custom credentials are leaked into the Nix store. Unless the default value
+      # is specified, this can be achieved by using the file/env provider:
+      # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
       (optional (
+        doesntUseFileProvider cfg.settings.database.password "" ||
+        doesntUseFileProvider cfg.settings.security.admin_password "admin"
+      ) ''
+        Grafana passwords will be stored as plaintext in the Nix store!
+        Use file provider or an env-var instead.
+      '')
+      # Warn about deprecated notifiers.
+      ++ (optional (cfg.provision.notifiers != []) ''
+        Notifiers are deprecated upstream and will be removed in Grafana 10.
+        Use `services.grafana.provision.alerting.contactPoints` instead.
+      '')
+      # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
+      # only uses file/env providers.
+      ++ (optional (
+        let
+          datasourcesToCheck = optionals
+            (cfg.provision.datasources.settings != null)
+            cfg.provision.datasources.settings.datasources;
+          declarationUnsafe = { secureJsonData, ... }:
+            secureJsonData != null
+            && any (flip doesntUseFileProvider null) (attrValues secureJsonData);
+        in any declarationUnsafe datasourcesToCheck
+      ) ''
+        Declarations in the `secureJsonData`-block of a datasource will be leaked to the
+        Nix store unless a file-provider or an env-var is used!
+      '')
+      ++ (optional (
         any (x: x.secure_settings != null) cfg.provision.notifiers
-      ) "Notifier secure settings will be stored as plaintext in the Nix store!")
-    ];
+      ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.");
 
     environment.systemPackages = [ cfg.package ];
 
     assertions = [
       {
-        assertion = cfg.database.password != opt.database.password.default -> cfg.database.passwordFile == null;
-        message = "Cannot set both password and passwordFile";
+        assertion = cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
+        message = "Cannot set both datasources settings and datasources path";
+      }
+      {
+        assertion = let
+          prometheusIsNotDirect = opt: all
+          ({ type, access, ... }: type == "prometheus" -> access != "direct")
+          opt;
+        in
+          cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
+        message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
+      }
+      {
+        assertion = cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
+        message = "Cannot set both dashboards settings and dashboards path";
+      }
+      {
+        assertion = cfg.provision.alerting.rules.settings == null || cfg.provision.alerting.rules.path == null;
+        message = "Cannot set both rules settings and rules path";
+      }
+      {
+        assertion = cfg.provision.alerting.contactPoints.settings == null || cfg.provision.alerting.contactPoints.path == null;
+        message = "Cannot set both contact points settings and contact points path";
       }
       {
-        assertion = cfg.security.adminPassword != opt.security.adminPassword.default -> cfg.security.adminPasswordFile == null;
-        message = "Cannot set both adminPassword and adminPasswordFile";
+        assertion = cfg.provision.alerting.policies.settings == null || cfg.provision.alerting.policies.path == null;
+        message = "Cannot set both policies settings and policies path";
       }
       {
-        assertion = cfg.security.secretKey != opt.security.secretKey.default -> cfg.security.secretKeyFile == null;
-        message = "Cannot set both secretKey and secretKeyFile";
+        assertion = cfg.provision.alerting.templates.settings == null || cfg.provision.alerting.templates.path == null;
+        message = "Cannot set both templates settings and templates path";
       }
       {
-        assertion = cfg.smtp.password != opt.smtp.password.default -> cfg.smtp.passwordFile == null;
-        message = "Cannot set both password and passwordFile";
+        assertion = cfg.provision.alerting.muteTimings.settings == null || cfg.provision.alerting.muteTimings.path == null;
+        message = "Cannot set both mute timings settings and mute timings path";
       }
     ];
 
@@ -726,41 +1254,11 @@ in {
       description = "Grafana Service Daemon";
       wantedBy = ["multi-user.target"];
       after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
-      environment = {
-        QT_QPA_PLATFORM = "offscreen";
-      } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions;
       script = ''
         set -o errexit -o pipefail -o nounset -o errtrace
         shopt -s inherit_errexit
 
-        ${optionalString (cfg.auth.azuread.clientSecretFile != null) ''
-          GF_AUTH_AZUREAD_CLIENT_SECRET="$(<${escapeShellArg cfg.auth.azuread.clientSecretFile})"
-          export GF_AUTH_AZUREAD_CLIENT_SECRET
-        ''}
-        ${optionalString (cfg.auth.google.clientSecretFile != null) ''
-          GF_AUTH_GOOGLE_CLIENT_SECRET="$(<${escapeShellArg cfg.auth.google.clientSecretFile})"
-          export GF_AUTH_GOOGLE_CLIENT_SECRET
-        ''}
-        ${optionalString (cfg.database.passwordFile != null) ''
-          GF_DATABASE_PASSWORD="$(<${escapeShellArg cfg.database.passwordFile})"
-          export GF_DATABASE_PASSWORD
-        ''}
-        ${optionalString (cfg.security.adminPasswordFile != null) ''
-          GF_SECURITY_ADMIN_PASSWORD="$(<${escapeShellArg cfg.security.adminPasswordFile})"
-          export GF_SECURITY_ADMIN_PASSWORD
-        ''}
-        ${optionalString (cfg.security.secretKeyFile != null) ''
-          GF_SECURITY_SECRET_KEY="$(<${escapeShellArg cfg.security.secretKeyFile})"
-          export GF_SECURITY_SECRET_KEY
-        ''}
-        ${optionalString (cfg.smtp.passwordFile != null) ''
-          GF_SMTP_PASSWORD="$(<${escapeShellArg cfg.smtp.passwordFile})"
-          export GF_SMTP_PASSWORD
-        ''}
-        ${optionalString cfg.provision.enable ''
-          export GF_PATHS_PROVISIONING=${provisionConfDir};
-        ''}
-        exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir}
+        exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} -config ${configFile}
       '';
       serviceConfig = {
         WorkingDirectory = cfg.dataDir;
@@ -768,8 +1266,8 @@ in {
         RuntimeDirectory = "grafana";
         RuntimeDirectoryMode = "0755";
         # Hardening
-        AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
-        CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
+        AmbientCapabilities = lib.mkIf (cfg.settings.server.http_port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = if (cfg.settings.server.http_port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
         DeviceAllow = [ "" ];
         LockPersonality = true;
         NoNewPrivileges = true;
@@ -792,7 +1290,10 @@ in {
         SystemCallArchitectures = "native";
         # Upstream grafana is not setting SystemCallFilter for compatibility
         # reasons, see https://github.com/grafana/grafana/pull/40176
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ];
         UMask = "0027";
       };
       preStart = ''
diff --git a/nixpkgs/nixos/modules/services/monitoring/graphite.nix b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
index 8edb2ca09974..65c91b8f79bb 100644
--- a/nixpkgs/nixos/modules/services/monitoring/graphite.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
@@ -94,7 +94,7 @@ in {
       port = mkOption {
         description = lib.mdDoc "Graphite web frontend port.";
         default = 8080;
-        type = types.int;
+        type = types.port;
       };
 
       extraConfig = mkOption {
@@ -154,14 +154,14 @@ in {
       };
 
       blacklist = mkOption {
-        description = lib.mdDoc "Any metrics received which match one of the experssions will be dropped.";
+        description = lib.mdDoc "Any metrics received which match one of the expressions will be dropped.";
         default = null;
         type = types.nullOr types.str;
         example = "^some\\.noisy\\.metric\\.prefix\\..*";
       };
 
       whitelist = mkOption {
-        description = lib.mdDoc "Only metrics received which match one of the experssions will be persisted.";
+        description = lib.mdDoc "Only metrics received which match one of the expressions will be persisted.";
         default = null;
         type = types.nullOr types.str;
         example = ".*";
@@ -225,7 +225,7 @@ in {
       port = mkOption {
         description = lib.mdDoc "Seyren listening port.";
         default = 8081;
-        type = types.int;
+        type = types.port;
       };
 
       seyrenUrl = mkOption {
diff --git a/nixpkgs/nixos/modules/services/monitoring/hdaps.nix b/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
index 2cad3b84d847..59b8b9b3c054 100644
--- a/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
@@ -9,10 +9,10 @@ in
 {
   options = {
     services.hdapsd.enable = mkEnableOption
-      ''
+      (lib.mdDoc ''
         Hard Drive Active Protection System Daemon,
         devices are detected and managed automatically by udev and systemd
-      '';
+      '');
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/monitoring/heapster.nix b/nixpkgs/nixos/modules/services/monitoring/heapster.nix
index 2f2467477ae2..fc63276b62f7 100644
--- a/nixpkgs/nixos/modules/services/monitoring/heapster.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/heapster.nix
@@ -6,11 +6,7 @@ let
   cfg = config.services.heapster;
 in {
   options.services.heapster = {
-    enable = mkOption {
-      description = lib.mdDoc "Whether to enable heapster monitoring";
-      default = false;
-      type = types.bool;
-    };
+    enable = mkEnableOption (lib.mdDoc "Heapster monitoring");
 
     source = mkOption {
       description = lib.mdDoc "Heapster metric source";
diff --git a/nixpkgs/nixos/modules/services/monitoring/incron.nix b/nixpkgs/nixos/modules/services/monitoring/incron.nix
index 53cbe1a9e26a..3766f1fa238d 100644
--- a/nixpkgs/nixos/modules/services/monitoring/incron.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/incron.nix
@@ -17,10 +17,10 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the incron daemon.
 
-          Note that commands run under incrontab only support common Nix profiles for the <envar>PATH</envar> provided variable.
+          Note that commands run under incrontab only support common Nix profiles for the {env}`PATH` provided variable.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix b/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
index 9cdb0e4495a4..727b694047b4 100644
--- a/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
@@ -57,7 +57,7 @@ let
 in
 {
   options.services.kapacitor = {
-    enable = mkEnableOption "kapacitor";
+    enable = mkEnableOption (lib.mdDoc "kapacitor");
 
     dataDir = mkOption {
       type = types.path;
@@ -66,7 +66,7 @@ in
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 9092;
       description = lib.mdDoc "Port of Kapacitor";
     };
@@ -109,7 +109,7 @@ in
     };
 
     defaultDatabase = {
-      enable = mkEnableOption "kapacitor.defaultDatabase";
+      enable = mkEnableOption (lib.mdDoc "kapacitor.defaultDatabase");
 
       url = mkOption {
         description = lib.mdDoc "The URL to an InfluxDB server that serves as the default database";
@@ -129,7 +129,7 @@ in
     };
 
     alerta = {
-      enable = mkEnableOption "kapacitor alerta integration";
+      enable = mkEnableOption (lib.mdDoc "kapacitor alerta integration");
 
       url = mkOption {
         description = lib.mdDoc "The URL to the Alerta REST API";
diff --git a/nixpkgs/nixos/modules/services/monitoring/karma.nix b/nixpkgs/nixos/modules/services/monitoring/karma.nix
new file mode 100644
index 000000000000..85dbc81f443f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/karma.nix
@@ -0,0 +1,128 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.services.karma;
+  yaml = pkgs.formats.yaml { };
+in
+{
+  options.services.karma = {
+    enable = mkEnableOption (mdDoc "the Karma dashboard service");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.karma;
+      defaultText = literalExpression "pkgs.karma";
+      description = mdDoc ''
+        The Karma package that should be used.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.path;
+      default = yaml.generate "karma.yaml" cfg.settings;
+      defaultText = "A configuration file generated from the provided nix attributes settings option.";
+      description = mdDoc ''
+        A YAML config file which can be used to configure karma instead of the nix-generated file.
+      '';
+      example = "/etc/karma/karma.conf";
+    };
+
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = {};
+      description = mdDoc ''
+        Additional environment variables to provide to karma.
+      '';
+      example = {
+        ALERTMANAGER_URI = "https://alertmanager.example.com";
+        ALERTMANAGER_NAME= "single";
+      };
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Whether to open ports in the firewall needed for karma to function.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = mdDoc ''
+        Extra command line options.
+      '';
+      example = [
+        "--alertmanager.timeout 10s"
+      ];
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = yaml.type;
+
+        options.listen = {
+          address = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            description = mdDoc ''
+              Hostname or IP to listen on.
+            '';
+            example = "[::]";
+          };
+
+          port = mkOption {
+            type = types.port;
+            default = 8080;
+            description = mdDoc ''
+              HTTP port to listen on.
+            '';
+            example = 8182;
+          };
+        };
+      };
+      default = {
+        listen = {
+          address = "127.0.0.1";
+        };
+      };
+      description = mdDoc ''
+        Karma dashboard configuration as nix attributes.
+
+        Reference: <https://github.com/prymitive/karma/blob/main/docs/CONFIGURATION.md>
+      '';
+      example = {
+        listen = {
+          address = "192.168.1.4";
+          port = "8000";
+          prefix = "/dashboard";
+        };
+        alertmanager = {
+          interval = "15s";
+          servers = [
+            {
+              name = "prod";
+              uri = "http://alertmanager.example.com";
+            }
+          ];
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.karma = {
+      description = "Alert dashboard for Prometheus Alertmanager";
+      wantedBy = [ "multi-user.target" ];
+      environment = cfg.environment;
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        Restart = "on-failure";
+        ExecStart = "${pkgs.karma}/bin/karma --config.file ${cfg.configFile} ${concatStringsSep " " cfg.extraOptions}";
+      };
+    };
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.listen.port ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/kthxbye.nix b/nixpkgs/nixos/modules/services/monitoring/kthxbye.nix
new file mode 100644
index 000000000000..3f988dcb722f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/kthxbye.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, lib, ... }:
+with lib;
+
+let
+  cfg = config.services.kthxbye;
+in
+
+{
+  options.services.kthxbye = {
+    enable = mkEnableOption (mdDoc "kthxbye alert acknowledgement management daemon");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.kthxbye;
+      defaultText = literalExpression "pkgs.kthxbye";
+      description = mdDoc ''
+        The kthxbye package that should be used.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Whether to open ports in the firewall needed for the daemon to function.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = mdDoc ''
+        Extra command line options.
+
+        Documentation can be found [here](https://github.com/prymitive/kthxbye/blob/main/README.md).
+      '';
+      example = literalExpression ''
+        [
+          "-extend-with-prefix 'ACK!'"
+        ];
+      '';
+    };
+
+    alertmanager = {
+      timeout = mkOption {
+        type = types.str;
+        default = "1m0s";
+        description = mdDoc ''
+          Alertmanager request timeout duration in the [time.Duration](https://pkg.go.dev/time#ParseDuration) format.
+        '';
+        example = "30s";
+      };
+      uri = mkOption {
+        type = types.str;
+        default = "http://localhost:9093";
+        description = mdDoc ''
+          Alertmanager URI to use.
+        '';
+        example = "https://alertmanager.example.com";
+      };
+    };
+
+    extendBy = mkOption {
+      type = types.str;
+      default = "15m0s";
+      description = mdDoc ''
+        Extend silences by adding DURATION seconds.
+
+        DURATION should be provided in the [time.Duration](https://pkg.go.dev/time#ParseDuration) format.
+      '';
+      example = "6h0m0s";
+    };
+
+    extendIfExpiringIn = mkOption {
+      type = types.str;
+      default = "5m0s";
+      description = mdDoc ''
+        Extend silences that are about to expire in the next DURATION seconds.
+
+        DURATION should be provided in the [time.Duration](https://pkg.go.dev/time#ParseDuration) format.
+      '';
+      example = "1m0s";
+    };
+
+    extendWithPrefix = mkOption {
+      type = types.str;
+      default = "ACK!";
+      description = mdDoc ''
+        Extend silences with comment starting with PREFIX string.
+      '';
+      example = "!perma-silence";
+    };
+
+    interval = mkOption {
+      type = types.str;
+      default = "45s";
+      description = mdDoc ''
+        Silence check interval duration in the [time.Duration](https://pkg.go.dev/time#ParseDuration) format.
+      '';
+      example = "30s";
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = mdDoc ''
+        The address to listen on for HTTP requests.
+      '';
+      example = "127.0.0.1";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = mdDoc ''
+        The port to listen on for HTTP requests.
+      '';
+    };
+
+    logJSON = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Format logged messages as JSON.
+      '';
+    };
+
+    maxDuration = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      description = mdDoc ''
+        Maximum duration of a silence, it won't be extended anymore after reaching it.
+
+        Duration should be provided in the [time.Duration](https://pkg.go.dev/time#ParseDuration) format.
+      '';
+      example = "30d";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.kthxbye = {
+      description = "kthxbye Alertmanager ack management daemon";
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        ${cfg.package}/bin/kthxbye \
+          -alertmanager.timeout ${cfg.alertmanager.timeout} \
+          -alertmanager.uri ${cfg.alertmanager.uri} \
+          -extend-by ${cfg.extendBy} \
+          -extend-if-expiring-in ${cfg.extendIfExpiringIn} \
+          -extend-with-prefix ${cfg.extendWithPrefix} \
+          -interval ${cfg.interval} \
+          -listen ${cfg.listenAddress}:${toString cfg.port} \
+          ${optionalString cfg.logJSON "-log-json"} \
+          ${optionalString (cfg.maxDuration != null) "-max-duration ${cfg.maxDuration}"} \
+          ${concatStringsSep " " cfg.extraOptions}
+      '';
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/loki.nix b/nixpkgs/nixos/modules/services/monitoring/loki.nix
index d73e2abb71d2..f3b97e9151ea 100644
--- a/nixpkgs/nixos/modules/services/monitoring/loki.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/loki.nix
@@ -12,7 +12,7 @@ let
 
 in {
   options.services.loki = {
-    enable = mkEnableOption "loki";
+    enable = mkEnableOption (lib.mdDoc "loki");
 
     user = mkOption {
       type = types.str;
@@ -22,6 +22,8 @@ in {
       '';
     };
 
+    package = lib.mkPackageOptionMD pkgs "grafana-loki" { };
+
     group = mkOption {
       type = types.str;
       default = "loki";
@@ -78,7 +80,7 @@ in {
       '';
     }];
 
-    environment.systemPackages = [ pkgs.grafana-loki ]; # logcli
+    environment.systemPackages = [ cfg.package ]; # logcli
 
     users.groups.${cfg.group} = { };
     users.users.${cfg.user} = {
@@ -99,7 +101,7 @@ in {
                else cfg.configFile;
       in
       {
-        ExecStart = "${pkgs.grafana-loki}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
+        ExecStart = "${cfg.package}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
         User = cfg.user;
         Restart = "always";
         PrivateTmp = true;
diff --git a/nixpkgs/nixos/modules/services/monitoring/mackerel-agent.nix b/nixpkgs/nixos/modules/services/monitoring/mackerel-agent.nix
index 89c6d4d6c65b..67dc1bc19edd 100644
--- a/nixpkgs/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -7,24 +7,24 @@ let
   settingsFmt = pkgs.formats.toml {};
 in {
   options.services.mackerel-agent = {
-    enable = mkEnableOption "mackerel.io agent";
+    enable = mkEnableOption (lib.mdDoc "mackerel.io agent");
 
     # the upstream package runs as root, but doesn't seem to be strictly
     # necessary for basic functionality
-    runAsRoot = mkEnableOption "Whether to run as root.";
+    runAsRoot = mkEnableOption (lib.mdDoc "Whether to run as root");
 
-    autoRetirement = mkEnableOption ''
+    autoRetirement = mkEnableOption (lib.mdDoc ''
       Whether to automatically retire the host upon OS shutdown.
-    '';
+    '');
 
     apiKeyFile = mkOption {
       type = types.path;
       example = "/run/keys/mackerel-api-key";
-      description = ''
+      description = lib.mdDoc ''
         Path to file containing the Mackerel API key. The file should contain a
         single line of the following form:
 
-        <literallayout>apikey = "EXAMPLE_API_KEY"</literallayout>
+        `apikey = "EXAMPLE_API_KEY"`
       '';
     };
 
@@ -59,7 +59,7 @@ in {
         };
 
         options.diagnostic =
-          mkEnableOption "Collect memory usage for the agent itself";
+          mkEnableOption (lib.mdDoc "Collect memory usage for the agent itself");
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/monitoring/metricbeat.nix b/nixpkgs/nixos/modules/services/monitoring/metricbeat.nix
index 14066da1be81..310c9d8ed509 100644
--- a/nixpkgs/nixos/modules/services/monitoring/metricbeat.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/metricbeat.nix
@@ -19,7 +19,7 @@ in
 
     services.metricbeat = {
 
-      enable = mkEnableOption "metricbeat";
+      enable = mkEnableOption (lib.mdDoc "metricbeat");
 
       package = mkOption {
         type = types.package;
@@ -99,10 +99,10 @@ in
               type = types.listOf settingsFormat.type;
               default = [];
               internal = true;
-              description = ''
-                The metric collecting modules. Use <xref linkend="opt-services.metricbeat.modules"/> instead.
+              description = lib.mdDoc ''
+                The metric collecting modules. Use [](#opt-services.metricbeat.modules) instead.
 
-                See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html"/>.
+                See <https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html>.
               '';
             };
           };
diff --git a/nixpkgs/nixos/modules/services/monitoring/mimir.nix b/nixpkgs/nixos/modules/services/monitoring/mimir.nix
index 87f7af7855e1..edca9b7be4ff 100644
--- a/nixpkgs/nixos/modules/services/monitoring/mimir.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/mimir.nix
@@ -8,7 +8,7 @@ let
   settingsFormat = pkgs.formats.yaml {};
 in {
   options.services.mimir = {
-    enable = mkEnableOption "mimir";
+    enable = mkEnableOption (lib.mdDoc "mimir");
 
     configuration = mkOption {
       type = (pkgs.formats.json {}).type;
@@ -25,6 +25,13 @@ in {
         Specify a configuration file that Mimir should use.
       '';
     };
+
+    package = mkOption {
+      default = pkgs.mimir;
+      defaultText = lib.literalExpression "pkgs.mimir";
+      type = types.package;
+      description = lib.mdDoc ''Mimir package to use.'';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -53,7 +60,7 @@ in {
                else cfg.configFile;
       in
       {
-        ExecStart = "${pkgs.mimir}/bin/mimir --config.file=${conf}";
+        ExecStart = "${cfg.package}/bin/mimir --config.file=${conf}";
         DynamicUser = true;
         Restart = "always";
         ProtectSystem = "full";
diff --git a/nixpkgs/nixos/modules/services/monitoring/monit.nix b/nixpkgs/nixos/modules/services/monitoring/monit.nix
index 6ce5b44eb279..a22bbc9046ba 100644
--- a/nixpkgs/nixos/modules/services/monitoring/monit.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/monit.nix
@@ -9,7 +9,7 @@ in
 {
   options.services.monit = {
 
-    enable = mkEnableOption "Monit";
+    enable = mkEnableOption (lib.mdDoc "Monit");
 
     config = mkOption {
       type = types.lines;
diff --git a/nixpkgs/nixos/modules/services/monitoring/nagios.nix b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
index a45d0c0e070b..8feff22c1182 100644
--- a/nixpkgs/nixos/modules/services/monitoring/nagios.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
@@ -88,14 +88,14 @@ in
 
   options = {
     services.nagios = {
-      enable = mkEnableOption ''<link xlink:href="http://www.nagios.org/">Nagios</link> to monitor your system or network.'';
+      enable = mkEnableOption (lib.mdDoc ''[Nagios](http://www.nagios.org/) to monitor your system or network.'');
 
       objectDefs = mkOption {
-        description = "
+        description = lib.mdDoc ''
           A list of Nagios object configuration files that must define
           the hosts, host groups, services and contacts for the
           network that you want Nagios to monitor.
-        ";
+        '';
         type = types.listOf types.path;
         example = literalExpression "[ ./objects.cfg ]";
       };
@@ -104,18 +104,18 @@ in
         type = types.listOf types.package;
         default = with pkgs; [ monitoring-plugins msmtp mailutils ];
         defaultText = literalExpression "[pkgs.monitoring-plugins pkgs.msmtp pkgs.mailutils]";
-        description = "
-          Packages to be added to the Nagios <envar>PATH</envar>.
+        description = lib.mdDoc ''
+          Packages to be added to the Nagios {env}`PATH`.
           Typically used to add plugins, but can be anything.
-        ";
+        '';
       };
 
       mainConfigFile = mkOption {
         type = types.nullOr types.package;
         default = null;
-        description = "
+        description = lib.mdDoc ''
           If non-null, overrides the main configuration file of Nagios.
-        ";
+        '';
       };
 
       extraConfig = mkOption {
@@ -139,19 +139,19 @@ in
         type = types.package;
         default = nagiosCGICfgFile;
         defaultText = literalExpression "nagiosCGICfgFile";
-        description = "
+        description = lib.mdDoc ''
           Derivation for the configuration file of Nagios CGI scripts
           that can be used in web servers for running the Nagios web interface.
-        ";
+        '';
       };
 
       enableWebInterface = mkOption {
         type = types.bool;
         default = false;
-        description = "
+        description = lib.mdDoc ''
           Whether to enable the Nagios web interface.  You should also
-          enable Apache (<option>services.httpd.enable</option>).
-        ";
+          enable Apache ({option}`services.httpd.enable`).
+        '';
       };
 
       virtualHost = mkOption {
diff --git a/nixpkgs/nixos/modules/services/monitoring/netdata.nix b/nixpkgs/nixos/modules/services/monitoring/netdata.nix
index e20eaf3b1440..bd0dea83e1a8 100644
--- a/nixpkgs/nixos/modules/services/monitoring/netdata.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/netdata.nix
@@ -49,7 +49,7 @@ let
 in {
   options = {
     services.netdata = {
-      enable = mkEnableOption "netdata";
+      enable = mkEnableOption (lib.mdDoc "netdata");
 
       package = mkOption {
         type = types.package;
@@ -169,6 +169,20 @@ in {
           See: <https://learn.netdata.cloud/docs/agent/anonymous-statistics>
         '';
       };
+
+      deadlineBeforeStopSec = mkOption {
+        type = types.int;
+        default = 120;
+        description = lib.mdDoc ''
+          In order to detect when netdata is misbehaving, we run a concurrent task pinging netdata (wait-for-netdata-up)
+          in the systemd unit.
+
+          If after a while, this task does not succeed, we stop the unit and mark it as failed.
+
+          You can control this deadline in seconds with this option, it's useful to bump it
+          if you have (1) a lot of data (2) doing upgrades (3) have low IOPS/throughput.
+        '';
+      };
     };
   };
 
@@ -205,7 +219,7 @@ in {
           while [ "$(${pkgs.netdata}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done
         '';
 
-        TimeoutStopSec = 60;
+        TimeoutStopSec = cfg.deadlineBeforeStopSec;
         Restart = "on-failure";
         # User and group
         User = cfg.user;
diff --git a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.md b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.md
index d93134a4cc76..eac07e0cc9fe 100644
--- a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.md
+++ b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.md
@@ -17,7 +17,6 @@ services.parsedmarc = {
     host = "imap.example.com";
     user = "alice@example.com";
     password = "/path/to/imap_password_file";
-    watch = true;
   };
   provision.geoIp = false; # Not recommended!
 };
@@ -26,7 +25,7 @@ services.parsedmarc = {
 Note that GeoIP provisioning is disabled in the example for
 simplicity, but should be turned on for fully functional reports.
 
-## Local mail
+## Local mail {#module-services-parsedmarc-local-mail}
 Instead of watching an external inbox, a local inbox can be
 automatically provisioned. The recipient's name is by default set to
 `dmarc`, but can be configured in
@@ -50,7 +49,7 @@ services.parsedmarc = {
 };
 ```
 
-## Grafana and GeoIP
+## Grafana and GeoIP {#module-services-parsedmarc-grafana-geoip}
 The reports can be visualized and summarized with parsedmarc's
 official Grafana dashboard. For all views to work, and for the data to
 be complete, GeoIP databases are also required. The following example
diff --git a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix
index b0858184b5fc..44fc359b6a7d 100644
--- a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix
@@ -20,9 +20,9 @@ in
 {
   options.services.parsedmarc = {
 
-    enable = lib.mkEnableOption ''
+    enable = lib.mkEnableOption (lib.mdDoc ''
       parsedmarc, a DMARC report monitoring service
-    '';
+    '');
 
     provision = {
       localMail = {
@@ -123,7 +123,10 @@ in
             host = "imap.example.com";
             user = "alice@example.com";
             password = { _secret = "/run/keys/imap_password" };
+          };
+          mailbox = {
             watch = true;
+            batch_size = 30;
           };
           splunk_hec = {
             url = "https://splunkhec.example.com";
@@ -170,6 +173,24 @@ in
             };
           };
 
+          mailbox = {
+            watch = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Use the IMAP IDLE command to process messages as they arrive.
+              '';
+            };
+
+            delete = lib.mkOption {
+              type = lib.types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                Delete messages after processing them, instead of archiving them.
+              '';
+            };
+          };
+
           imap = {
             host = lib.mkOption {
               type = lib.types.str;
@@ -216,22 +237,6 @@ in
               '';
               apply = x: if isAttrs x || x == null then x else { _secret = x; };
             };
-
-            watch = lib.mkOption {
-              type = lib.types.bool;
-              default = true;
-              description = lib.mdDoc ''
-                Use the IMAP IDLE command to process messages as they arrive.
-              '';
-            };
-
-            delete = lib.mkOption {
-              type = lib.types.bool;
-              default = false;
-              description = lib.mdDoc ''
-                Delete messages after processing them, instead of archiving them.
-              '';
-            };
           };
 
           smtp = {
@@ -360,6 +365,13 @@ in
 
   config = lib.mkIf cfg.enable {
 
+    warnings = let
+      deprecationWarning = optname: "Starting in 8.0.0, the `${optname}` option has been moved from the `services.parsedmarc.settings.imap`"
+        + "configuration section to the `services.parsedmarc.settings.mailbox` configuration section.";
+      hasImapOpt = lib.flip builtins.hasAttr cfg.settings.imap;
+      movedOptions = [ "reports_folder" "archive_folder" "watch" "delete" "test" "batch_size" ];
+    in builtins.map deprecationWarning (builtins.filter hasImapOpt movedOptions);
+
     services.elasticsearch.enable = lib.mkDefault cfg.provision.elasticsearch;
 
     services.geoipupdate = lib.mkIf cfg.provision.geoIp {
@@ -397,7 +409,7 @@ in
 
       provision = {
         enable = cfg.provision.grafana.datasource || cfg.provision.grafana.dashboard;
-        datasources =
+        datasources.settings.datasources =
           let
             esVersion = lib.getVersion config.services.elasticsearch.package;
           in
@@ -423,7 +435,7 @@ in
                 };
               }
             ];
-        dashboards = lib.mkIf cfg.provision.grafana.dashboard [{
+        dashboards.settings.providers = lib.mkIf cfg.provision.grafana.dashboard [{
           name = "parsedmarc";
           options.path = "${pkgs.python3Packages.parsedmarc.dashboard}";
         }];
@@ -444,6 +456,8 @@ in
           ssl = false;
           user = cfg.provision.localMail.recipientName;
           password = "${pkgs.writeText "imap-password" "@imap-password@"}";
+        };
+        mailbox = {
           watch = true;
         };
       })
@@ -494,7 +508,7 @@ in
             Group = "parsedmarc";
             DynamicUser = true;
             RuntimeDirectory = "parsedmarc";
-            RuntimeDirectoryMode = 0700;
+            RuntimeDirectoryMode = "0700";
             CapabilityBoundingSet = "";
             PrivateDevices = true;
             PrivateMounts = true;
@@ -525,8 +539,6 @@ in
     };
   };
 
-  # Don't edit the docbook xml directly, edit the md and generate it:
-  # `pandoc parsedmarc.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > parsedmarc.xml`
-  meta.doc = ./parsedmarc.xml;
+  meta.doc = ./parsedmarc.md;
   meta.maintainers = [ lib.maintainers.talyz ];
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.xml b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.xml
deleted file mode 100644
index 7167b52d0357..000000000000
--- a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-parsedmarc">
-  <title>parsedmarc</title>
-  <para>
-    <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>
-    is a service which parses incoming
-    <link xlink:href="https://dmarc.org/">DMARC</link> reports and
-    stores or sends them to a downstream service for further analysis.
-    In combination with Elasticsearch, Grafana and the included Grafana
-    dashboard, it provides a handy overview of DMARC reports over time.
-  </para>
-  <section xml:id="module-services-parsedmarc-basic-usage">
-    <title>Basic usage</title>
-    <para>
-      A very minimal setup which reads incoming reports from an external
-      email address and saves them to a local Elasticsearch instance
-      looks like this:
-    </para>
-    <programlisting language="bash">
-services.parsedmarc = {
-  enable = true;
-  settings.imap = {
-    host = &quot;imap.example.com&quot;;
-    user = &quot;alice@example.com&quot;;
-    password = &quot;/path/to/imap_password_file&quot;;
-    watch = true;
-  };
-  provision.geoIp = false; # Not recommended!
-};
-</programlisting>
-    <para>
-      Note that GeoIP provisioning is disabled in the example for
-      simplicity, but should be turned on for fully functional reports.
-    </para>
-  </section>
-  <section xml:id="local-mail">
-    <title>Local mail</title>
-    <para>
-      Instead of watching an external inbox, a local inbox can be
-      automatically provisioned. The recipient’s name is by default set
-      to <literal>dmarc</literal>, but can be configured in
-      <link xlink:href="options.html#opt-services.parsedmarc.provision.localMail.recipientName">services.parsedmarc.provision.localMail.recipientName</link>.
-      You need to add an MX record pointing to the host. More
-      concretely: for the example to work, an MX record needs to be set
-      up for <literal>monitoring.example.com</literal> and the complete
-      email address that should be configured in the domain’s dmarc
-      policy is <literal>dmarc@monitoring.example.com</literal>.
-    </para>
-    <programlisting language="bash">
-services.parsedmarc = {
-  enable = true;
-  provision = {
-    localMail = {
-      enable = true;
-      hostname = monitoring.example.com;
-    };
-    geoIp = false; # Not recommended!
-  };
-};
-</programlisting>
-  </section>
-  <section xml:id="grafana-and-geoip">
-    <title>Grafana and GeoIP</title>
-    <para>
-      The reports can be visualized and summarized with parsedmarc’s
-      official Grafana dashboard. For all views to work, and for the
-      data to be complete, GeoIP databases are also required. The
-      following example shows a basic deployment where the provisioned
-      Elasticsearch instance is automatically added as a Grafana
-      datasource, and the dashboard is added to Grafana as well.
-    </para>
-    <programlisting language="bash">
-services.parsedmarc = {
-  enable = true;
-  provision = {
-    localMail = {
-      enable = true;
-      hostname = url;
-    };
-    grafana = {
-      datasource = true;
-      dashboard = true;
-    };
-  };
-};
-
-# Not required, but recommended for full functionality
-services.geoipupdate = {
-  settings = {
-    AccountID = 000000;
-    LicenseKey = &quot;/path/to/license_key_file&quot;;
-  };
-};
-
-services.grafana = {
-  enable = true;
-  addr = &quot;0.0.0.0&quot;;
-  domain = url;
-  rootUrl = &quot;https://&quot; + url;
-  protocol = &quot;socket&quot;;
-  security = {
-    adminUser = &quot;admin&quot;;
-    adminPasswordFile = &quot;/path/to/admin_password_file&quot;;
-    secretKeyFile = &quot;/path/to/secret_key_file&quot;;
-  };
-};
-
-services.nginx = {
-  enable = true;
-  recommendedTlsSettings = true;
-  recommendedOptimisation = true;
-  recommendedGzipSettings = true;
-  recommendedProxySettings = true;
-  upstreams.grafana.servers.&quot;unix:/${config.services.grafana.socket}&quot; = {};
-  virtualHosts.${url} = {
-    root = config.services.grafana.staticRootPath;
-    enableACME = true;
-    forceSSL = true;
-    locations.&quot;/&quot;.tryFiles = &quot;$uri @grafana&quot;;
-    locations.&quot;@grafana&quot;.proxyPass = &quot;http://grafana&quot;;
-  };
-};
-users.users.nginx.extraGroups = [ &quot;grafana&quot; ];
-</programlisting>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
new file mode 100644
index 000000000000..b81d5f6db5e0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.alertmanagerIrcRelay;
+
+  configFormat = pkgs.formats.yaml { };
+  configFile = configFormat.generate "alertmanager-irc-relay.yml" cfg.settings;
+in
+{
+  options.services.prometheus.alertmanagerIrcRelay = {
+    enable = mkEnableOption (mdDoc "Alertmanager IRC Relay");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.alertmanager-irc-relay;
+      defaultText = literalExpression "pkgs.alertmanager-irc-relay";
+      description = mdDoc "Alertmanager IRC Relay package to use.";
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = mdDoc "Extra command line options to pass to alertmanager-irc-relay.";
+    };
+
+    settings = mkOption {
+      type = configFormat.type;
+      example = literalExpression ''
+        {
+          http_host = "localhost";
+          http_port = 8000;
+
+          irc_host = "irc.example.com";
+          irc_port = 7000;
+          irc_nickname = "myalertbot";
+
+          irc_channels = [
+            { name = "#mychannel"; }
+          ];
+        }
+      '';
+      description = mdDoc ''
+        Configuration for Alertmanager IRC Relay as a Nix attribute set.
+        For a reference, check out the
+        [example configuration](https://github.com/google/alertmanager-irc-relay#configuring-and-running-the-bot)
+        and the
+        [source code](https://github.com/google/alertmanager-irc-relay/blob/master/config.go).
+
+        Note: The webhook's URL MUST point to the IRC channel where the message
+        should be posted. For `#mychannel` from the example, this would be
+        `http://localhost:8080/mychannel`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.alertmanager-irc-relay = {
+      description = "Alertmanager IRC Relay";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/alertmanager-irc-relay \
+          -config ${configFile} \
+          ${escapeShellArgs cfg.extraFlags}
+        '';
+
+        DynamicUser = true;
+        NoNewPrivileges = true;
+
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation"
+          "~@privileged"
+          "~@reboot"
+          "~@setuid"
+          "~@swap"
+        ];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.oxzi ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 60e0523cc353..987f17c2c6e6 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -6,10 +6,12 @@ let
   cfg = config.services.prometheus.alertmanager;
   mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
 
-  checkedConfig = file: pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
-    ln -s ${file} $out
-    amtool check-config $out
-  '';
+  checkedConfig = file:
+    if cfg.checkConfig then
+      pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+        ln -s ${file} $out
+        amtool check-config $out
+      '' else file;
 
   alertmanagerYml = let
     yml = if cfg.configText != null then
@@ -34,13 +36,13 @@ in {
     (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.")
     (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] ''
       Due to incompatibility, the alertmanagerURL option has been removed,
-      please use 'services.prometheus2.alertmanagers' instead.
+      please use 'services.prometheus.alertmanagers' instead.
     '')
   ];
 
   options = {
     services.prometheus.alertmanager = {
-      enable = mkEnableOption "Prometheus Alertmanager";
+      enable = mkEnableOption (lib.mdDoc "Prometheus Alertmanager");
 
       package = mkOption {
         type = types.package;
@@ -70,6 +72,20 @@ in {
         '';
       };
 
+      checkConfig = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Check configuration with `amtool check-config`. The call to `amtool` is
+          subject to sandboxing by Nix.
+
+          If you use credentials stored in external files
+          (`environmentFile`, etc),
+          they will not be visible to `amtool`
+          and it will report errors, despite a correct configuration.
+        '';
+      };
+
       logFormat = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -107,7 +123,7 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 9093;
         description = lib.mdDoc ''
           Port to listen on for the web interface and API.
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
index 3bc61fba158f..19ee3ae6f7da 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  json = pkgs.formats.json { };
+  yaml = pkgs.formats.yaml { };
   cfg = config.services.prometheus;
   checkConfigEnabled =
     (lib.isBool cfg.checkConfig && cfg.checkConfig)
@@ -11,8 +11,6 @@ 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
@@ -33,12 +31,12 @@ let
     if checkConfigEnabled then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
-        { buildInputs = [ cfg.package ]; } ''
+        { buildInputs = [ cfg.package.cli ]; } ''
         ln -s ${file} $out
         promtool ${what} $out
       '' else file;
 
-  generatedPrometheusYml = json.generate "prometheus.yml" promConfig;
+  generatedPrometheusYml = yaml.generate "prometheus.yml" promConfig;
 
   # This becomes the main config file for Prometheus
   promConfig = {
@@ -73,7 +71,8 @@ let
     "--web.listen-address=${cfg.listenAddress}:${builtins.toString cfg.port}"
     "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
   ] ++ 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}"
+    ++ optional (cfg.webConfigFile != null) "--web.config.file=${cfg.webConfigFile}";
 
   filterValidPrometheus = filterAttrsListRecursive (n: v: !(n == "_module" || v == null));
   filterAttrsListRecursive = pred: x:
@@ -99,14 +98,14 @@ let
 
   mkDefOpt = type: defaultStr: description: mkOpt type (description + ''
 
-    Defaults to <literal>${defaultStr}</literal> in prometheus
-    when set to <literal>null</literal>.
+    Defaults to ````${defaultStr}```` in prometheus
+    when set to `null`.
   '');
 
   mkOpt = type: description: mkOption {
     type = types.nullOr type;
     default = null;
-    inherit description;
+    description = lib.mdDoc description;
   };
 
   mkSdConfigModule = extraOptions: types.submodule {
@@ -251,7 +250,7 @@ let
       authorization = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Sets the `Authorization` header on every scrape request with the configured credentials.
         '';
       };
@@ -288,7 +287,7 @@ let
 
         If honor_labels is set to "false", label conflicts are
         resolved by renaming conflicting labels in the scraped data
-        to "exported_&lt;original-label&gt;" (for example
+        to "exported_\<original-label\>" (for example
         "exported_instance", "exported_job") and then attaching
         server-side labels. This is useful for use cases such as
         federation, where all labels specified in the target should
@@ -299,10 +298,10 @@ let
         honor_timestamps controls whether Prometheus respects the timestamps present
         in scraped data.
 
-        If honor_timestamps is set to <literal>true</literal>, the timestamps of the metrics exposed
+        If honor_timestamps is set to `true`, the timestamps of the metrics exposed
         by the target will be used.
 
-        If honor_timestamps is set to <literal>false</literal>, the timestamps of the metrics exposed
+        If honor_timestamps is set to `false`, the timestamps of the metrics exposed
         by the target will be ignored.
       '';
 
@@ -323,13 +322,13 @@ let
       bearer_token = mkOpt types.str ''
         Sets the `Authorization` header on every scrape request with
         the configured bearer token. It is mutually exclusive with
-        <option>bearer_token_file</option>.
+        {option}`bearer_token_file`.
       '';
 
       bearer_token_file = mkOpt types.str ''
         Sets the `Authorization` header on every scrape request with
         the bearer token read from the configured file. It is mutually
-        exclusive with <option>bearer_token</option>.
+        exclusive with {option}`bearer_token`.
       '';
 
       tls_config = mkOpt promTypes.tls_config ''
@@ -379,7 +378,7 @@ let
       gce_sd_configs = mkOpt (types.listOf promTypes.gce_sd_config) ''
         List of Google Compute Engine service discovery configurations.
 
-        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config">the relevant Prometheus configuration docs</link>
+        See [the relevant Prometheus configuration docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config)
         for more detail.
       '';
 
@@ -591,7 +590,7 @@ let
 
     allow_stale = mkOpt types.bool ''
       Allow stale Consul results
-      (see <link xlink:href="https://www.consul.io/api/index.html#consistency-modes"/>).
+      (see <https://www.consul.io/api/index.html#consistency-modes>).
 
       Will reduce load on Consul.
     '';
@@ -632,16 +631,16 @@ let
         options = {
           name = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               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"/>
+              Services: <https://docs.docker.com/engine/api/v1.40/#operation/ServiceList>
+              Tasks: <https://docs.docker.com/engine/api/v1.40/#operation/TaskList>
+              Nodes: <https://docs.docker.com/engine/api/v1.40/#operation/NodeList>
             '';
           };
           values = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               Value for the filter.
             '';
           };
@@ -664,7 +663,7 @@ let
   promTypes.dockerswarm_sd_config = mkDockerSdConfigModule {
     role = mkOption {
       type = types.enum [ "services" "tasks" "nodes" ];
-      description = ''
+      description = lib.mdDoc ''
         Role of the targets to retrieve. Must be `services`, `tasks`, or `nodes`.
       '';
     };
@@ -707,12 +706,12 @@ let
 
       access_key = mkOpt types.str ''
         The AWS API key id. If blank, the environment variable
-        <literal>AWS_ACCESS_KEY_ID</literal> is used.
+        `AWS_ACCESS_KEY_ID` is used.
       '';
 
       secret_key = mkOpt types.str ''
         The AWS API key secret. If blank, the environment variable
-         <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
+         `AWS_SECRET_ACCESS_KEY` is used.
       '';
 
       profile = mkOpt types.str ''
@@ -738,8 +737,8 @@ let
           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>
+              description = lib.mdDoc ''
+                See [this list](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html)
                 for the available filters.
               '';
             };
@@ -747,7 +746,7 @@ let
             values = mkOption {
               type = types.listOf types.str;
               default = [ ];
-              description = ''
+              description = lib.mdDoc ''
                 Value of the filter.
               '';
             };
@@ -806,7 +805,7 @@ let
       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"/>.
+        query parameter section: <https://cloud.google.com/compute/docs/reference/latest/instances/list>.
       '';
 
       refresh_interval = mkDefOpt types.str "60s" ''
@@ -822,7 +821,7 @@ let
         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"/>
+        <https://cloud.google.com/vpc/docs/add-remove-network-tags>
       '';
     };
   };
@@ -917,7 +916,7 @@ let
             options = {
               role = mkOption {
                 type = types.str;
-                description = ''
+                description = lib.mdDoc ''
                   Selector role
                 '';
               };
@@ -976,11 +975,11 @@ let
       '';
 
       access_key = mkOpt types.str ''
-        The AWS API keys. If blank, the environment variable <literal>AWS_ACCESS_KEY_ID</literal> is used.
+        The AWS API keys. If blank, the environment variable `AWS_ACCESS_KEY_ID` is used.
       '';
 
       secret_key = mkOpt types.str ''
-        The AWS API keys. If blank, the environment variable <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
+        The AWS API keys. If blank, the environment variable `AWS_SECRET_ACCESS_KEY` is used.
       '';
 
       profile = mkOpt types.str ''
@@ -1030,14 +1029,14 @@ let
 
     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.
+      <https://docs.mesosphere.com/1.11/security/ent/iam-api/#passing-an-authentication-token>
+      It is mutually exclusive with `auth_token_file` 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.
+      <https://docs.mesosphere.com/1.11/security/ent/iam-api/#passing-an-authentication-token>
+      It is mutually exclusive with `auth_token` and other authentication mechanisms.
     '';
   };
 
@@ -1222,7 +1221,7 @@ let
 
       role = mkOption {
         type = types.enum [ "instance" "baremetal" ];
-        description = ''
+        description = lib.mdDoc ''
           Role of the targets to retrieve. Must be `instance` or `baremetal`.
         '';
       };
@@ -1299,7 +1298,7 @@ let
       };
 
       groups = mkOpt (types.listOf types.str) ''
-        A list of groups for which targets are retrieved, only supported when targeting the <literal>container</literal> role.
+        A list of groups for which targets are retrieved, only supported when targeting the `container` role.
         If omitted all containers owned by the requesting account are scraped.
       '';
 
@@ -1409,7 +1408,7 @@ let
       '';
 
       action =
-        mkDefOpt (types.enum [ "replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
+        mkDefOpt (types.enum [ "replace" "lowercase" "uppercase" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
           Action to perform based on regex matching.
         '';
     };
@@ -1563,13 +1562,7 @@ in
 
   options.services.prometheus = {
 
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable the Prometheus monitoring daemon.
-      '';
-    };
+    enable = mkEnableOption (lib.mdDoc "Prometheus monitoring daemon");
 
     package = mkOption {
       type = types.package;
@@ -1621,7 +1614,7 @@ in
 
         The following property holds: switching to a configuration
         (`switch-to-configuration`) that changes the prometheus
-        configuration only finishes successully when prometheus has finished
+        configuration only finishes successfully when prometheus has finished
         loading the new configuration.
       '';
     };
@@ -1725,20 +1718,28 @@ in
       '';
     };
 
+    webConfigFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Specifies which file should be used as web.config.file and be passed on startup.
+        See https://prometheus.io/docs/prometheus/latest/configuration/https/ for valid options.
+      '';
+    };
+
     checkConfig = mkOption {
       type = with types; either bool (enum [ "syntax-only" ]);
       default = true;
       example = "syntax-only";
-      description = ''
-        Check configuration with <literal>promtool
-        check</literal>. The call to <literal>promtool</literal> is
+      description = lib.mdDoc ''
+        Check configuration with `promtool check`. The call to `promtool` is
         subject to sandboxing by Nix.
 
         If you use credentials stored in external files
-        (<literal>password_file</literal>, <literal>bearer_token_file</literal>, etc),
-        they will not be visible to <literal>promtool</literal>
+        (`password_file`, `bearer_token_file`, etc),
+        they will not be visible to `promtool`
         and it will report errors, despite a correct configuration.
-        To resolve this, you may set this option to <literal>"syntax-only"</literal>
+        To resolve this, you may set this option to `"syntax-only"`
         in order to only syntax check the Prometheus configuration.
       '';
     };
@@ -1797,6 +1798,33 @@ in
         WorkingDirectory = workingDir;
         StateDirectory = cfg.stateDir;
         StateDirectoryMode = "0700";
+        # Hardening
+        AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
+        DeviceAllow = [ "/dev/null rw" ];
+        DevicePolicy = "strict";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "full";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
       };
     };
     # prometheus-config-reload will activate after prometheus. However, what we
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.md b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.md
new file mode 100644
index 000000000000..34fadecadc74
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.md
@@ -0,0 +1,180 @@
+# Prometheus exporters {#module-services-prometheus-exporters}
+
+Prometheus exporters provide metrics for the
+[prometheus monitoring system](https://prometheus.io).
+
+## Configuration {#module-services-prometheus-exporters-configuration}
+
+One of the most common exporters is the
+[node exporter](https://github.com/prometheus/node_exporter),
+it provides hardware and OS metrics from the host it's
+running on. The exporter could be configured as follows:
+```
+  services.prometheus.exporters.node = {
+    enable = true;
+    port = 9100;
+    enabledCollectors = [
+      "logind"
+      "systemd"
+    ];
+    disabledCollectors = [
+      "textfile"
+    ];
+    openFirewall = true;
+    firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
+  };
+```
+It should now serve all metrics from the collectors that are explicitly
+enabled and the ones that are
+[enabled by default](https://github.com/prometheus/node_exporter#enabled-by-default),
+via http under `/metrics`. In this
+example the firewall should just allow incoming connections to the
+exporter's port on the bridge interface `br0` (this would
+have to be configured separately of course). For more information about
+configuration see `man configuration.nix` or search through
+the [available options](https://nixos.org/nixos/options.html#prometheus.exporters).
+
+Prometheus can now be configured to consume the metrics produced by the exporter:
+```
+    services.prometheus = {
+      # ...
+
+      scrapeConfigs = [
+        {
+          job_name = "node";
+          static_configs = [{
+            targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ];
+          }];
+        }
+      ];
+
+      # ...
+    }
+```
+
+## Adding a new exporter {#module-services-prometheus-exporters-new-exporter}
+
+To add a new exporter, it has to be packaged first (see
+`nixpkgs/pkgs/servers/monitoring/prometheus/` for
+examples), then a module can be added. The postfix exporter is used in this
+example:
+
+  - Some default options for all exporters are provided by
+    `nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix`:
+
+      - `enable`
+      - `port`
+      - `listenAddress`
+      - `extraFlags`
+      - `openFirewall`
+      - `firewallFilter`
+      - `user`
+      - `group`
+  - As there is already a package available, the module can now be added. This
+    is accomplished by adding a new file to the
+    `nixos/modules/services/monitoring/prometheus/exporters/`
+    directory, which will be called postfix.nix and contains all exporter
+    specific options and configuration:
+    ```
+    # nixpkgs/nixos/modules/services/prometheus/exporters/postfix.nix
+    { config, lib, pkgs, options }:
+
+    with lib;
+
+    let
+      # for convenience we define cfg here
+      cfg = config.services.prometheus.exporters.postfix;
+    in
+    {
+      port = 9154; # The postfix exporter listens on this port by default
+
+      # `extraOpts` is an attribute set which contains additional options
+      # (and optional overrides for default options).
+      # Note that this attribute is optional.
+      extraOpts = {
+        telemetryPath = mkOption {
+          type = types.str;
+          default = "/metrics";
+          description = ''
+            Path under which to expose metrics.
+          '';
+        };
+        logfilePath = mkOption {
+          type = types.path;
+          default = /var/log/postfix_exporter_input.log;
+          example = /var/log/mail.log;
+          description = ''
+            Path where Postfix writes log entries.
+            This file will be truncated by this exporter!
+          '';
+        };
+        showqPath = mkOption {
+          type = types.path;
+          default = /var/spool/postfix/public/showq;
+          example = /var/lib/postfix/queue/public/showq;
+          description = ''
+            Path at which Postfix places its showq socket.
+          '';
+        };
+      };
+
+      # `serviceOpts` is an attribute set which contains configuration
+      # for the exporter's systemd service. One of
+      # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
+      # has to be specified here. This will be merged with the default
+      # service configuration.
+      # Note that by default 'DynamicUser' is 'true'.
+      serviceOpts = {
+        serviceConfig = {
+          DynamicUser = false;
+          ExecStart = ''
+            ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
+              --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+              --web.telemetry-path ${cfg.telemetryPath} \
+              ${concatStringsSep " \\\n  " cfg.extraFlags}
+          '';
+        };
+      };
+    }
+    ```
+  - This should already be enough for the postfix exporter. Additionally one
+    could now add assertions and conditional default values. This can be done
+    in the 'meta-module' that combines all exporter definitions and generates
+    the submodules:
+    `nixpkgs/nixos/modules/services/prometheus/exporters.nix`
+
+## Updating an exporter module {#module-services-prometheus-exporters-update-exporter-module}
+
+Should an exporter option change at some point, it is possible to add
+information about the change to the exporter definition similar to
+`nixpkgs/nixos/modules/rename.nix`:
+```
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.nginx;
+in
+{
+  port = 9113;
+  extraOpts = {
+    # additional module options
+    # ...
+  };
+  serviceOpts = {
+    # service configuration
+    # ...
+  };
+  imports = [
+    # 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath'
+    (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+
+    # removed option 'services.prometheus.exporters.nginx.insecure'
+    (mkRemovedOptionModule [ "insecure" ] ''
+      This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
+    '')
+    ({ options.warnings = options.warnings; })
+  ];
+}
+```
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
index 47c30a0f24bb..235296168934 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -35,7 +35,9 @@ let
     "dovecot"
     "fastly"
     "fritzbox"
+    "graphite"
     "influxdb"
+    "ipmi"
     "json"
     "jitsi"
     "kea"
@@ -50,6 +52,7 @@ let
     "nginx"
     "nginxlog"
     "node"
+    "nut"
     "openldap"
     "openvpn"
     "pihole"
@@ -62,25 +65,29 @@ let
     "rspamd"
     "rtl_433"
     "script"
+    "shelly"
     "snmp"
     "smartctl"
     "smokeping"
     "sql"
+    "statsd"
     "surfboard"
     "systemd"
     "tor"
     "unbound"
     "unifi"
-    "unifi-poller"
+    "unpoller"
+    "v2ray"
     "varnish"
     "wireguard"
     "flow"
+    "zfs"
   ] (name:
     import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
   );
 
   mkExporterOpts = ({ name, port }: {
-    enable = mkEnableOption "the prometheus ${name} exporter";
+    enable = mkEnableOption (lib.mdDoc "the prometheus ${name} exporter");
     port = mkOption {
       type = types.port;
       default = port;
@@ -115,10 +122,10 @@ let
       example = literalExpression ''
         "-i eth0 -p tcp -m tcp --dport ${toString port}"
       '';
-      description = ''
+      description = lib.mdDoc ''
         Specify a filter for iptables to use when
-        <option>services.prometheus.exporters.${name}.openFirewall</option>
-        is true. It is used as `ip46tables -I nixos-fw <option>firewallFilter</option> -j nixos-fw-accept`.
+        {option}`services.prometheus.exporters.${name}.openFirewall`
+        is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`.
       '';
     };
     user = mkOption {
@@ -194,7 +201,7 @@ let
         serviceConfig.LockPersonality = true;
         serviceConfig.MemoryDenyWriteExecute = true;
         serviceConfig.NoNewPrivileges = true;
-        serviceConfig.PrivateDevices = true;
+        serviceConfig.PrivateDevices = mkDefault true;
         serviceConfig.ProtectClock = mkDefault true;
         serviceConfig.ProtectControlGroups = true;
         serviceConfig.ProtectHome = true;
@@ -226,6 +233,10 @@ in
   options.services.prometheus.exporters = mkOption {
     type = types.submodule {
       options = (mkSubModules);
+      imports = [
+        ../../../misc/assertions.nix
+        (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ])
+      ];
     };
     description = lib.mdDoc "Prometheus exporter configuration";
     default = {};
@@ -242,6 +253,22 @@ in
 
   config = mkMerge ([{
     assertions = [ {
+      assertion = cfg.ipmi.enable -> (cfg.ipmi.configFile != null) -> (
+        !(lib.hasPrefix "/tmp/" cfg.ipmi.configFile)
+      );
+      message = ''
+        Config file specified in `services.prometheus.exporters.ipmi.configFile' must
+          not reside within /tmp - it won't be visible to the systemd service.
+      '';
+    } {
+      assertion = cfg.ipmi.enable -> (cfg.ipmi.webConfigFile != null) -> (
+        !(lib.hasPrefix "/tmp/" cfg.ipmi.webConfigFile)
+      );
+      message = ''
+        Config file specified in `services.prometheus.exporters.ipmi.webConfigFile' must
+          not reside within /tmp - it won't be visible to the systemd service.
+      '';
+    } {
       assertion = cfg.snmp.enable -> (
         (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null)
       );
@@ -273,13 +300,14 @@ in
         Please specify either 'services.prometheus.exporters.sql.configuration' or
           'services.prometheus.exporters.sql.configFile'
       '';
-    } ] ++ (flip map (attrNames cfg) (exporter: {
+    } ] ++ (flip map (attrNames exporterOpts) (exporter: {
       assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
       message = ''
         The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
         `openFirewall' is set to `true'!
       '';
-    }));
+    })) ++ config.services.prometheus.exporters.assertions;
+    warnings = config.services.prometheus.exporters.warnings;
   }] ++ [(mkIf config.services.minio.enable {
     services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
     services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
@@ -297,7 +325,7 @@ in
   );
 
   meta = {
-    doc = ./exporters.xml;
+    doc = ./exporters.md;
     maintainers = [ maintainers.willibutz ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml
deleted file mode 100644
index 1df88bb61a12..000000000000
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ /dev/null
@@ -1,248 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-prometheus-exporters">
- <title>Prometheus exporters</title>
- <para>
-  Prometheus exporters provide metrics for the
-  <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.
- </para>
- <section xml:id="module-services-prometheus-exporters-configuration">
-  <title>Configuration</title>
-
-  <para>
-   One of the most common exporters is the
-   <link xlink:href="https://github.com/prometheus/node_exporter">node
-   exporter</link>, it provides hardware and OS metrics from the host it's
-   running on. The exporter could be configured as follows:
-<programlisting>
-  services.prometheus.exporters.node = {
-    enable = true;
-    port = 9100;
-    enabledCollectors = [
-      "logind"
-      "systemd"
-    ];
-    disabledCollectors = [
-      "textfile"
-    ];
-    openFirewall = true;
-    firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
-  };
-</programlisting>
-   It should now serve all metrics from the collectors that are explicitly
-   enabled and the ones that are
-   <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled
-   by default</link>, via http under <literal>/metrics</literal>. In this
-   example the firewall should just allow incoming connections to the
-   exporter's port on the bridge interface <literal>br0</literal> (this would
-   have to be configured seperately of course). For more information about
-   configuration see <literal>man configuration.nix</literal> or search through
-   the
-   <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
-   options</link>.
-  </para>
-
-  <para>
-    Prometheus can now be configured to consume the metrics produced by the exporter:
-    <programlisting>
-    services.prometheus = {
-      # ...
-
-      scrapeConfigs = [
-        {
-          job_name = "node";
-          static_configs = [{
-            targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ];
-          }];
-        }
-      ];
-
-      # ...
-    }
-    </programlisting>
-  </para>
- </section>
- <section xml:id="module-services-prometheus-exporters-new-exporter">
-  <title>Adding a new exporter</title>
-
-  <para>
-   To add a new exporter, it has to be packaged first (see
-   <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for
-   examples), then a module can be added. The postfix exporter is used in this
-   example:
-  </para>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     Some default options for all exporters are provided by
-     <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
-    </para>
-   </listitem>
-   <listitem override='none'>
-    <itemizedlist>
-     <listitem>
-      <para>
-       <literal>enable</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>port</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>listenAddress</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>extraFlags</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>openFirewall</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>firewallFilter</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>user</literal>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       <literal>group</literal>
-      </para>
-     </listitem>
-    </itemizedlist>
-   </listitem>
-   <listitem>
-    <para>
-     As there is already a package available, the module can now be added. This
-     is accomplished by adding a new file to the
-     <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal>
-     directory, which will be called postfix.nix and contains all exporter
-     specific options and configuration:
-<programlisting>
-# nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
-{ config, lib, pkgs, options }:
-
-with lib;
-
-let
-  # for convenience we define cfg here
-  cfg = config.services.prometheus.exporters.postfix;
-in
-{
-  port = 9154; # The postfix exporter listens on this port by default
-
-  # `extraOpts` is an attribute set which contains additional options
-  # (and optional overrides for default options).
-  # Note that this attribute is optional.
-  extraOpts = {
-    telemetryPath = mkOption {
-      type = types.str;
-      default = "/metrics";
-      description = ''
-        Path under which to expose metrics.
-      '';
-    };
-    logfilePath = mkOption {
-      type = types.path;
-      default = /var/log/postfix_exporter_input.log;
-      example = /var/log/mail.log;
-      description = ''
-        Path where Postfix writes log entries.
-        This file will be truncated by this exporter!
-      '';
-    };
-    showqPath = mkOption {
-      type = types.path;
-      default = /var/spool/postfix/public/showq;
-      example = /var/lib/postfix/queue/public/showq;
-      description = ''
-        Path at which Postfix places its showq socket.
-      '';
-    };
-  };
-
-  # `serviceOpts` is an attribute set which contains configuration
-  # for the exporter's systemd service. One of
-  # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
-  # has to be specified here. This will be merged with the default
-  # service confiuration.
-  # Note that by default 'DynamicUser' is 'true'.
-  serviceOpts = {
-    serviceConfig = {
-      DynamicUser = false;
-      ExecStart = ''
-        ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
-          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-          --web.telemetry-path ${cfg.telemetryPath} \
-          ${concatStringsSep " \\\n  " cfg.extraFlags}
-      '';
-    };
-  };
-}
-</programlisting>
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     This should already be enough for the postfix exporter. Additionally one
-     could now add assertions and conditional default values. This can be done
-     in the 'meta-module' that combines all exporter definitions and generates
-     the submodules:
-     <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
-    </para>
-   </listitem>
-  </itemizedlist>
- </section>
- <section xml:id="module-services-prometheus-exporters-update-exporter-module">
-  <title>Updating an exporter module</title>
-   <para>
-     Should an exporter option change at some point, it is possible to add
-     information about the change to the exporter definition similar to
-     <literal>nixpkgs/nixos/modules/rename.nix</literal>:
-<programlisting>
-{ config, lib, pkgs, options }:
-
-with lib;
-
-let
-  cfg = config.services.prometheus.exporters.nginx;
-in
-{
-  port = 9113;
-  extraOpts = {
-    # additional module options
-    # ...
-  };
-  serviceOpts = {
-    # service configuration
-    # ...
-  };
-  imports = [
-    # 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath'
-    (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
-
-    # removed option 'services.prometheus.exporters.nginx.insecure'
-    (mkRemovedOptionModule [ "insecure" ] ''
-      This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
-    '')
-    ({ options.warnings = options.warnings; })
-  ];
-}
-</programlisting>
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
index eab1f9e7b4d2..f67596f05a3a 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -9,7 +9,7 @@ in
   port = 9103;
   extraOpts = {
     collectdBinary = {
-      enable = mkEnableOption "collectd binary protocol receiver";
+      enable = mkEnableOption (lib.mdDoc "collectd binary protocol receiver");
 
       authFile = mkOption {
         default = null;
@@ -18,7 +18,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 25826;
         description = lib.mdDoc "Network address on which to accept collectd binary network packets.";
       };
@@ -58,10 +58,10 @@ in
     };
   };
   serviceOpts = let
-    collectSettingsArgs = if (cfg.collectdBinary.enable) then ''
+    collectSettingsArgs = optionalString (cfg.collectdBinary.enable) ''
       --collectd.listen-address ${cfg.collectdBinary.listenAddress}:${toString cfg.collectdBinary.port} \
       --collectd.security-level ${cfg.collectdBinary.securityLevel} \
-    '' else "";
+    '';
   in {
     serviceConfig = {
       ExecStart = ''
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
index d86b750ba5f2..6fb438353a4c 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -19,24 +19,24 @@ in
       type = types.path;
       default = "/var/run/dovecot/stats";
       example = "/var/run/dovecot2/old-stats";
-      description = ''
+      description = lib.mdDoc ''
         Path under which the stats socket is placed.
         The user/group under which the exporter runs,
         should be able to access the socket in order
         to scrape the metrics successfully.
 
         Please keep in mind that the stats module has changed in
-        <link xlink:href="https://wiki2.dovecot.org/Upgrading/2.3">Dovecot 2.3+</link> which
-        is not <link xlink:href="https://github.com/kumina/dovecot_exporter/issues/8">compatible with this exporter</link>.
+        [Dovecot 2.3+](https://wiki2.dovecot.org/Upgrading/2.3) which
+        is not [compatible with this exporter](https://github.com/kumina/dovecot_exporter/issues/8).
 
         The following extra config has to be passed to Dovecot to ensure that recent versions
         work with this exporter:
-        <programlisting>
+        ```
         {
-          <xref linkend="opt-services.prometheus.exporters.dovecot.enable"/> = true;
-          <xref linkend="opt-services.prometheus.exporters.dovecot.socketPath"/> = "/var/run/dovecot2/old-stats";
-          <xref linkend="opt-services.dovecot2.mailPlugins.globally.enable"/> = [ "old_stats" ];
-          <xref linkend="opt-services.dovecot2.extraConfig"/> = '''
+          services.prometheus.exporters.dovecot.enable = true;
+          services.prometheus.exporters.dovecot.socketPath = "/var/run/dovecot2/old-stats";
+          services.dovecot2.mailPlugins.globally.enable = [ "old_stats" ];
+          services.dovecot2.extraConfig = '''
             service old-stats {
               unix_listener old-stats {
                 user = dovecot-exporter
@@ -60,7 +60,7 @@ in
             }
           ''';
         }
-        </programlisting>
+        ```
       '';
     };
     scopes = mkOption {
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
index 182a1131c054..36409caccf2e 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
@@ -7,7 +7,7 @@ in
 {
   port = 9118;
   extraOpts = {
-    debug = mkEnableOption "Debug logging mode for fastly-exporter";
+    debug = mkEnableOption (lib.mdDoc "Debug logging mode for fastly-exporter");
 
     configFile = mkOption {
       type = types.nullOr types.path;
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/graphite.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/graphite.nix
new file mode 100644
index 000000000000..34a887104212
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/graphite.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, options }:
+
+let
+  cfg = config.services.prometheus.exporters.graphite;
+  format = pkgs.formats.yaml { };
+in
+{
+  port = 9108;
+  extraOpts = {
+    graphitePort = lib.mkOption {
+      type = lib.types.port;
+      default = 9109;
+      description = lib.mdDoc ''
+        Port to use for the graphite server.
+      '';
+    };
+    mappingSettings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = format.type;
+        options = { };
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Mapping configuration for the exporter, see
+        <https://github.com/prometheus/graphite_exporter#yaml-config> for
+        available options.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-graphite-exporter}/bin/graphite_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --graphite.listen-address ${cfg.listenAddress}:${toString cfg.graphitePort} \
+          --graphite.mapping-config ${format.generate "mapping.yml" cfg.mappingSettings} \
+          ${lib.concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix
new file mode 100644
index 000000000000..55c4f4aa4826
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  logPrefix = "services.prometheus.exporter.ipmi";
+  cfg = config.services.prometheus.exporters.ipmi;
+in {
+  port = 9290;
+
+  extraOpts = {
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to configuration file.
+      '';
+    };
+
+    webConfigFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to configuration file that can enable TLS or authentication.
+      '';
+    };
+  };
+
+  serviceOpts.serviceConfig = {
+    ExecStart = with cfg; concatStringsSep " " ([
+      "${pkgs.prometheus-ipmi-exporter}/bin/ipmi_exporter"
+      "--web.listen-address ${listenAddress}:${toString port}"
+    ] ++ optionals (cfg.webConfigFile != null) [
+      "--web.config.file ${escapeShellArg cfg.webConfigFile}"
+    ] ++ optionals (cfg.configFile != null) [
+      "--config.file ${escapeShellArg cfg.configFile}"
+    ] ++ extraFlags);
+
+    ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index 0682f9da4003..ed33c72f644f 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -35,7 +35,7 @@ in {
         ${pkgs.prometheus-kea-exporter}/bin/kea-exporter \
           --address ${cfg.listenAddress} \
           --port ${toString cfg.port} \
-          ${concatStringsSep " \\n" cfg.controlSocketPaths}
+          ${concatStringsSep " " cfg.controlSocketPaths}
       '';
       SupplementaryGroups = [ "kea" ];
       RestrictAddressFamilies = [
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
index 2df0ab93bdef..a73425b37da7 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
@@ -11,8 +11,8 @@ in {
       type = types.str;
       default = "${pkgs.knot-dns.out}/lib/libknot.so";
       defaultText = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
-      description = ''
-        Path to the library of <package>knot-dns</package>.
+      description = lib.mdDoc ''
+        Path to the library of `knot-dns`.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
index 205188538a21..15079f5841f4 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -33,7 +33,7 @@ let
       '';
     };
     port = mkOption {
-      type = types.int;
+      type = types.port;
       example = 587;
       description = lib.mdDoc ''
         Port to use for SMTP.
@@ -112,16 +112,16 @@ let
           detectionDir = "/path/to/Maildir/new";
         } ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         List of servers that should be probed.
 
-        <emphasis>Note:</emphasis> if your mailserver has <citerefentry><refentrytitle>rspamd</refentrytitle><manvolnum>8</manvolnum></citerefentry> configured,
+        *Note:* if your mailserver has {manpage}`rspamd(8)` configured,
         it can happen that emails from this exporter are marked as spam.
 
         It's possible to work around the issue with a config like this:
-        <programlisting>
+        ```
         {
-          <link linkend="opt-services.rspamd.locals._name_.text">services.rspamd.locals."multimap.conf".text</link> = '''
+          services.rspamd.locals."multimap.conf".text = '''
             ALLOWLIST_PROMETHEUS {
               filter = "email:domain:tld";
               type = "from";
@@ -130,7 +130,7 @@ let
             }
           ''';
         }
-        </programlisting>
+        ```
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
index 9967d3647e00..674dc9dd4158 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
@@ -10,14 +10,14 @@ in {
     settings = mkOption {
       type = types.attrs;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         All settings of nginxlog expressed as an Nix attrset.
 
         Check the official documentation for the corresponding YAML
         settings that can all be used here: https://github.com/martin-helmich/prometheus-nginxlog-exporter
 
         The `listen` object is already generated by `port`, `listenAddress` and `metricsEndpoint` and
-        will be merged with the value of `settings` before writting it as JSON.
+        will be merged with the value of `settings` before writing it as JSON.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index ae69c29d0a51..dd8602e2c63d 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -4,6 +4,8 @@ with lib;
 
 let
   cfg = config.services.prometheus.exporters.node;
+  collectorIsEnabled = final: any (collector: (final == collector)) cfg.enabledCollectors;
+  collectorIsDisabled = final: any (collector: (final == collector)) cfg.disabledCollectors;
 in
 {
   port = 9100;
@@ -35,15 +37,15 @@ in
           ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
       '';
-      RestrictAddressFamilies = optionals (any (collector: (collector == "logind" || collector == "systemd")) cfg.enabledCollectors) [
+      RestrictAddressFamilies = optionals (collectorIsEnabled "logind" || collectorIsEnabled "systemd") [
         # needs access to dbus via unix sockets (logind/systemd)
         "AF_UNIX"
-      ] ++ optionals (any (collector: (collector == "network_route" || collector == "wifi")) cfg.enabledCollectors) [
+      ] ++ optionals (collectorIsEnabled "network_route" || collectorIsEnabled "wifi" || ! collectorIsDisabled "netdev") [
         # needs netlink sockets for wireless collector
         "AF_NETLINK"
       ];
       # The timex collector needs to access clock APIs
-      ProtectClock = any (collector: collector == "timex") cfg.disabledCollectors;
+      ProtectClock = collectorIsDisabled "timex";
       # Allow space monitoring under /home
       ProtectHome = true;
     };
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nut.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nut.nix
new file mode 100644
index 000000000000..1c86b48b4509
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nut.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.nut;
+in
+{
+  port = 9199;
+  extraOpts = {
+    nutServer = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = lib.mdDoc ''
+        Hostname or address of the NUT server
+      '';
+    };
+    nutUser = mkOption {
+      type = types.str;
+      default = "";
+      example = "nut";
+      description = lib.mdDoc ''
+        The user to log in into NUT server. If set, passwordPath should
+        also be set.
+
+        Default NUT configs usually permit reading variables without
+        authentication.
+      '';
+    };
+    passwordPath = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      apply = final: if final == null then null else toString final;
+      description = lib.mdDoc ''
+        A run-time path to the nutUser password file, which should be
+        provisioned outside of Nix store.
+      '';
+    };
+  };
+  serviceOpts = {
+    script = ''
+      ${optionalString (cfg.passwordPath != null)
+      "export NUT_EXPORTER_PASSWORD=$(cat ${toString cfg.passwordPath})"}
+      ${pkgs.prometheus-nut-exporter}/bin/nut_exporter \
+        --nut.server=${cfg.nutServer} \
+        --web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
+        ${optionalString (cfg.nutUser != "") "--nut.username=${cfg.nutUser}"}
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix
index 59fcedef9ddc..aee3ae5bb2d4 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix
@@ -10,31 +10,31 @@ in {
     ldapCredentialFile = mkOption {
       type = types.path;
       example = "/run/keys/ldap_pass";
-      description = ''
+      description = lib.mdDoc ''
         Environment file to contain the credentials to authenticate against
-        <package>openldap</package>.
+        `openldap`.
 
         The file should look like this:
-        <programlisting>
+        ```
         ---
         ldapUser: "cn=monitoring,cn=Monitor"
         ldapPass: "secret"
-        </programlisting>
+        ```
       '';
     };
     protocol = mkOption {
       default = "tcp";
       example = "udp";
       type = types.str;
-      description = ''
-        Which protocol to use to connect against <package>openldap</package>.
+      description = lib.mdDoc ''
+        Which protocol to use to connect against `openldap`.
       '';
     };
     ldapAddr = mkOption {
       default = "localhost:389";
       type = types.str;
-      description = ''
-        Address of the <package>openldap</package>-instance.
+      description = lib.mdDoc ''
+        Address of the `openldap`-instance.
       '';
     };
     metricsPath = mkOption {
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
index 537d72e85c8f..6f403b3e58c8 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
@@ -6,6 +6,11 @@ let
   cfg = config.services.prometheus.exporters.pihole;
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "interval"] "This option has been removed.")
+    ({ options.warnings = options.warnings; options.assertions = options.assertions; })
+  ];
+
   port = 9617;
   extraOpts = {
     apiToken = mkOption {
@@ -13,15 +18,7 @@ in
       default = "";
       example = "580a770cb40511eb85290242ac130003580a770cb40511eb85290242ac130003";
       description = lib.mdDoc ''
-        pi-hole API token which can be used instead of a password
-      '';
-    };
-    interval = mkOption {
-      type = types.str;
-      default = "10s";
-      example = "30s";
-      description = lib.mdDoc ''
-        How often to scrape new data
+        Pi-Hole API token which can be used instead of a password
       '';
     };
     password = mkOption {
@@ -29,7 +26,7 @@ in
       default = "";
       example = "password";
       description = lib.mdDoc ''
-        The password to login into pihole. An api token can be used instead.
+        The password to login into Pi-Hole. An api token can be used instead.
       '';
     };
     piholeHostname = mkOption {
@@ -37,7 +34,7 @@ in
       default = "pihole";
       example = "127.0.0.1";
       description = lib.mdDoc ''
-        Hostname or address where to find the pihole webinterface
+        Hostname or address where to find the Pi-Hole webinterface
       '';
     };
     piholePort = mkOption {
@@ -45,7 +42,7 @@ in
       default = 80;
       example = 443;
       description = lib.mdDoc ''
-        The port pihole webinterface is reachable on
+        The port Pi-Hole webinterface is reachable on
       '';
     };
     protocol = mkOption {
@@ -53,21 +50,28 @@ in
       default = "http";
       example = "https";
       description = lib.mdDoc ''
-        The protocol which is used to connect to pihole
+        The protocol which is used to connect to Pi-Hole
+      '';
+    };
+    timeout = mkOption {
+      type = types.str;
+      default = "5s";
+      description = lib.mdDoc ''
+        Controls the timeout to connect to a Pi-Hole instance
       '';
     };
   };
   serviceOpts = {
     serviceConfig = {
       ExecStart = ''
-        ${pkgs.bash}/bin/bash -c "${pkgs.prometheus-pihole-exporter}/bin/pihole-exporter \
-          -interval ${cfg.interval} \
+        ${pkgs.prometheus-pihole-exporter}/bin/pihole-exporter \
           ${optionalString (cfg.apiToken != "") "-pihole_api_token ${cfg.apiToken}"} \
           -pihole_hostname ${cfg.piholeHostname} \
           ${optionalString (cfg.password != "") "-pihole_password ${cfg.password}"} \
           -pihole_port ${toString cfg.piholePort} \
           -pihole_protocol ${cfg.protocol} \
-          -port ${toString cfg.port}"
+          -port ${toString cfg.port} \
+          -timeout ${cfg.timeout}
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
index 5e8dd21af85f..755d771ecdff 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
@@ -36,8 +36,8 @@ in
       type = types.nullOr types.path;
       default = null;
       example = "/root/prometheus-postgres-exporter.env";
-      description = ''
-        Environment file as defined in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)`.
 
         Secrets may be passed to the service without adding them to the
         world-readable Nix store, by specifying placeholder variables as
@@ -46,7 +46,7 @@ in
 
         Environment variables from this file will be interpolated into the
         config file using envsubst with this syntax:
-        <literal>$ENVIRONMENT ''${VARIABLE}</literal>
+        `$ENVIRONMENT ''${VARIABLE}`
 
         The main use is to set the DATA_SOURCE_NAME that contains the
         postgres password
@@ -54,10 +54,10 @@ in
         note that contents from this file will override dataSourceName
         if you have set it from nix.
 
-        <programlisting>
+        ```
           # Content of the environment file
           DATA_SOURCE_NAME=postgresql://username:password@localhost:5432/postgres?sslmode=disable
-        </programlisting>
+        ```
 
         Note that this file needs to be available on the host on which
         this exporter is running.
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pve.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
index 8e2573d084bc..e02acad3ecd1 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
@@ -10,7 +10,7 @@ let
     text = "default:";
   };
 
-  computedConfigFile = "${if cfg.configFile == null then emptyConfigFile else cfg.configFile}";
+  computedConfigFile = if cfg.configFile == null then emptyConfigFile else cfg.configFile;
 in
 {
   port = 9221;
@@ -100,6 +100,8 @@ in
   };
   serviceOpts = {
     serviceConfig = {
+      DynamicUser = cfg.environmentFile == null;
+      LoadCredential = "configFile:${computedConfigFile}";
       ExecStart = ''
         ${cfg.package}/bin/pve_exporter \
           --${if cfg.collectors.status == true then "" else "no-"}collector.status \
@@ -108,11 +110,11 @@ in
           --${if cfg.collectors.cluster == true then "" else "no-"}collector.cluster \
           --${if cfg.collectors.resources == true then "" else "no-"}collector.resources \
           --${if cfg.collectors.config == true then "" else "no-"}collector.config \
-          ${computedConfigFile} \
+          %d/configFile \
           ${toString cfg.port} ${cfg.listenAddress}
       '';
     } // optionalAttrs (cfg.environmentFile != null) {
-          EnvironmentFile = cfg.environmentFile;
+      EnvironmentFile = cfg.environmentFile;
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
index 0b48827f43f7..f9dcfad07d30 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
@@ -9,7 +9,7 @@ let
     pkgs.writeText "rspamd-exporter-config.yml" (builtins.toJSON conf);
 
   generateConfig = extraLabels: {
-    metrics = (map (path: {
+    modules.default.metrics = (map (path: {
       name = "rspamd_${replaceStrings [ "[" "." " " "]" "\\" "'" ] [ "_" "_" "_" "" "" "" ] path}";
       path = "{ .${path} }";
       labels = extraLabels;
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
index 42bf1788aea9..1f7235cb7830 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
@@ -16,7 +16,7 @@ in
           };
           "${field}" = lib.mkOption {
             type = int;
-            inherit description;
+            description = lib.mdDoc description;
           };
           location = lib.mkOption {
             type = str;
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix
new file mode 100644
index 000000000000..b9cfd1b1e84a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/shelly.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.shelly;
+in
+{
+  port = 9784;
+  extraOpts = {
+    metrics-file = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path to the JSON file with the metric definitions
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-shelly-exporter}/bin/shelly_exporter \
+          -metrics-file ${cfg.metrics-file} \
+          -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
index 8906c25d5037..50e1321a1e9c 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
@@ -4,16 +4,12 @@ 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;
-    };
-  };
+  args = lib.escapeShellArgs ([
+    "--web.listen-address=${cfg.listenAddress}:${toString cfg.port}"
+    "--smartctl.path=${pkgs.smartmontools}/bin/smartctl"
+    "--smartctl.interval=${cfg.maxInterval}"
+  ] ++ map (device: "--smartctl.device=${device}") cfg.devices
+  ++ cfg.extraFlags);
 in {
   port = 9633;
 
@@ -50,26 +46,19 @@ in {
         "CAP_SYS_ADMIN"
       ];
       DevicePolicy = "closed";
-      DeviceAllow = lib.mkOverride 100 (
-        if cfg.devices != [] then
-          cfg.devices
-        else [
-          "block-blkext rw"
-          "block-sd rw"
-          "char-nvme rw"
-        ]
-      );
+      DeviceAllow = lib.mkOverride 50 [
+        "block-blkext rw"
+        "block-sd rw"
+        "char-nvme rw"
+      ];
       ExecStart = ''
-        ${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter -config ${configFile}
+        ${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter ${args}
       '';
       PrivateDevices = lib.mkForce false;
       ProtectProc = "invisible";
       ProcSubset = "pid";
       SupplementaryGroups = [ "disk" ];
-      SystemCallFilter = [
-        "@system-service"
-        "~@privileged @resources"
-      ];
+      SystemCallFilter = [ "@system-service" "~@privileged" ];
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix
new file mode 100644
index 000000000000..d9d732d8c125
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.statsd;
+in
+{
+  port = 9102;
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-statsd-exporter}/bin/statsd_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
index edf9b57607a3..7a9167110a27 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -17,7 +17,7 @@ in
     };
 
     torControlPort = mkOption {
-      type = types.int;
+      type = types.port;
       default = 9051;
       description = lib.mdDoc ''
         Tor control port.
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix
deleted file mode 100644
index 394e6e201f03..000000000000
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix
+++ /dev/null
@@ -1,34 +0,0 @@
-{ config, lib, pkgs, options }:
-
-with lib;
-
-let
-  cfg = config.services.prometheus.exporters.unifi-poller;
-
-  configFile = pkgs.writeText "prometheus-unifi-poller-exporter.json" (generators.toJSON {} {
-    poller = { inherit (cfg.log) debug quiet; };
-    unifi = { inherit (cfg) controllers; };
-    influxdb.disable = true;
-    prometheus = {
-      http_listen = "${cfg.listenAddress}:${toString cfg.port}";
-      report_errors = cfg.log.prometheusErrors;
-    };
-  });
-
-in {
-  port = 9130;
-
-  extraOpts = {
-    inherit (options.services.unifi-poller.unifi) controllers;
-    log = {
-      debug = mkEnableOption "debug logging including line numbers, high resolution timestamps, per-device logs.";
-      quiet = mkEnableOption "startup and error logs only.";
-      prometheusErrors = mkEnableOption "emitting errors to prometheus.";
-    };
-  };
-
-  serviceOpts.serviceConfig = {
-    ExecStart = "${pkgs.unifi-poller}/bin/unifi-poller --config ${configFile}";
-    DynamicUser = false;
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
new file mode 100644
index 000000000000..3b7f978528cd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.unpoller;
+
+  configFile = pkgs.writeText "prometheus-unpoller-exporter.json" (generators.toJSON {} {
+    poller = { inherit (cfg.log) debug quiet; };
+    unifi = { inherit (cfg) controllers; };
+    influxdb.disable = true;
+    datadog.disable = true; # workaround for https://github.com/unpoller/unpoller/issues/442
+    prometheus = {
+      http_listen = "${cfg.listenAddress}:${toString cfg.port}";
+      report_errors = cfg.log.prometheusErrors;
+    };
+    inherit (cfg) loki;
+  });
+
+in {
+  port = 9130;
+
+  extraOpts = {
+    inherit (options.services.unpoller.unifi) controllers;
+    inherit (options.services.unpoller) loki;
+    log = {
+      debug = mkEnableOption (lib.mdDoc "debug logging including line numbers, high resolution timestamps, per-device logs");
+      quiet = mkEnableOption (lib.mdDoc "startup and error logs only");
+      prometheusErrors = mkEnableOption (lib.mdDoc "emitting errors to prometheus");
+    };
+  };
+
+  serviceOpts.serviceConfig = {
+    ExecStart = "${pkgs.unpoller}/bin/unpoller --config ${configFile}";
+    DynamicUser = false;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix
new file mode 100644
index 000000000000..a019157c664b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.v2ray;
+in
+{
+  port = 9299;
+  extraOpts = {
+    v2rayEndpoint = mkOption {
+      type = types.str;
+      default = "127.0.0.1:54321";
+      description = lib.mdDoc ''
+        v2ray grpc api endpoint
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-v2ray-exporter}/bin/v2ray-exporter \
+          --v2ray-endpoint ${cfg.v2rayEndpoint} \
+          --listen ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
index 20631f2af934..c98dcd9f64bf 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -11,7 +11,7 @@ in {
     ({ options.warnings = options.warnings; options.assertions = options.assertions; })
   ];
   extraOpts = {
-    verbose = mkEnableOption "Verbose logging mode for prometheus-wireguard-exporter";
+    verbose = mkEnableOption (lib.mdDoc "Verbose logging mode for prometheus-wireguard-exporter");
 
     wireguardConfig = mkOption {
       type = with types; nullOr (either path str);
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/zfs.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/zfs.nix
new file mode 100644
index 000000000000..ff12a52d49a9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/zfs.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.zfs;
+in
+{
+  port = 9134;
+
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = lib.mdDoc ''
+        Path under which to expose metrics.
+      '';
+    };
+
+    pools = mkOption {
+      type = with types; nullOr (listOf str);
+      default = [ ];
+      description = lib.mdDoc ''
+        Name of the pool(s) to collect, repeat for multiple pools (default: all pools).
+      '';
+    };
+  };
+
+  serviceOpts = {
+    # needs zpool
+    path = [ config.boot.zfs.package ];
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-zfs-exporter}/bin/zfs_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          ${concatMapStringsSep " " (x: "--pool=${x}") cfg.pools} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+      ProtectClock = false;
+      PrivateDevices = false;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
index ac7a2300f67c..f5c114c92752 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -21,7 +21,7 @@ let
 in {
   options = {
     services.prometheus.pushgateway = {
-      enable = mkEnableOption "Prometheus Pushgateway";
+      enable = mkEnableOption (lib.mdDoc "Prometheus Pushgateway");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/sachet.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/sachet.nix
new file mode 100644
index 000000000000..c908d599bd4e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/sachet.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.sachet;
+  configFile = pkgs.writeText "sachet.yml" (builtins.toJSON cfg.configuration);
+in
+{
+  options = {
+    services.prometheus.sachet = {
+      enable = mkEnableOption (lib.mdDoc "Sachet, an SMS alerting tool for the Prometheus Alertmanager");
+
+      configuration = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        example = literalExpression ''
+          {
+            providers = {
+              twilio = {
+                # environment variables gets expanded at runtime
+                account_sid = "$TWILIO_ACCOUNT";
+                auth_token = "$TWILIO_TOKEN";
+              };
+            };
+            templates = [ ./some-template.tmpl ];
+            receivers = [{
+              name = "pager";
+              provider = "twilio";
+              to = [ "+33123456789" ];
+              text = "{{ template \"message\" . }}";
+            }];
+          }
+        '';
+        description = lib.mdDoc ''
+          Sachet's configuration as a nix attribute set.
+        '';
+      };
+
+      address = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc ''
+          The address Sachet will listen to.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 9876;
+        description = lib.mdDoc ''
+          The port Sachet will listen to.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = singleton {
+      assertion = cfg.configuration != null;
+      message = "Cannot enable Sachet without a configuration.";
+    };
+
+    systemd.services.sachet = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "network-online.target" ];
+      script = ''
+        ${pkgs.envsubst}/bin/envsubst -i "${configFile}" > /tmp/sachet.yaml
+        exec ${pkgs.prometheus-sachet}/bin/sachet -config /tmp/sachet.yaml -listen-address ${cfg.address}:${builtins.toString cfg.port}
+      '';
+
+      serviceConfig = {
+        Restart = "always";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+
+        DynamicUser = true;
+        PrivateTmp = true;
+        WorkingDirectory = "/tmp/";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
index 1d7da7ced3fe..4545ca37d278 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
@@ -15,7 +15,7 @@ in
   ];
 
   options.services.prometheus.xmpp-alerts = {
-    enable = mkEnableOption "XMPP Web hook service for Alertmanager";
+    enable = mkEnableOption (lib.mdDoc "XMPP Web hook service for Alertmanager");
 
     settings = mkOption {
       type = settingsFormat.type;
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix b/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
index b5cd79c74301..28821267b4f3 100644
--- a/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
@@ -37,7 +37,7 @@ in {
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of commandline-switches forwarded to a riemann-tool.
           See for example `riemann-health --help` for available options.
         '';
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann.nix b/nixpkgs/nixos/modules/services/monitoring/riemann.nix
index 8d61ec2a308f..7ab8af85ed79 100644
--- a/nixpkgs/nixos/modules/services/monitoring/riemann.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann.nix
@@ -27,13 +27,8 @@ in {
   options = {
 
     services.riemann = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable the Riemann network monitoring daemon.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "Riemann network monitoring daemon");
+
       config = mkOption {
         type = types.lines;
         description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/services/monitoring/smartd.nix b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
index 83791631d2ca..1e654cad5dd2 100644
--- a/nixpkgs/nixos/modules/services/monitoring/smartd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
@@ -4,8 +4,7 @@ with lib;
 
 let
 
-  host = config.networking.hostName or "unknown"
-       + optionalString (config.networking.domain != null) ".${config.networking.domain}";
+  host = config.networking.fqdnOrHostName;
 
   cfg = config.services.smartd;
   opt = options.services.smartd;
@@ -95,7 +94,7 @@ in
 
     services.smartd = {
 
-      enable = mkEnableOption "smartd daemon from <literal>smartmontools</literal> package";
+      enable = mkEnableOption (lib.mdDoc "smartd daemon from `smartmontools` package");
 
       autodetect = mkOption {
         default = true;
@@ -135,9 +134,9 @@ in
             default = "root";
             example = "example@domain.tld";
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               Sender of the notification messages.
-              Acts as the value of <literal>email</literal> in the emails' <literal>From: ... </literal> field.
+              Acts as the value of `email` in the emails' `From: ...` field.
             '';
           };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/statsd.nix b/nixpkgs/nixos/modules/services/monitoring/statsd.nix
index d109e0826204..bbc1c7146a84 100644
--- a/nixpkgs/nixos/modules/services/monitoring/statsd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/statsd.nix
@@ -56,7 +56,7 @@ in
 
   options.services.statsd = {
 
-    enable = mkEnableOption "statsd";
+    enable = mkEnableOption (lib.mdDoc "statsd");
 
     listenAddress = mkOption {
       description = lib.mdDoc "Address that statsd listens on over UDP";
diff --git a/nixpkgs/nixos/modules/services/monitoring/sysstat.nix b/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
index f8621f08bb8c..5468fc3aa454 100644
--- a/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
@@ -5,7 +5,7 @@ let
 in {
   options = {
     services.sysstat = {
-      enable = mkEnableOption "sar system activity collection";
+      enable = mkEnableOption (lib.mdDoc "sar system activity collection");
 
       collect-frequency = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix b/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
index e2271e571c40..9b1278317943 100644
--- a/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
@@ -14,7 +14,7 @@ in
 
   options = {
 
-    services.teamviewer.enable = mkEnableOption "TeamViewer daemon";
+    services.teamviewer.enable = mkEnableOption (lib.mdDoc "TeamViewer daemon");
 
   };
 
@@ -30,7 +30,7 @@ in
       description = "TeamViewer remote control daemon";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "NetworkManager-wait-online.service" "network.target" "dbus.service" ];
+      after = [ "network-online.target" "network.target" "dbus.service" ];
       requires = [ "dbus.service" ];
       preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer";
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/telegraf.nix b/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
index d228b5cc2d0a..913e599c189a 100644
--- a/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
@@ -11,7 +11,7 @@ in {
   ###### interface
   options = {
     services.telegraf = {
-      enable = mkEnableOption "telegraf server";
+      enable = mkEnableOption (lib.mdDoc "telegraf server");
 
       package = mkOption {
         default = pkgs.telegraf;
diff --git a/nixpkgs/nixos/modules/services/monitoring/thanos.nix b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
index c7404241fbf5..e6d8afc66624 100644
--- a/nixpkgs/nixos/modules/services/monitoring/thanos.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
@@ -8,7 +8,7 @@ let
   nullOpt = type: description: mkOption {
     type = types.nullOr type;
     default = null;
-    inherit description;
+    description = lib.mdDoc description;
   };
 
   optionToArgs = opt: v  : optional (v != null)  ''--${opt}="${toString v}"'';
@@ -18,8 +18,8 @@ let
 
   mkParamDef = type: default: description: mkParam type (description + ''
 
-    Defaults to <literal>${toString default}</literal> in Thanos
-    when set to <literal>null</literal>.
+    Defaults to `${toString default}` in Thanos
+    when set to `null`.
   '');
 
   mkParam = type: description: {
@@ -32,7 +32,7 @@ let
     option = mkOption {
       type = types.bool;
       default = false;
-      inherit description;
+      description = lib.mdDoc description;
     };
   };
 
@@ -41,7 +41,7 @@ let
     option = mkOption {
       type = types.listOf types.str;
       default = [];
-      inherit description;
+      description = lib.mdDoc description;
     };
   };
 
@@ -50,7 +50,7 @@ let
     option = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      inherit description;
+      description = lib.mdDoc description;
     };
   };
 
@@ -59,7 +59,7 @@ let
     option = mkOption {
       type = types.str;
       inherit default;
-      inherit description;
+      description = lib.mdDoc description;
     };
   };
 
@@ -83,8 +83,8 @@ let
   mkArgumentsOption = cmd: mkOption {
     type = types.listOf types.str;
     default = argumentsOf cmd;
-    defaultText = literalDocBook ''
-      calculated from <literal>config.services.thanos.${cmd}</literal>
+    defaultText = literalMD ''
+      calculated from `config.services.thanos.${cmd}`
     '';
     description = lib.mdDoc ''
       Arguments to the `thanos ${cmd}` command.
@@ -141,13 +141,13 @@ let
           option = nullOpt types.attrs ''
             Tracing configuration.
 
-            When not <literal>null</literal> the attribute set gets converted to
+            When not `null` the attribute set gets converted to
             a YAML file and stored in the Nix store. The option
-            <option>tracing.config-file</option> will default to its path.
+            {option}`tracing.config-file` will default to its path.
 
-            If <option>tracing.config-file</option> is set this option has no effect.
+            If {option}`tracing.config-file` is set this option has no effect.
 
-            See format details: <link xlink:href="https://thanos.io/tracing.md/#configuration"/>
+            See format details: <https://thanos.io/tracing.md/#configuration>
           '';
         };
     };
@@ -155,11 +155,11 @@ let
     common = cfg: params.log // params.tracing cfg // {
 
       http-address = mkParamDef types.str "0.0.0.0:10902" ''
-        Listen <literal>host:port</literal> for HTTP endpoints.
+        Listen `host:port` for HTTP endpoints.
       '';
 
       grpc-address = mkParamDef types.str "0.0.0.0:10901" ''
-        Listen <literal>ip:port</literal> address for gRPC endpoints (StoreAPI).
+        Listen `ip:port` address for gRPC endpoints (StoreAPI).
 
         Make sure this address is routable from other components.
       '';
@@ -206,13 +206,13 @@ let
           option = nullOpt types.attrs ''
             Object store configuration.
 
-            When not <literal>null</literal> the attribute set gets converted to
+            When not `null` the attribute set gets converted to
             a YAML file and stored in the Nix store. The option
-            <option>objstore.config-file</option> will default to its path.
+            {option}`objstore.config-file` will default to its path.
 
-            If <option>objstore.config-file</option> is set this option has no effect.
+            If {option}`objstore.config-file` is set this option has no effect.
 
-            See format details: <link xlink:href="https://thanos.io/storage.md/#configuration"/>
+            See format details: <https://thanos.io/storage.md/#configuration>
           '';
         };
     };
@@ -254,7 +254,7 @@ let
     store = params.common cfg.store // params.objstore cfg.store // {
 
       stateDir = mkStateDirParam "data-dir" "thanos-store" ''
-        Data directory relative to <literal>/var/lib</literal>
+        Data directory relative to `/var/lib`
         in which to cache remote blocks.
       '';
 
@@ -269,7 +269,7 @@ let
       store.grpc.series-sample-limit = mkParamDef types.int 0 ''
         Maximum amount of samples returned via a single Series call.
 
-        <literal>0</literal> means no limit.
+        `0` means no limit.
 
         NOTE: for efficiency we take 120 as the number of samples in chunk (it
         cannot be bigger than that), so the actual number of samples might be
@@ -300,7 +300,7 @@ let
       max-time = mkParamDef types.str "9999-12-31T23:59:59Z" ''
         End of time range limit to serve.
 
-        Thanos Store serves only blocks, which happened eariler than this
+        Thanos Store serves only blocks, which happened earlier than this
         value. Option can be a constant time in RFC3339 format or time duration
         relative to current time, such as -1d or 2h45m. Valid duration units are
         ms, s, m, h, d, w, y.
@@ -327,14 +327,14 @@ let
 
       grpc-client-server-name = mkParam types.str ''
         Server name to verify the hostname on the returned gRPC certificates.
-        See <link xlink:href="https://tools.ietf.org/html/rfc4366#section-3.1"/>
+        See <https://tools.ietf.org/html/rfc4366#section-3.1>
       '';
 
       web.route-prefix = mkParam types.str ''
         Prefix for API and UI endpoints.
 
         This allows thanos UI to be served on a sub-path. This option is
-        analogous to <option>web.route-prefix</option> of Promethus.
+        analogous to {option}`web.route-prefix` of Promethus.
       '';
 
       web.external-prefix = mkParam types.str ''
@@ -342,7 +342,7 @@ let
         interface.
 
         Actual endpoints are still served on / or the
-        <option>web.route-prefix</option>. This allows thanos UI to be served
+        {option}`web.route-prefix`. This allows thanos UI to be served
         behind a reverse proxy that strips a URL sub-path.
       '';
 
@@ -351,15 +351,15 @@ let
         redirects.
 
         This option is ignored if the option
-        <literal>web.external-prefix</literal> is set.
+        `web.external-prefix` is set.
 
         Security risk: enable this option only if a reverse proxy in front of
         thanos is resetting the header.
 
-        The setting <literal>web.prefix-header="X-Forwarded-Prefix"</literal>
+        The setting `web.prefix-header="X-Forwarded-Prefix"`
         can be useful, for example, if Thanos UI is served via Traefik reverse
-        proxy with <literal>PathPrefixStrip</literal> option enabled, which
-        sends the stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+        proxy with `PathPrefixStrip` option enabled, which
+        sends the stripped prefix value in `X-Forwarded-Prefix`
         header. This allows thanos UI to be served on a sub-path.
       '';
 
@@ -376,7 +376,7 @@ let
         deduplicated.
 
         Still you will be able to query without deduplication using
-        <literal>dedup=false</literal> parameter.
+        `dedup=false` parameter.
       '';
 
       selector-labels = mkAttrsParam "selector-label" ''
@@ -386,8 +386,8 @@ let
       store.addresses = mkListParam "store" ''
         Addresses of statically configured store API servers.
 
-        The scheme may be prefixed with <literal>dns+</literal> or
-        <literal>dnssrv+</literal> to detect store API servers through
+        The scheme may be prefixed with `dns+` or
+        `dnssrv+` to detect store API servers through
         respective DNS lookups.
       '';
 
@@ -411,12 +411,12 @@ let
       query.auto-downsampling = mkFlagParam ''
         Enable automatic adjustment (step / 5) to what source of data should
         be used in store gateways if no
-        <literal>max_source_resolution</literal> param is specified.
+        `max_source_resolution` param is specified.
       '';
 
       query.partial-response = mkFlagParam ''
         Enable partial response for queries if no
-        <literal>partial_response</literal> param is specified.
+        `partial_response` param is specified.
       '';
 
       query.default-evaluation-interval = mkParamDef types.str "1m" ''
@@ -426,7 +426,7 @@ let
       store.response-timeout = mkParamDef types.str "0ms" ''
         If a Store doesn't send any data in this specified duration then a
         Store will be ignored and partial data will be returned if it's
-        enabled. <literal>0</literal> disables timeout.
+        enabled. `0` disables timeout.
       '';
     };
 
@@ -440,7 +440,7 @@ let
       '';
 
       stateDir = mkStateDirParam "data-dir" "thanos-rule" ''
-        Data directory relative to <literal>/var/lib</literal>.
+        Data directory relative to `/var/lib`.
       '';
 
       rule-files = mkListParam "rule-file" ''
@@ -464,9 +464,9 @@ let
 
         Ruler claims success if push to at least one alertmanager from
         discovered succeeds. The scheme may be prefixed with
-        <literal>dns+</literal> or <literal>dnssrv+</literal> to detect
+        `dns+` or `dnssrv+` to detect
         Alertmanager IPs through respective DNS lookups. The port defaults to
-        <literal>9093</literal> or the SRV record's value. The URL path is
+        `9093` or the SRV record's value. The URL path is
         used as a prefix for the regular Alertmanager API path.
       '';
 
@@ -491,7 +491,7 @@ let
 
         This allows thanos UI to be served on a sub-path.
 
-        This option is analogous to <literal>--web.route-prefix</literal> of Promethus.
+        This option is analogous to `--web.route-prefix` of Promethus.
       '';
 
       web.external-prefix = mkParam types.str ''
@@ -499,7 +499,7 @@ let
         interface.
 
         Actual endpoints are still served on / or the
-        <option>web.route-prefix</option>. This allows thanos UI to be served
+        {option}`web.route-prefix`. This allows thanos UI to be served
         behind a reverse proxy that strips a URL sub-path.
       '';
 
@@ -508,23 +508,23 @@ let
         redirects.
 
         This option is ignored if the option
-        <option>web.external-prefix</option> is set.
+        {option}`web.external-prefix` is set.
 
         Security risk: enable this option only if a reverse proxy in front of
         thanos is resetting the header.
 
-        The header <literal>X-Forwarded-Prefix</literal> can be useful, for
+        The header `X-Forwarded-Prefix` can be useful, for
         example, if Thanos UI is served via Traefik reverse proxy with
-        <literal>PathPrefixStrip</literal> option enabled, which sends the
-        stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+        `PathPrefixStrip` option enabled, which sends the
+        stripped prefix value in `X-Forwarded-Prefix`
         header. This allows thanos UI to be served on a sub-path.
       '';
 
       query.addresses = mkListParam "query" ''
         Addresses of statically configured query API servers.
 
-        The scheme may be prefixed with <literal>dns+</literal> or
-        <literal>dnssrv+</literal> to detect query API servers through
+        The scheme may be prefixed with `dns+` or
+        `dnssrv+` to detect query API servers through
         respective DNS lookups.
       '';
 
@@ -545,11 +545,11 @@ let
     compact = params.log // params.tracing cfg.compact // params.objstore cfg.compact // {
 
       http-address = mkParamDef types.str "0.0.0.0:10902" ''
-        Listen <literal>host:port</literal> for HTTP endpoints.
+        Listen `host:port` for HTTP endpoints.
       '';
 
       stateDir = mkStateDirParam "data-dir" "thanos-compact" ''
-        Data directory relative to <literal>/var/lib</literal>
+        Data directory relative to `/var/lib`
         in which to cache blocks and process compactions.
       '';
 
@@ -562,28 +562,28 @@ let
       retention.resolution-raw = mkParamDef types.str "0d" ''
         How long to retain raw samples in bucket.
 
-        <literal>0d</literal> - disables this retention
+        `0d` - disables this retention
       '';
 
       retention.resolution-5m = mkParamDef types.str "0d" ''
         How long to retain samples of resolution 1 (5 minutes) in bucket.
 
-        <literal>0d</literal> - disables this retention
+        `0d` - disables this retention
       '';
 
       retention.resolution-1h = mkParamDef types.str "0d" ''
         How long to retain samples of resolution 2 (1 hour) in bucket.
 
-        <literal>0d</literal> - disables this retention
+        `0d` - disables this retention
       '';
 
       startAt = {
         toArgs = _opt: startAt: flagToArgs "wait" (startAt == null);
         option = nullOpt types.str ''
-          When this option is set to a <literal>systemd.time</literal>
+          When this option is set to a `systemd.time`
           specification the Thanos compactor will run at the specified period.
 
-          When this option is <literal>null</literal> the Thanos compactor service
+          When this option is `null` the Thanos compactor service
           will run continuously. So it will not exit after all compactions have
           been processed but wait for new work.
         '';
@@ -609,7 +609,7 @@ let
     downsample = params.log // params.tracing cfg.downsample // params.objstore cfg.downsample // {
 
       stateDir = mkStateDirParam "data-dir" "thanos-downsample" ''
-        Data directory relative to <literal>/var/lib</literal>
+        Data directory relative to `/var/lib`
         in which to cache blocks and process downsamplings.
       '';
 
@@ -622,7 +622,7 @@ let
       '';
 
       stateDir = mkStateDirParam "tsdb.path" "thanos-receive" ''
-        Data directory relative to <literal>/var/lib</literal> of TSDB.
+        Data directory relative to `/var/lib` of TSDB.
       '';
 
       labels = mkAttrsParam "labels" ''
@@ -635,7 +635,7 @@ let
       tsdb.retention = mkParamDef types.str "15d" ''
         How long to retain raw samples on local storage.
 
-        <literal>0d</literal> - disables this retention
+        `0d` - disables this retention
       '';
     };
 
@@ -667,46 +667,46 @@ in {
 
     sidecar = paramsToOptions params.sidecar // {
       enable = mkEnableOption
-        "the Thanos sidecar for Prometheus server";
+        (lib.mdDoc "the Thanos sidecar for Prometheus server");
       arguments = mkArgumentsOption "sidecar";
     };
 
     store = paramsToOptions params.store // {
       enable = mkEnableOption
-        "the Thanos store node giving access to blocks in a bucket provider.";
+        (lib.mdDoc "the Thanos store node giving access to blocks in a bucket provider.");
       arguments = mkArgumentsOption "store";
     };
 
     query = paramsToOptions params.query // {
       enable = mkEnableOption
-        ("the Thanos query node exposing PromQL enabled Query API " +
-         "with data retrieved from multiple store nodes");
+        (lib.mdDoc ("the Thanos query node exposing PromQL enabled Query API " +
+         "with data retrieved from multiple store nodes"));
       arguments = mkArgumentsOption "query";
     };
 
     rule = paramsToOptions params.rule // {
       enable = mkEnableOption
-        ("the Thanos ruler service which evaluates Prometheus rules against" +
-        " given Query nodes, exposing Store API and storing old blocks in bucket");
+        (lib.mdDoc ("the Thanos ruler service which evaluates Prometheus rules against" +
+        " given Query nodes, exposing Store API and storing old blocks in bucket"));
       arguments = mkArgumentsOption "rule";
     };
 
     compact = paramsToOptions params.compact // {
       enable = mkEnableOption
-        "the Thanos compactor which continuously compacts blocks in an object store bucket";
+        (lib.mdDoc "the Thanos compactor which continuously compacts blocks in an object store bucket");
       arguments = mkArgumentsOption "compact";
     };
 
     downsample = paramsToOptions params.downsample // {
       enable = mkEnableOption
-        "the Thanos downsampler which continuously downsamples blocks in an object store bucket";
+        (lib.mdDoc "the Thanos downsampler which continuously downsamples blocks in an object store bucket");
       arguments = mkArgumentsOption "downsample";
     };
 
     receive = paramsToOptions params.receive // {
       enable = mkEnableOption
-        ("the Thanos receiver which accept Prometheus remote write API requests " +
-         "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)");
+        (lib.mdDoc ("the Thanos receiver which accept Prometheus remote write API requests " +
+         "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)"));
       arguments = mkArgumentsOption "receive";
     };
   };
diff --git a/nixpkgs/nixos/modules/services/monitoring/tremor-rs.nix b/nixpkgs/nixos/modules/services/monitoring/tremor-rs.nix
new file mode 100644
index 000000000000..213e8a474868
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/tremor-rs.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.tremor-rs;
+
+  loggerSettingsFormat = pkgs.formats.yaml { };
+  loggerConfigFile = loggerSettingsFormat.generate "logger.yaml" cfg.loggerSettings;
+in {
+
+  options = {
+    services.tremor-rs = {
+      enable = lib.mkEnableOption (lib.mdDoc "Tremor event- or stream-processing system");
+
+      troyFileList = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = lib.mdDoc "List of troy files to load.";
+      };
+
+      tremorLibDir = mkOption {
+        type = types.path;
+        default = "";
+        description = lib.mdDoc "Directory where to find /lib containing tremor script files";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = lib.mdDoc "The host tremor should be listening on";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 9898;
+        description = lib.mdDoc "the port tremor should be listening on";
+      };
+
+      loggerSettings = mkOption {
+        description = lib.mdDoc "Tremor logger configuration";
+        default = {};
+        type = loggerSettingsFormat.type;
+
+        example = {
+          refresh_rate = "30 seconds";
+          appenders.stdout.kind = "console";
+          root = {
+            level = "warn";
+            appenders = [ "stdout" ];
+          };
+          loggers = {
+            tremor_runtime = {
+              level = "debug";
+              appenders = [ "stdout" ];
+              additive = false;
+            };
+            tremor = {
+              level = "debug";
+              appenders = [ "stdout" ];
+              additive = false;
+            };
+          };
+        };
+
+        defaultText = literalExpression ''
+          {
+            refresh_rate = "30 seconds";
+            appenders.stdout.kind = "console";
+            root = {
+              level = "warn";
+              appenders = [ "stdout" ];
+            };
+            loggers = {
+              tremor_runtime = {
+                level = "debug";
+                appenders = [ "stdout" ];
+                additive = false;
+              };
+              tremor = {
+                level = "debug";
+                appenders = [ "stdout" ];
+                additive = false;
+              };
+            };
+          }
+        '';
+
+      };
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+
+    environment.systemPackages = [ pkgs.tremor-rs ] ;
+
+    systemd.services.tremor-rs = {
+      description = "Tremor event- or stream-processing system";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      environment.TREMOR_PATH = "${pkgs.tremor-rs}/lib:${cfg.tremorLibDir}";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.tremor-rs}/bin/tremor --logger-config ${loggerConfigFile} server run ${concatStringsSep " " cfg.troyFileList} --api-host ${cfg.host}:${toString cfg.port}";
+        DynamicUser = true;
+        Restart = "always";
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/tuptime.nix b/nixpkgs/nixos/modules/services/monitoring/tuptime.nix
index 770fbee2a844..97cc37526254 100644
--- a/nixpkgs/nixos/modules/services/monitoring/tuptime.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/tuptime.nix
@@ -10,7 +10,7 @@ in {
 
   options.services.tuptime = {
 
-    enable = mkEnableOption "the total uptime service";
+    enable = mkEnableOption (lib.mdDoc "the total uptime service");
 
     timer = {
       enable = mkOption {
@@ -54,8 +54,8 @@ in {
             Type = "oneshot";
             User = "_tuptime";
             RemainAfterExit = true;
-            ExecStart = "${pkgs.tuptime}/bin/tuptime -x";
-            ExecStop = "${pkgs.tuptime}/bin/tuptime -xg";
+            ExecStart = "${pkgs.tuptime}/bin/tuptime -q";
+            ExecStop = "${pkgs.tuptime}/bin/tuptime -qg";
           };
         };
 
@@ -64,7 +64,7 @@ in {
           serviceConfig = {
             Type = "oneshot";
             User = "_tuptime";
-            ExecStart = "${pkgs.tuptime}/bin/tuptime -x";
+            ExecStart = "${pkgs.tuptime}/bin/tuptime -q";
           };
         };
       };
diff --git a/nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix b/nixpkgs/nixos/modules/services/monitoring/unpoller.nix
index a955bf4907fc..557e2bff4c26 100644
--- a/nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/unpoller.nix
@@ -3,15 +3,19 @@
 with lib;
 
 let
-  cfg = config.services.unifi-poller;
+  cfg = config.services.unpoller;
 
-  configFile = pkgs.writeText "unifi-poller.json" (generators.toJSON {} {
+  configFile = pkgs.writeText "unpoller.json" (generators.toJSON {} {
     inherit (cfg) poller influxdb loki prometheus unifi;
   });
 
 in {
-  options.services.unifi-poller = {
-    enable = mkEnableOption "unifi-poller";
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "unifi-poller" ] [ "services" "unpoller" ])
+  ];
+
+  options.services.unpoller = {
+    enable = mkEnableOption (lib.mdDoc "unpoller");
 
     poller = {
       debug = mkOption {
@@ -43,7 +47,7 @@ in {
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Whether to disable the prometheus ouput plugin.
+          Whether to disable the prometheus output plugin.
         '';
       };
       http_listen = mkOption {
@@ -67,7 +71,7 @@ in {
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Whether to disable the influxdb ouput plugin.
+          Whether to disable the influxdb output plugin.
         '';
       };
       url = mkOption {
@@ -86,8 +90,8 @@ in {
       };
       pass = mkOption {
         type = types.path;
-        default = pkgs.writeText "unifi-poller-influxdb-default.password" "unifipoller";
-        defaultText = literalExpression "unifi-poller-influxdb-default.password";
+        default = pkgs.writeText "unpoller-influxdb-default.password" "unifipoller";
+        defaultText = literalExpression "unpoller-influxdb-default.password";
         description = lib.mdDoc ''
           Path of a file containing the password for influxdb.
           This file needs to be readable by the unifi-poller user.
@@ -135,8 +139,8 @@ in {
       };
       pass = mkOption {
         type = types.path;
-        default = pkgs.writeText "unifi-poller-loki-default.password" "";
-        defaultText = "unifi-poller-influxdb-default.password";
+        default = pkgs.writeText "unpoller-loki-default.password" "";
+        defaultText = "unpoller-influxdb-default.password";
         description = lib.mdDoc ''
           Path of a file containing the password for Loki.
           This file needs to be readable by the unifi-poller user.
@@ -184,8 +188,8 @@ in {
         };
         pass = mkOption {
           type = types.path;
-          default = pkgs.writeText "unifi-poller-unifi-default.password" "unifi";
-          defaultText = literalExpression "unifi-poller-unifi-default.password";
+          default = pkgs.writeText "unpoller-unifi-default.password" "unifi";
+          defaultText = literalExpression "unpoller-unifi-default.password";
           description = lib.mdDoc ''
             Path of a file containing the password for the unifi service user.
             This file needs to be readable by the unifi-poller user.
@@ -303,7 +307,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.unifi-poller}/bin/unifi-poller --config ${configFile}";
+        ExecStart = "${pkgs.unpoller}/bin/unpoller --config ${configFile}";
         Restart = "always";
         PrivateTmp = true;
         ProtectHome = true;
diff --git a/nixpkgs/nixos/modules/services/monitoring/ups.nix b/nixpkgs/nixos/modules/services/monitoring/ups.nix
index 8af2c2a1f253..bb11b6a1c1d0 100644
--- a/nixpkgs/nixos/modules/services/monitoring/ups.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/ups.nix
@@ -12,7 +12,7 @@ let
   upsOptions = {name, config, ...}:
   {
     options = {
-      # This can be infered from the UPS model by looking at
+      # This can be inferred from the UPS model by looking at
       # /nix/store/nut/share/driver.list
       driver = mkOption {
         type = types.str;
@@ -116,7 +116,7 @@ in
       mode = mkOption {
         default = "standalone";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The MODE determines which part of the NUT is to be started, and
           which configuration files must be modified.
 
@@ -228,7 +228,7 @@ in
           "}
         '';
       "nut/upssched.conf".source = cfg.schedulerRules;
-      # These file are containing private informations and thus should not
+      # These file are containing private information and thus should not
       # be stored inside the Nix store.
       /*
       "nut/upsd.conf".source = "";
diff --git a/nixpkgs/nixos/modules/services/monitoring/uptime-kuma.nix b/nixpkgs/nixos/modules/services/monitoring/uptime-kuma.nix
new file mode 100644
index 000000000000..7027046b2425
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/uptime-kuma.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uptime-kuma;
+in
+{
+
+  meta.maintainers = [ lib.maintainers.julienmalka ];
+
+  options = {
+    services.uptime-kuma = {
+      enable = mkEnableOption (mdDoc "Uptime Kuma, this assumes a reverse proxy to be set");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.uptime-kuma;
+        defaultText = literalExpression "pkgs.uptime-kuma";
+        description = lib.mdDoc "Uptime Kuma package to use.";
+      };
+
+      appriseSupport = mkEnableOption (mdDoc "apprise support for notifications");
+
+      settings = lib.mkOption {
+        type = lib.types.submodule { freeformType = with lib.types; attrsOf str; };
+        default = { };
+        example = {
+          PORT = "4000";
+          NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+        };
+        description = lib.mdDoc ''
+          Additional configuration for Uptime Kuma, see
+          <https://github.com/louislam/uptime-kuma/wiki/Environment-Variables>
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.uptime-kuma.settings = {
+      DATA_DIR = "/var/lib/uptime-kuma/";
+      NODE_ENV = mkDefault "production";
+      HOST = mkDefault "127.0.0.1";
+      PORT = mkDefault "3001";
+    };
+
+    systemd.services.uptime-kuma = {
+      description = "Uptime Kuma";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = cfg.settings;
+      path = with pkgs; [ unixtools.ping ] ++ lib.optional cfg.appriseSupport apprise;
+      serviceConfig = {
+        Type = "simple";
+        StateDirectory = "uptime-kuma";
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/uptime-kuma-server";
+        Restart = "on-failure";
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        NoNewPrivileges = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+      };
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/monitoring/uptime.nix b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
index 24ca7c3763f4..7bf9e593c95e 100644
--- a/nixpkgs/nixos/modules/services/monitoring/uptime.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
@@ -51,9 +51,9 @@ in {
       type = types.bool;
     };
 
-    enableWebService = mkEnableOption "the uptime monitoring program web service";
+    enableWebService = mkEnableOption (lib.mdDoc "the uptime monitoring program web service");
 
-    enableSeparateMonitoringService = mkEnableOption "the uptime monitoring service" // {
+    enableSeparateMonitoringService = mkEnableOption (lib.mdDoc "the uptime monitoring service") // {
       default = cfg.enableWebService;
       defaultText = literalExpression "config.${opt.enableWebService}";
     };
diff --git a/nixpkgs/nixos/modules/services/monitoring/vmagent.nix b/nixpkgs/nixos/modules/services/monitoring/vmagent.nix
new file mode 100644
index 000000000000..c793bb073199
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/vmagent.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.services.vmagent;
+  settingsFormat = pkgs.formats.json { };
+in {
+  options.services.vmagent = {
+    enable = mkEnableOption (lib.mdDoc "vmagent");
+
+    user = mkOption {
+      default = "vmagent";
+      type = types.str;
+      description = lib.mdDoc ''
+        User account under which vmagent runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "vmagent";
+      description = lib.mdDoc ''
+        Group under which vmagent runs.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.vmagent;
+      defaultText = lib.literalMD "pkgs.vmagent";
+      type = types.package;
+      description = lib.mdDoc ''
+        vmagent package to use.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/vmagent";
+      description = lib.mdDoc ''
+        The directory where vmagent stores its data files.
+      '';
+    };
+
+    remoteWriteUrl = mkOption {
+      default = "http://localhost:8428/api/v1/write";
+      type = types.str;
+      description = lib.mdDoc ''
+        The storage endpoint such as VictoriaMetrics
+      '';
+    };
+
+    prometheusConfig = mkOption {
+      type = lib.types.submodule { freeformType = settingsFormat.type; };
+      description = lib.mdDoc ''
+        Config for prometheus style metrics
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to open the firewall for the default ports.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = mkIf (cfg.group == "vmagent") { vmagent = { }; };
+
+    users.users = mkIf (cfg.user == "vmagent") {
+      vmagent = {
+        group = cfg.group;
+        description = "vmagent daemon user";
+        home = cfg.dataDir;
+        isSystemUser = true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ 8429 ];
+
+    systemd.services.vmagent = let
+      prometheusConfig = settingsFormat.generate "prometheusConfig.yaml" cfg.prometheusConfig;
+    in {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "vmagent system service";
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Type = "simple";
+        Restart = "on-failure";
+        WorkingDirectory = cfg.dataDir;
+        ExecStart = "${cfg.package}/bin/vmagent -remoteWrite.url=${cfg.remoteWriteUrl} -promscrape.config=${prometheusConfig}";
+      };
+    };
+
+    systemd.tmpfiles.rules =
+      [ "d '${cfg.dataDir}' 0755 ${cfg.user} ${cfg.group} -" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/vmalert.nix b/nixpkgs/nixos/modules/services/monitoring/vmalert.nix
new file mode 100644
index 000000000000..27fb34e199b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/vmalert.nix
@@ -0,0 +1,136 @@
+{ config, pkgs, lib, ... }: with lib;
+let
+  cfg = config.services.vmalert;
+
+  format = pkgs.formats.yaml {};
+
+  confOpts = concatStringsSep " \\\n" (mapAttrsToList mkLine (filterAttrs (_: v: v != false) cfg.settings));
+  confType = with types;
+    let
+      valueType = oneOf [ bool int path str ];
+    in
+    attrsOf (either valueType (listOf valueType));
+
+  mkLine = key: value:
+    if value == true then "-${key}"
+    else if isList value then concatMapStringsSep " " (v: "-${key}=${escapeShellArg (toString v)}") value
+    else "-${key}=${escapeShellArg (toString value)}"
+  ;
+in
+{
+  # interface
+  options.services.vmalert = {
+    enable = mkEnableOption (mdDoc "vmalert");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.victoriametrics;
+      defaultText = "pkgs.victoriametrics";
+      description = mdDoc ''
+        The VictoriaMetrics derivation to use.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = confType;
+        options = {
+
+          "datasource.url" = mkOption {
+            type = types.nonEmptyStr;
+            example = "http://localhost:8428";
+            description = mdDoc ''
+              Datasource compatible with Prometheus HTTP API.
+            '';
+          };
+
+          "notifier.url" = mkOption {
+            type = with types; listOf nonEmptyStr;
+            default = [];
+            example = [ "http://127.0.0.1:9093" ];
+            description = mdDoc ''
+              Prometheus Alertmanager URL. List all Alertmanager URLs if it runs in the cluster mode to ensure high availability.
+            '';
+          };
+
+          "rule" = mkOption {
+            type = with types; listOf path;
+            description = mdDoc ''
+              Path to the files with alerting and/or recording rules.
+
+              ::: {.note}
+              Consider using the {option}`services.vmalert.rules` option as a convenient alternative for declaring rules
+              directly in the `nix` language.
+              :::
+            '';
+          };
+
+        };
+      };
+      default = { };
+      example = {
+        "datasource.url" = "http://localhost:8428";
+        "datasource.disableKeepAlive" = true;
+        "datasource.showURL" = false;
+        "rule" = [
+          "http://<some-server-addr>/path/to/rules"
+          "dir/*.yaml"
+        ];
+      };
+      description = mdDoc ''
+        `vmalert` configuration, passed via command line flags. Refer to
+        <https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmalert/README.md#configuration>
+        for details on supported values.
+      '';
+    };
+
+    rules = mkOption {
+      type = format.type;
+      default = {};
+      example = {
+        group = [
+          { name = "TestGroup";
+            rules = [
+              { alert = "ExampleAlertAlwaysFiring";
+                expr = ''
+                  sum by(job)
+                  (up == 1)
+                '';
+              }
+            ];
+          }
+        ];
+      };
+      description = mdDoc ''
+        A list of the given alerting or recording rules against configured `"datasource.url"` compatible with
+        Prometheus HTTP API for `vmalert` to execute. Refer to
+        <https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmalert/README.md#rules>
+        for details on supported values.
+      '';
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    environment.etc."vmalert/rules.yml".source = format.generate "rules.yml" cfg.rules;
+
+    services.vmalert.settings.rule = [
+      "/etc/vmalert/rules.yml"
+    ];
+
+    systemd.services.vmalert = {
+      description = "vmalert service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      reloadTriggers = [ config.environment.etc."vmalert/rules.yml".source ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "on-failure";
+        ExecStart = "${cfg.package}/bin/vmalert ${confOpts}";
+        ExecReload = ''${pkgs.coreutils}/bin/kill -SIGHUP "$MAINPID"'';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/vnstat.nix b/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
index 5e19c399568d..a498962ae57e 100644
--- a/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.vnstat;
 in {
   options.services.vnstat = {
-    enable = mkEnableOption "update of network usage statistics via vnstatd";
+    enable = mkEnableOption (lib.mdDoc "update of network usage statistics via vnstatd");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
index f2a8adace69e..b497ecbcdb6c 100644
--- a/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -29,7 +29,7 @@ in
   options = {
 
     services.zabbixAgent = {
-      enable = mkEnableOption "the Zabbix Agent";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix Agent");
 
       package = mkOption {
         type = types.package;
@@ -43,8 +43,8 @@ in
         default = with pkgs; [ nettools ];
         defaultText = literalExpression "with pkgs; [ nettools ]";
         example = literalExpression "with pkgs; [ nettools mysql ]";
-        description = ''
-          Packages to be added to the Zabbix <envar>PATH</envar>.
+        description = lib.mdDoc ''
+          Packages to be added to the Zabbix {env}`PATH`.
           Typically used to add executables for scripts, but can be anything.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
index 9cfe1bdaa205..85da416ba6c3 100644
--- a/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -38,7 +38,7 @@ in
   options = {
 
     services.zabbixProxy = {
-      enable = mkEnableOption "the Zabbix Proxy";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix Proxy");
 
       server = mkOption {
         type = types.str;
@@ -61,8 +61,8 @@ in
         type = types.listOf types.package;
         default = with pkgs; [ nettools nmap traceroute ];
         defaultText = literalExpression "[ nettools nmap traceroute ]";
-        description = ''
-          Packages to be added to the Zabbix <envar>PATH</envar>.
+        description = lib.mdDoc ''
+          Packages to be added to the Zabbix {env}`PATH`.
           Typically used to add executables for scripts, but can be anything.
         '';
       };
@@ -102,7 +102,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
           defaultText = literalExpression ''
             if config.${opt.database.type} == "mysql"
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
index 566ec4ab2f6d..2b50280e3969 100644
--- a/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
@@ -40,7 +40,7 @@ in
   options = {
 
     services.zabbixServer = {
-      enable = mkEnableOption "the Zabbix Server";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix Server");
 
       package = mkOption {
         type = types.package;
@@ -53,8 +53,8 @@ in
         type = types.listOf types.package;
         default = with pkgs; [ nettools nmap traceroute ];
         defaultText = literalExpression "[ nettools nmap traceroute ]";
-        description = ''
-          Packages to be added to the Zabbix <envar>PATH</envar>.
+        description = lib.mdDoc ''
+          Packages to be added to the Zabbix {env}`PATH`.
           Typically used to add executables for scripts, but can be anything.
         '';
       };
@@ -94,7 +94,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
           defaultText = literalExpression ''
             if config.${opt.database.type} == "mysql"
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
index 2437aba86e4f..22d58f29cb81 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
@@ -72,7 +72,7 @@ in
   options.services.ceph = {
     # Ceph has a monolithic configuration file but different sections for
     # each daemon, a separate client section and a global section
-    enable = mkEnableOption "Ceph global configuration";
+    enable = mkEnableOption (lib.mdDoc "Ceph global configuration");
 
     global = {
       fsid = mkOption {
@@ -201,7 +201,7 @@ in
     };
 
     mgr = {
-      enable = mkEnableOption "Ceph MGR daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph MGR daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
@@ -221,7 +221,7 @@ in
     };
 
     mon = {
-      enable = mkEnableOption "Ceph MON daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph MON daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
@@ -241,7 +241,7 @@ in
     };
 
     osd = {
-      enable = mkEnableOption "Ceph OSD daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph OSD daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
@@ -269,7 +269,7 @@ in
     };
 
     mds = {
-      enable = mkEnableOption "Ceph MDS daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph MDS daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
@@ -289,7 +289,7 @@ in
     };
 
     rgw = {
-      enable = mkEnableOption "Ceph RadosGW daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph RadosGW daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
@@ -304,7 +304,7 @@ in
     };
 
     client = {
-      enable = mkEnableOption "Ceph client configuration";
+      enable = mkEnableOption (lib.mdDoc "Ceph client configuration");
       extraConfig = mkOption {
         type = with types; attrsOf (attrsOf str);
         default = {};
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
index 99aa26feb6f1..5c3e197b687d 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -33,7 +33,7 @@ in
 
     services.glusterfs = {
 
-      enable = mkEnableOption "GlusterFS Daemon";
+      enable = mkEnableOption (lib.mdDoc "GlusterFS Daemon");
 
       logLevel = mkOption {
         type = types.enum ["DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL" "TRACE" "NONE"];
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
deleted file mode 100644
index af4b725bf21b..000000000000
--- a/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
+++ /dev/null
@@ -1,323 +0,0 @@
-{ config, lib, pkgs, utils, ... }:
-with lib;
-let
-  cfg = config.services.ipfs;
-
-  ipfsFlags = utils.escapeSystemdExecArgs (
-    optional cfg.autoMount "--mount" ++
-    optional cfg.enableGC "--enable-gc" ++
-    optional (cfg.serviceFdlimit != null) "--manage-fdlimit=false" ++
-    optional (cfg.defaultMode == "offline") "--offline" ++
-    optional (cfg.defaultMode == "norouting") "--routing=none" ++
-    cfg.extraFlags
-  );
-
-  profile =
-    if cfg.localDiscovery
-    then "local-discovery"
-    else "server";
-
-  splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
-
-  multiaddrToListenStream = addrRaw:
-    let
-      addr = splitMulitaddr addrRaw;
-      s = builtins.elemAt addr;
-    in
-    if s 0 == "ip4" && s 2 == "tcp"
-    then "${s 1}:${s 3}"
-    else if s 0 == "ip6" && s 2 == "tcp"
-    then "[${s 1}]:${s 3}"
-    else if s 0 == "unix"
-    then "/${lib.concatStringsSep "/" (lib.tail addr)}"
-    else null; # not valid for listen stream, skip
-
-  multiaddrToListenDatagram = addrRaw:
-    let
-      addr = splitMulitaddr addrRaw;
-      s = builtins.elemAt addr;
-    in
-    if s 0 == "ip4" && s 2 == "udp"
-    then "${s 1}:${s 3}"
-    else if s 0 == "ip6" && s 2 == "udp"
-    then "[${s 1}]:${s 3}"
-    else null; # not valid for listen datagram, skip
-
-in
-{
-
-  ###### interface
-
-  options = {
-
-    services.ipfs = {
-
-      enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)";
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ipfs;
-        defaultText = literalExpression "pkgs.ipfs";
-        description = lib.mdDoc "Which IPFS package to use.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "ipfs";
-        description = lib.mdDoc "User under which the IPFS daemon runs";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "ipfs";
-        description = lib.mdDoc "Group under which the IPFS daemon runs";
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default =
-          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 = lib.mdDoc "The data dir for IPFS";
-      };
-
-      defaultMode = mkOption {
-        type = types.enum [ "online" "offline" "norouting" ];
-        default = "online";
-        description = lib.mdDoc "systemd service that is enabled by default";
-      };
-
-      autoMount = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether IPFS should try to mount /ipfs and /ipns at startup.";
-      };
-
-      autoMigrate = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc "Whether IPFS should try to run the fs-repo-migration at startup.";
-      };
-
-      ipfsMountDir = mkOption {
-        type = types.str;
-        default = "/ipfs";
-        description = lib.mdDoc "Where to mount the IPFS namespace to";
-      };
-
-      ipnsMountDir = mkOption {
-        type = types.str;
-        default = "/ipns";
-        description = lib.mdDoc "Where to mount the IPNS namespace to";
-      };
-
-      gatewayAddress = mkOption {
-        type = types.str;
-        default = "/ip4/127.0.0.1/tcp/8080";
-        description = lib.mdDoc "Where the IPFS Gateway can be reached";
-      };
-
-      apiAddress = mkOption {
-        type = types.str;
-        default = "/ip4/127.0.0.1/tcp/5001";
-        description = lib.mdDoc "Where IPFS exposes its API to";
-      };
-
-      swarmAddress = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "/ip4/0.0.0.0/tcp/4001"
-          "/ip6/::/tcp/4001"
-          "/ip4/0.0.0.0/udp/4001/quic"
-          "/ip6/::/udp/4001/quic"
-        ];
-        description = lib.mdDoc "Where IPFS listens for incoming p2p connections";
-      };
-
-      enableGC = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether to enable automatic garbage collection";
-      };
-
-      emptyRepo = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "If set to true, the repo won't be initialized with help files";
-      };
-
-      extraConfig = mkOption {
-        type = types.attrs;
-        description = lib.mdDoc ''
-          Attrset of daemon configuration to set using {command}`ipfs config`, every time the daemon starts.
-          These are applied last, so may override configuration set by other options in this module.
-          Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
-        '';
-        default = { };
-        example = {
-          Datastore.StorageMax = "100GB";
-          Discovery.MDNS.Enabled = false;
-          Bootstrap = [
-            "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
-            "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
-          ];
-          Swarm.AddrFilters = null;
-        };
-
-      };
-
-      extraFlags = mkOption {
-        type = types.listOf types.str;
-        description = lib.mdDoc "Extra flags passed to the IPFS daemon";
-        default = [ ];
-      };
-
-      localDiscovery = mkOption {
-        type = types.bool;
-        description = lib.mdDoc ''Whether to enable local discovery for the ipfs daemon.
-          This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
-        '';
-        default = false;
-      };
-
-      serviceFdlimit = mkOption {
-        type = types.nullOr types.int;
-        default = null;
-        description = lib.mdDoc "The fdlimit for the IPFS systemd unit or `null` to have the daemon attempt to manage it";
-        example = 64 * 1024;
-      };
-
-      startWhenNeeded = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether to use socket activation to start IPFS when needed.";
-      };
-
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ cfg.package ];
-    environment.variables.IPFS_PATH = cfg.dataDir;
-
-    # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
-    boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
-
-    programs.fuse = mkIf cfg.autoMount {
-      userAllowOther = true;
-    };
-
-    users.users = mkIf (cfg.user == "ipfs") {
-      ipfs = {
-        group = cfg.group;
-        home = cfg.dataDir;
-        createHome = false;
-        uid = config.ids.uids.ipfs;
-        description = "IPFS daemon user";
-        packages = [
-          pkgs.ipfs-migrator
-        ];
-      };
-    };
-
-    users.groups = mkIf (cfg.group == "ipfs") {
-      ipfs.gid = config.ids.gids.ipfs;
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
-    ] ++ optionals cfg.autoMount [
-      "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
-    ];
-
-    # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself
-    systemd.packages = if cfg.autoMount
-      then [ cfg.package.systemd_unit ]
-      else [ cfg.package.systemd_unit_hardened ];
-
-    systemd.services.ipfs = {
-      path = [ "/run/wrappers" cfg.package ];
-      environment.IPFS_PATH = cfg.dataDir;
-
-      preStart = ''
-        if [[ ! -f "$IPFS_PATH/config" ]]; then
-          ipfs init ${optionalString cfg.emptyRepo "-e"} --profile=${profile}
-        else
-          # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
-          rm -vf "$IPFS_PATH/api"
-      '' + optionalString cfg.autoMigrate ''
-        ${pkgs.ipfs-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
-      '' + ''
-          ipfs --offline config profile apply ${profile} >/dev/null
-        fi
-      '' + optionalString cfg.autoMount ''
-        ipfs --offline config Mounts.FuseAllowOther --json true
-        ipfs --offline config Mounts.IPFS ${cfg.ipfsMountDir}
-        ipfs --offline config Mounts.IPNS ${cfg.ipnsMountDir}
-      '' + ''
-        ipfs --offline config show \
-          | ${pkgs.jq}/bin/jq '. * $extraConfig' --argjson extraConfig ${
-              escapeShellArg (builtins.toJSON (
-                recursiveUpdate
-                  {
-                    Addresses.API = cfg.apiAddress;
-                    Addresses.Gateway = cfg.gatewayAddress;
-                    Addresses.Swarm = cfg.swarmAddress;
-                  }
-                  cfg.extraConfig
-              ))
-            } \
-          | ipfs --offline config replace -
-      '';
-      serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${ipfsFlags}" ];
-        User = cfg.user;
-        Group = cfg.group;
-        StateDirectory = "";
-        ReadWritePaths = optionals (!cfg.autoMount) [ "" cfg.dataDir ];
-      } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
-    } // optionalAttrs (!cfg.startWhenNeeded) {
-      wantedBy = [ "default.target" ];
-    };
-
-    systemd.sockets.ipfs-gateway = {
-      wantedBy = [ "sockets.target" ];
-      socketConfig = {
-        ListenStream =
-          let
-            fromCfg = multiaddrToListenStream cfg.gatewayAddress;
-          in
-          [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
-        ListenDatagram =
-          let
-            fromCfg = multiaddrToListenDatagram cfg.gatewayAddress;
-          in
-          [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
-      };
-    };
-
-    systemd.sockets.ipfs-api = {
-      wantedBy = [ "sockets.target" ];
-      # We also include "%t/ipfs.sock" because there is no way to put the "%t"
-      # in the multiaddr.
-      socketConfig.ListenStream =
-        let
-          fromCfg = multiaddrToListenStream cfg.apiAddress;
-        in
-        [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg;
-    };
-  };
-
-  meta = {
-    maintainers = with lib.maintainers; [ Luflosi ];
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/kubo.nix b/nixpkgs/nixos/modules/services/network-filesystems/kubo.nix
new file mode 100644
index 000000000000..a5c370b5be89
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/kubo.nix
@@ -0,0 +1,424 @@
+{ config, lib, pkgs, utils, ... }:
+with lib;
+let
+  cfg = config.services.kubo;
+
+  settingsFormat = pkgs.formats.json {};
+
+  rawDefaultConfig = lib.importJSON (pkgs.runCommand "kubo-default-config" {
+    nativeBuildInputs = [ cfg.package ];
+  } ''
+    export IPFS_PATH="$TMPDIR"
+    ipfs init --empty-repo --profile=${profile}
+    ipfs --offline config show > "$out"
+  '');
+
+  # Remove the PeerID (an attribute of "Identity") of the temporary Kubo repo.
+  # The "Pinning" section contains the "RemoteServices" section, which would prevent
+  # the daemon from starting as that setting can't be changed via ipfs config replace.
+  defaultConfig = builtins.removeAttrs rawDefaultConfig [ "Identity" "Pinning" ];
+
+  customizedConfig = lib.recursiveUpdate defaultConfig cfg.settings;
+
+  configFile = settingsFormat.generate "kubo-config.json" customizedConfig;
+
+  # Create a fake repo containing only the file "api".
+  # $IPFS_PATH will point to this directory instead of the real one.
+  # For some reason the Kubo CLI tools insist on reading the
+  # config file when it exists. But the Kubo daemon sets the file
+  # permissions such that only the ipfs user is allowed to read
+  # this file. This prevents normal users from talking to the daemon.
+  # To work around this terrible design, create a fake repo with no
+  # config file, only an api file and everything should work as expected.
+  fakeKuboRepo = pkgs.writeTextDir "api" ''
+    /unix/run/ipfs.sock
+  '';
+
+  kuboFlags = utils.escapeSystemdExecArgs (
+    optional cfg.autoMount "--mount" ++
+    optional cfg.enableGC "--enable-gc" ++
+    optional (cfg.serviceFdlimit != null) "--manage-fdlimit=false" ++
+    optional (cfg.defaultMode == "offline") "--offline" ++
+    optional (cfg.defaultMode == "norouting") "--routing=none" ++
+    cfg.extraFlags
+  );
+
+  profile =
+    if cfg.localDiscovery
+    then "local-discovery"
+    else "server";
+
+  splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
+
+  multiaddrsToListenStreams = addrIn:
+    let
+      addrs = if builtins.typeOf addrIn == "list"
+      then addrIn else [ addrIn ];
+      unfilteredResult = map multiaddrToListenStream addrs;
+    in
+      builtins.filter (addr: addr != null) unfilteredResult;
+
+  multiaddrsToListenDatagrams = addrIn:
+    let
+      addrs = if builtins.typeOf addrIn == "list"
+      then addrIn else [ addrIn ];
+      unfilteredResult = map multiaddrToListenDatagram addrs;
+    in
+      builtins.filter (addr: addr != null) unfilteredResult;
+
+  multiaddrToListenStream = addrRaw:
+    let
+      addr = splitMulitaddr addrRaw;
+      s = builtins.elemAt addr;
+    in
+    if s 0 == "ip4" && s 2 == "tcp"
+    then "${s 1}:${s 3}"
+    else if s 0 == "ip6" && s 2 == "tcp"
+    then "[${s 1}]:${s 3}"
+    else if s 0 == "unix"
+    then "/${lib.concatStringsSep "/" (lib.tail addr)}"
+    else null; # not valid for listen stream, skip
+
+  multiaddrToListenDatagram = addrRaw:
+    let
+      addr = splitMulitaddr addrRaw;
+      s = builtins.elemAt addr;
+    in
+    if s 0 == "ip4" && s 2 == "udp"
+    then "${s 1}:${s 3}"
+    else if s 0 == "ip6" && s 2 == "udp"
+    then "[${s 1}]:${s 3}"
+    else null; # not valid for listen datagram, skip
+
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.kubo = {
+
+      enable = mkEnableOption (lib.mdDoc "Interplanetary File System (WARNING: may cause severe network degradation)");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.kubo;
+        defaultText = literalExpression "pkgs.kubo";
+        description = lib.mdDoc "Which Kubo package to use.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "ipfs";
+        description = lib.mdDoc "User under which the Kubo daemon runs";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "ipfs";
+        description = lib.mdDoc "Group under which the Kubo daemon runs";
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default =
+          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 = lib.mdDoc "The data dir for Kubo";
+      };
+
+      defaultMode = mkOption {
+        type = types.enum [ "online" "offline" "norouting" ];
+        default = "online";
+        description = lib.mdDoc "systemd service that is enabled by default";
+      };
+
+      autoMount = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether Kubo should try to mount /ipfs and /ipns at startup.";
+      };
+
+      autoMigrate = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Whether Kubo should try to run the fs-repo-migration at startup.";
+      };
+
+      ipfsMountDir = mkOption {
+        type = types.str;
+        default = "/ipfs";
+        description = lib.mdDoc "Where to mount the IPFS namespace to";
+      };
+
+      ipnsMountDir = mkOption {
+        type = types.str;
+        default = "/ipns";
+        description = lib.mdDoc "Where to mount the IPNS namespace to";
+      };
+
+      enableGC = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether to enable automatic garbage collection";
+      };
+
+      emptyRepo = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "If set to false, the repo will be initialized with help files";
+      };
+
+      settings = mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            Addresses.API = mkOption {
+              type = types.oneOf [ types.str (types.listOf types.str) ];
+              default = [ ];
+              description = lib.mdDoc ''
+                Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on.
+                In addition to the multiaddrs listed here, the daemon will also listen on a Unix domain socket.
+                To allow the ipfs CLI tools to communicate with the daemon over that socket,
+                add your user to the correct group, e.g. `users.users.alice.extraGroups = [ config.services.kubo.group ];`
+              '';
+            };
+
+            Addresses.Gateway = mkOption {
+              type = types.oneOf [ types.str (types.listOf types.str) ];
+              default = "/ip4/127.0.0.1/tcp/8080";
+              description = lib.mdDoc "Where the IPFS Gateway can be reached";
+            };
+
+            Addresses.Swarm = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "/ip4/0.0.0.0/tcp/4001"
+                "/ip6/::/tcp/4001"
+                "/ip4/0.0.0.0/udp/4001/quic"
+                "/ip4/0.0.0.0/udp/4001/quic-v1"
+                "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"
+                "/ip6/::/udp/4001/quic"
+                "/ip6/::/udp/4001/quic-v1"
+                "/ip6/::/udp/4001/quic-v1/webtransport"
+              ];
+              description = lib.mdDoc "Where Kubo listens for incoming p2p connections";
+            };
+          };
+        };
+        description = lib.mdDoc ''
+          Attrset of daemon configuration.
+          See [https://github.com/ipfs/kubo/blob/master/docs/config.md](https://github.com/ipfs/kubo/blob/master/docs/config.md) for reference.
+          You can't set `Identity` or `Pinning`.
+        '';
+        default = { };
+        example = {
+          Datastore.StorageMax = "100GB";
+          Discovery.MDNS.Enabled = false;
+          Bootstrap = [
+            "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
+            "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
+          ];
+          Swarm.AddrFilters = null;
+        };
+
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        description = lib.mdDoc "Extra flags passed to the Kubo daemon";
+        default = [ ];
+      };
+
+      localDiscovery = mkOption {
+        type = types.bool;
+        description = lib.mdDoc ''Whether to enable local discovery for the Kubo daemon.
+          This will allow Kubo to scan ports on your local network. Some hosting services will ban you if you do this.
+        '';
+        default = false;
+      };
+
+      serviceFdlimit = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = lib.mdDoc "The fdlimit for the Kubo systemd unit or `null` to have the daemon attempt to manage it";
+        example = 64 * 1024;
+      };
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether to use socket activation to start Kubo when needed.";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !builtins.hasAttr "Identity" cfg.settings;
+        message = ''
+          You can't set services.kubo.settings.Identity because the ``config replace`` subcommand used at startup does not support modifying any of the Identity settings.
+        '';
+      }
+      {
+        assertion = !((builtins.hasAttr "Pinning" cfg.settings) && (builtins.hasAttr "RemoteServices" cfg.settings.Pinning));
+        message = ''
+          You can't set services.kubo.settings.Pinning.RemoteServices because the ``config replace`` subcommand used at startup does not work with it.
+        '';
+      }
+    ];
+
+    environment.systemPackages = [ cfg.package ];
+    environment.variables.IPFS_PATH = fakeKuboRepo;
+
+    # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
+    boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
+
+    programs.fuse = mkIf cfg.autoMount {
+      userAllowOther = true;
+    };
+
+    users.users = mkIf (cfg.user == "ipfs") {
+      ipfs = {
+        group = cfg.group;
+        home = cfg.dataDir;
+        createHome = false;
+        uid = config.ids.uids.ipfs;
+        description = "IPFS daemon user";
+        packages = [
+          pkgs.kubo-migrator
+        ];
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "ipfs") {
+      ipfs.gid = config.ids.gids.ipfs;
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+    ] ++ optionals cfg.autoMount [
+      "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself
+    systemd.packages = if cfg.autoMount
+      then [ cfg.package.systemd_unit ]
+      else [ cfg.package.systemd_unit_hardened ];
+
+    services.kubo.settings = mkIf cfg.autoMount {
+      Mounts.FuseAllowOther = lib.mkDefault true;
+      Mounts.IPFS = lib.mkDefault cfg.ipfsMountDir;
+      Mounts.IPNS = lib.mkDefault cfg.ipnsMountDir;
+    };
+
+    systemd.services.ipfs = {
+      path = [ "/run/wrappers" cfg.package ];
+      environment.IPFS_PATH = cfg.dataDir;
+
+      preStart = ''
+        if [[ ! -f "$IPFS_PATH/config" ]]; then
+          ipfs init --empty-repo=${lib.boolToString cfg.emptyRepo}
+        else
+          # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
+          rm -vf "$IPFS_PATH/api"
+      '' + optionalString cfg.autoMigrate ''
+        ${pkgs.kubo-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
+      '' + ''
+        fi
+        ipfs --offline config show |
+          ${pkgs.jq}/bin/jq -s '.[0].Pinning as $Pinning | .[0].Identity as $Identity | .[1] + {$Identity,$Pinning}' - '${configFile}' |
+
+          # This command automatically injects the private key and other secrets from
+          # the old config file back into the new config file.
+          # Unfortunately, it doesn't keep the original `Identity.PeerID`,
+          # so we need `ipfs config show` and jq above.
+          # See https://github.com/ipfs/kubo/issues/8993 for progress on fixing this problem.
+          # Kubo also wants a specific version of the original "Pinning.RemoteServices"
+          # section (redacted by `ipfs config show`), such that that section doesn't
+          # change when the changes are applied. Whyyyyyy.....
+          ipfs --offline config replace -
+      '';
+      postStop = mkIf cfg.autoMount ''
+        # After an unclean shutdown the fuse mounts at cfg.ipnsMountDir and cfg.ipfsMountDir are locked
+        umount --quiet '${cfg.ipnsMountDir}' '${cfg.ipfsMountDir}' || true
+      '';
+      serviceConfig = {
+        ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = "";
+        ReadWritePaths = optionals (!cfg.autoMount) [ "" cfg.dataDir ];
+      } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
+    } // optionalAttrs (!cfg.startWhenNeeded) {
+      wantedBy = [ "default.target" ];
+    };
+
+    systemd.sockets.ipfs-gateway = {
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream =
+          [ "" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.Gateway);
+        ListenDatagram =
+          [ "" ] ++ (multiaddrsToListenDatagrams cfg.settings.Addresses.Gateway);
+      };
+    };
+
+    systemd.sockets.ipfs-api = {
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        # We also include "%t/ipfs.sock" because there is no way to put the "%t"
+        # in the multiaddr.
+        ListenStream =
+          [ "" "%t/ipfs.sock" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.API);
+        SocketMode = "0660";
+        SocketUser = cfg.user;
+        SocketGroup = cfg.group;
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ Luflosi ];
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "ipfs" "enable" ] [ "services" "kubo" "enable" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "package" ] [ "services" "kubo" "package" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "user" ] [ "services" "kubo" "user" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "group" ] [ "services" "kubo" "group" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "dataDir" ] [ "services" "kubo" "dataDir" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "defaultMode" ] [ "services" "kubo" "defaultMode" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "autoMount" ] [ "services" "kubo" "autoMount" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "autoMigrate" ] [ "services" "kubo" "autoMigrate" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "ipfsMountDir" ] [ "services" "kubo" "ipfsMountDir" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "ipnsMountDir" ] [ "services" "kubo" "ipnsMountDir" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "enableGC" ] [ "services" "kubo" "enableGC" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "emptyRepo" ] [ "services" "kubo" "emptyRepo" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "extraConfig" ] [ "services" "kubo" "settings" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "extraFlags" ] [ "services" "kubo" "extraFlags" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "localDiscovery" ] [ "services" "kubo" "localDiscovery" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "serviceFdlimit" ] [ "services" "kubo" "serviceFdlimit" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "startWhenNeeded" ] [ "services" "kubo" "startWhenNeeded" ])
+    (mkRenamedOptionModule [ "services" "kubo" "extraConfig" ] [ "services" "kubo" "settings" ])
+    (mkRenamedOptionModule [ "services" "kubo" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ])
+    (mkRenamedOptionModule [ "services" "kubo" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ])
+    (mkRenamedOptionModule [ "services" "kubo" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ])
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/litestream/litestream.xml b/nixpkgs/nixos/modules/services/network-filesystems/litestream/default.md
index 598f9be8cf63..8d8486507b77 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/litestream/litestream.xml
+++ b/nixpkgs/nixos/modules/services/network-filesystems/litestream/default.md
@@ -1,23 +1,14 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-litestream">
- <title>Litestream</title>
- <para>
-  <link xlink:href="https://litestream.io/">Litestream</link> is a standalone streaming
-  replication tool for SQLite.
- </para>
+# Litestream {#module-services-litestream}
 
- <section xml:id="module-services-litestream-configuration">
-  <title>Configuration</title>
+[Litestream](https://litestream.io/) is a standalone streaming
+replication tool for SQLite.
 
-  <para>
-   Litestream service is managed by a dedicated user named <literal>litestream</literal>
-   which needs permission to the database file. Here's an example config which gives
-   required permissions to access <link linkend="opt-services.grafana.database.path">
-   grafana database</link>:
-<programlisting>
+## Configuration {#module-services-litestream-configuration}
+
+Litestream service is managed by a dedicated user named `litestream`
+which needs permission to the database file. Here's an example config which gives
+required permissions to access [grafana database](#opt-services.grafana.settings.database.path):
+```
 { pkgs, ... }:
 {
   users.users.litestream.extraGroups = [ "grafana" ];
@@ -58,8 +49,4 @@
     };
   };
 }
-</programlisting>
-  </para>
- </section>
-
-</chapter>
+```
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/litestream/default.nix b/nixpkgs/nixos/modules/services/network-filesystems/litestream/default.nix
index 92ae1d0fd3b9..6e2ec1ccaa3c 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/litestream/default.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/litestream/default.nix
@@ -8,7 +8,7 @@ let
 in
 {
   options.services.litestream = {
-    enable = mkEnableOption "litestream";
+    enable = mkEnableOption (lib.mdDoc "litestream");
 
     package = mkOption {
       description = lib.mdDoc "Package to use.";
@@ -40,8 +40,8 @@ in
       type = types.nullOr types.path;
       default = null;
       example = "/run/secrets/litestream";
-      description = ''
-        Environment file as defined in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)`.
 
         Secrets may be passed to the service without adding them to the
         world-readable Nix store, by specifying placeholder variables as
@@ -54,11 +54,11 @@ in
         variable values. If no value is set then it will be replaced with an
         empty string.
 
-        <programlisting>
+        ```
           # Content of the environment file
           LITESTREAM_ACCESS_KEY_ID=AKIAxxxxxxxxxxxxxxxx
           LITESTREAM_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxx
-        </programlisting>
+        ```
 
         Note that this file needs to be available on the host on which
         this exporter is running.
@@ -94,5 +94,6 @@ in
     };
     users.groups.litestream = {};
   };
-  meta.doc = ./litestream.xml;
+
+  meta.doc = ./default.md;
 }
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/moosefs.nix b/nixpkgs/nixos/modules/services/network-filesystems/moosefs.nix
index 6ad4b37761a8..49cbc89d5a91 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/moosefs.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/moosefs.nix
@@ -52,7 +52,7 @@ let
   chunkserverCfg = settingsFormat.generate
     "mfschunkserver.cfg" cfg.chunkserver.settings;
 
-  # generic template for all deamons
+  # generic template for all daemons
   systemdService = name: extraConfig: configFile: {
     wantedBy = [ "multi-user.target" ];
     wants = [ "network-online.target" ];
@@ -85,7 +85,7 @@ in {
         description = lib.mdDoc "Run daemons as user moosefs instead of root.";
       };
 
-      client.enable = mkEnableOption "Moosefs client.";
+      client.enable = mkEnableOption (lib.mdDoc "Moosefs client");
 
       master = {
         enable = mkOption {
@@ -94,7 +94,7 @@ in {
             Enable Moosefs master daemon.
 
             You need to run `mfsmaster-init` on a freshly installed master server to
-            initialize the `DATA_PATH` direcory.
+            initialize the `DATA_PATH` directory.
           '';
           default = false;
         };
@@ -131,7 +131,7 @@ in {
       };
 
       metalogger = {
-        enable = mkEnableOption "Moosefs metalogger daemon.";
+        enable = mkEnableOption (lib.mdDoc "Moosefs metalogger daemon");
 
         settings = mkOption {
           type = types.submodule {
@@ -149,7 +149,7 @@ in {
       };
 
       chunkserver = {
-        enable = mkEnableOption "Moosefs chunkserver daemon.";
+        enable = mkEnableOption (lib.mdDoc "Moosefs chunkserver daemon");
 
         openFirewall = mkOption {
           type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix b/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
index 838a374ba6c1..a40f68557c0e 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
@@ -10,7 +10,7 @@ in {
   options = {
     services.netatalk = {
 
-      enable = mkEnableOption "the Netatalk AFP fileserver";
+      enable = mkEnableOption (lib.mdDoc "the Netatalk AFP fileserver");
 
       port = mkOption {
         type = types.port;
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
index 80628f4dfaf2..e5e147a8dc33 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -1,13 +1,13 @@
 { config, lib, ...}:
 
 let
-  inherit (lib) concatStringsSep mkOption types;
+  inherit (lib) concatStringsSep mkOption types optionalString;
 
 in {
 
   mkCellServDB = cellName: db: ''
     >${cellName}
-  '' + (concatStringsSep "\n" (map (dbm: if (dbm.ip != "" && dbm.dnsname != "") then dbm.ip + " #" + dbm.dnsname else "")
+  '' + (concatStringsSep "\n" (map (dbm: optionalString (dbm.ip != "" && dbm.dnsname != "") "${dbm.ip} #${dbm.dnsname}")
                                    db))
      + "\n";
 
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
index 1c615d3bfb64..ad0fd7835670 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -4,7 +4,8 @@
 with import ./lib.nix { inherit config lib pkgs; };
 
 let
-  inherit (lib) concatStringsSep literalExpression mkIf mkOption optionalString types;
+  inherit (lib) concatStringsSep literalExpression mkIf mkOption mkEnableOption
+  optionalString types;
 
   bosConfig = pkgs.writeText "BosConfig" (''
     restrictmode 1
@@ -24,9 +25,15 @@ let
     parm ${openafsSrv}/libexec/openafs/salvageserver ${cfg.roles.fileserver.salvageserverArgs}
     parm ${openafsSrv}/libexec/openafs/dasalvager ${cfg.roles.fileserver.salvagerArgs}
     end
-  '') + (optionalString (cfg.roles.database.enable && cfg.roles.backup.enable) ''
+  '') + (optionalString (cfg.roles.database.enable && cfg.roles.backup.enable && (!cfg.roles.backup.enableFabs)) ''
     bnode simple buserver 1
-    parm ${openafsSrv}/libexec/openafs/buserver ${cfg.roles.backup.buserverArgs} ${optionalString (cfg.roles.backup.cellServDB != []) "-cellservdb /etc/openafs/backup/"}
+    parm ${openafsSrv}/libexec/openafs/buserver ${cfg.roles.backup.buserverArgs} ${optionalString useBuCellServDB "-cellservdb /etc/openafs/backup/"}
+    end
+  '') + (optionalString (cfg.roles.database.enable &&
+                         cfg.roles.backup.enable &&
+                         cfg.roles.backup.enableFabs) ''
+    bnode simple buserver 1
+    parm ${lib.getBin pkgs.fabs}/bin/fabsys server --config ${fabsConfFile} ${cfg.roles.backup.fabsArgs}
     end
   ''));
 
@@ -34,12 +41,27 @@ let
     pkgs.writeText "NetInfo" ((concatStringsSep "\nf " cfg.advertisedAddresses) + "\n")
   else null;
 
-  buCellServDB = pkgs.writeText "backup-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.roles.backup.cellServDB);
+  buCellServDB = pkgs.writeText "backup-cellServDB-${cfg.cellName}"
+    (mkCellServDB cfg.cellName cfg.roles.backup.cellServDB);
+
+  useBuCellServDB = (cfg.roles.backup.cellServDB != []) && (!cfg.roles.backup.enableFabs);
 
   cfg = config.services.openafsServer;
 
   udpSizeStr = toString cfg.udpPacketSize;
 
+  fabsConfFile = pkgs.writeText "fabs.yaml" (builtins.toJSON ({
+    afs = {
+      aklog = cfg.package + "/bin/aklog";
+      cell = cfg.cellName;
+      dumpscan = cfg.package + "/bin/afsdump_scan";
+      fs = cfg.package + "/bin/fs";
+      pts = cfg.package + "/bin/pts";
+      vos = cfg.package + "/bin/vos";
+    };
+    k5start.command = (lib.getBin pkgs.kstart) + "/bin/k5start";
+  } // cfg.roles.backup.fabsExtraConfig));
+
 in {
 
   options = {
@@ -80,8 +102,8 @@ in {
       };
 
       package = mkOption {
-        default = pkgs.openafs.server or pkgs.openafs;
-        defaultText = literalExpression "pkgs.openafs.server or pkgs.openafs";
+        default = pkgs.openafs;
+        defaultText = literalExpression "pkgs.openafs";
         type = types.package;
         description = lib.mdDoc "OpenAFS package for the server binaries";
       };
@@ -154,16 +176,20 @@ in {
         };
 
         backup = {
-          enable = mkOption {
-            default = false;
-            type = types.bool;
-            description = lib.mdDoc ''
-              Backup server role. Use in conjunction with the
-              `database` role to maintain the Backup
-              Database. Normally only used in conjunction with tape storage
-              or IBM's Tivoli Storage Manager.
-            '';
-          };
+          enable = mkEnableOption (lib.mdDoc ''
+            Backup server role. When using OpenAFS built-in buserver, use in conjunction with the
+            `database` role to maintain the Backup
+            Database. Normally only used in conjunction with tape storage
+            or IBM's Tivoli Storage Manager.
+
+            For a modern backup server, enable this role and see
+            {option}`enableFabs`.
+          '');
+
+          enableFabs = mkEnableOption (lib.mdDoc ''
+            FABS, the flexible AFS backup system. It stores volumes as dump files, relying on other
+            pre-existing backup solutions for handling them.
+          '');
 
           buserverArgs = mkOption {
             default = "";
@@ -181,6 +207,30 @@ in {
               other database server machines.
             '';
           };
+
+          fabsArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = lib.mdDoc ''
+              Arguments to the fabsys process. See
+              {manpage}`fabsys_server(1)` and
+              {manpage}`fabsys_config(1)`.
+            '';
+          };
+
+          fabsExtraConfig = mkOption {
+            default = {};
+            type = types.attrs;
+            description = lib.mdDoc ''
+              Additional configuration parameters for the FABS backup server.
+            '';
+            example = literalExpression ''
+            {
+              afs.localauth = true;
+              afs.keytab = config.sops.secrets.fabsKeytab.path;
+            }
+            '';
+          };
         };
       };
 
@@ -239,7 +289,7 @@ in {
         mode = "0644";
       };
       buCellServDB = {
-        enable = (cfg.roles.backup.cellServDB != []);
+        enable = useBuCellServDB;
         text = mkCellServDB cfg.cellName cfg.roles.backup.cellServDB;
         target = "openafs/backup/CellServDB";
       };
@@ -257,7 +307,7 @@ in {
         preStart = ''
           mkdir -m 0755 -p /var/openafs
           ${optionalString (netInfo != null) "cp ${netInfo} /var/openafs/netInfo"}
-          ${optionalString (cfg.roles.backup.cellServDB != []) "cp ${buCellServDB}"}
+          ${optionalString useBuCellServDB "cp ${buCellServDB}"}
         '';
         serviceConfig = {
           ExecStart = "${openafsBin}/bin/bosserver -nofork";
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix
index 26cc0e169aec..68f23f477af1 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix
@@ -10,7 +10,7 @@ in {
 
   options = {
     services.orangefs.client = {
-      enable = mkEnableOption "OrangeFS client daemon";
+      enable = mkEnableOption (lib.mdDoc "OrangeFS client daemon");
 
       extraOptions = mkOption {
         type = with types; listOf str;
@@ -21,7 +21,7 @@ in {
       fileSystems = mkOption {
         description = lib.mdDoc ''
           The orangefs file systems to be mounted.
-          This option is prefered over using {option}`fileSystems` directly since
+          This option is preferred over using {option}`fileSystems` directly since
           the pvfs client service needs to be running for it to be mounted.
         '';
 
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix
index 3bc3325e1864..e20e7975ebaa 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -74,7 +74,7 @@ in {
 
   options = {
     services.orangefs.server = {
-      enable = mkEnableOption "OrangeFS server";
+      enable = mkEnableOption (lib.mdDoc "OrangeFS server");
 
       logType = mkOption {
         type = with types; enum [ "file" "syslog" ];
@@ -209,7 +209,7 @@ in {
       after = [ "network-online.target" ];
 
       serviceConfig = {
-        # Run as "simple" in forground mode.
+        # Run as "simple" in foreground mode.
         # This is more reliable
         ExecStart = ''
           ${pkgs.orangefs}/bin/pvfs2-server -d \
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix b/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
index d65113c84b3b..c9d7475395fe 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
@@ -10,7 +10,7 @@ in {
   options = {
     services.rsyncd = {
 
-      enable = mkEnableOption "the rsync daemon";
+      enable = mkEnableOption (lib.mdDoc "the rsync daemon");
 
       port = mkOption {
         default = 873;
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/samba-wsdd.nix b/nixpkgs/nixos/modules/services/network-filesystems/samba-wsdd.nix
index 38980593e768..24407f05de6a 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/samba-wsdd.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/samba-wsdd.nix
@@ -8,17 +8,17 @@ let
 in {
   options = {
     services.samba-wsdd = {
-      enable = mkEnableOption ''
-        Enable Web Services Dynamic Discovery host daemon. This enables (Samba) hosts, like your local NAS device,
+      enable = mkEnableOption (lib.mdDoc ''
+        Web Services Dynamic Discovery host daemon. This enables (Samba) hosts, like your local NAS device,
         to be found by Web Service Discovery Clients like Windows.
-        <note>
-          <para>If you use the firewall consider adding the following:</para>
-          <programlisting>
+
+        ::: {.note}
+        If you use the firewall consider adding the following:
+
             networking.firewall.allowedTCPPorts = [ 5357 ];
             networking.firewall.allowedUDPPorts = [ 3702 ];
-          </programlisting>
-        </note>
-      '';
+        :::
+      '');
       interface = mkOption {
         type = types.nullOr types.str;
         default = null;
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/samba.nix b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
index 7a07b043859b..1310a374abd0 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
@@ -80,16 +80,15 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Samba, which provides file and print
           services to Windows clients through the SMB/CIFS protocol.
 
-          <note>
-            <para>If you use the firewall consider adding the following:</para>
-          <programlisting>
-            services.samba.openFirewall = true;
-          </programlisting>
-          </note>
+          ::: {.note}
+          If you use the firewall consider adding the following:
+
+              services.samba.openFirewall = true;
+          :::
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix b/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
index a816b5757f72..14c0a3d4725f 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
@@ -18,7 +18,7 @@ in
             };
             tub.port = mkOption {
               default = 3458;
-              type = types.int;
+              type = types.port;
               description = lib.mdDoc ''
                 The port on which the introducer will listen.
               '';
@@ -58,7 +58,7 @@ in
             };
             tub.port = mkOption {
               default = 3457;
-              type = types.int;
+              type = types.port;
               description = lib.mdDoc ''
                 The port on which the tub will listen.
 
@@ -80,7 +80,7 @@ in
             };
             web.port = mkOption {
               default = 3456;
-              type = types.int;
+              type = types.port;
               description = lib.mdDoc ''
                 The port on which the Web server will listen.
 
@@ -128,7 +128,7 @@ in
                 The number of shares required to store a file.
               '';
             };
-            storage.enable = mkEnableOption "storage service";
+            storage.enable = mkEnableOption (lib.mdDoc "storage service");
             storage.reservedSpace = mkOption {
               default = "1G";
               type = types.str;
@@ -136,8 +136,8 @@ in
                 The amount of filesystem space to not use for storage.
               '';
             };
-            helper.enable = mkEnableOption "helper service";
-            sftpd.enable = mkEnableOption "SFTP service";
+            helper.enable = mkEnableOption (lib.mdDoc "helper service");
+            sftpd.enable = mkEnableOption (lib.mdDoc "SFTP service");
             sftpd.port = mkOption {
               default = null;
               type = types.nullOr types.int;
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix b/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix
index bd07b8d4381b..34e717025e64 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix
@@ -14,7 +14,7 @@ in
 {
   options = {
     services.webdav-server-rs = {
-      enable = mkEnableOption "WebDAV server";
+      enable = mkEnableOption (lib.mdDoc "WebDAV server");
 
       user = mkOption {
         type = types.str;
@@ -28,6 +28,12 @@ in
         description = lib.mdDoc "Group to run under when setuid is not enabled.";
       };
 
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable debug mode.";
+      };
+
       settings = mkOption {
         type = format.type;
         default = { };
@@ -111,7 +117,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.webdav-server-rs}/bin/webdav-server -c ${cfg.configFile}";
+        ExecStart = "${pkgs.webdav-server-rs}/bin/webdav-server ${lib.optionalString cfg.debug "--debug"} -c ${cfg.configFile}";
 
         CapabilityBoundingSet = [
           "CAP_SETUID"
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/webdav.nix b/nixpkgs/nixos/modules/services/network-filesystems/webdav.nix
index b7c07b8c12c1..a384e58c96bf 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/webdav.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/webdav.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.webdav = {
-      enable = mkEnableOption "WebDAV server";
+      enable = mkEnableOption (lib.mdDoc "WebDAV server");
 
       user = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
index 7b476fc7ac93..926c3c3bd523 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -89,7 +89,7 @@ in
 
     services.xtreemfs = {
 
-      enable = mkEnableOption "XtreemFS";
+      enable = mkEnableOption (lib.mdDoc "XtreemFS");
 
       homeDir = mkOption {
         type = types.path;
@@ -111,7 +111,7 @@ in
         uuid = mkOption {
           example = "eacb6bab-f444-4ebf-a06a-3f72d7465e40";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Must be set to a unique identifier, preferably a UUID according to
             RFC 4122. UUIDs can be generated with `uuidgen` command, found in
             the `util-linux` package.
@@ -180,7 +180,7 @@ in
           '';
         };
         replication = {
-          enable = mkEnableOption "XtreemFS DIR replication plugin";
+          enable = mkEnableOption (lib.mdDoc "XtreemFS DIR replication plugin");
           extraConfig = mkOption {
             type = types.lines;
             example = ''
@@ -236,7 +236,7 @@ in
         uuid = mkOption {
           example = "eacb6bab-f444-4ebf-a06a-3f72d7465e41";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Must be set to a unique identifier, preferably a UUID according to
             RFC 4122. UUIDs can be generated with `uuidgen` command, found in
             the `util-linux` package.
@@ -323,7 +323,7 @@ in
           '';
         };
         replication = {
-          enable = mkEnableOption "XtreemFS MRC replication plugin";
+          enable = mkEnableOption (lib.mdDoc "XtreemFS MRC replication plugin");
           extraConfig = mkOption {
             type = types.lines;
             example = ''
@@ -379,7 +379,7 @@ in
         uuid = mkOption {
           example = "eacb6bab-f444-4ebf-a06a-3f72d7465e42";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Must be set to a unique identifier, preferably a UUID according to
             RFC 4122. UUIDs can be generated with `uuidgen` command, found in
             the `util-linux` package.
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix b/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
index 94f806a61789..1078df0bed25 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
@@ -23,9 +23,9 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "
+        description = lib.mdDoc ''
           Whether to enable Yandex-disk client. See https://disk.yandex.ru/
-        ";
+        '';
       };
 
       username = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/3proxy.nix b/nixpkgs/nixos/modules/services/networking/3proxy.nix
index 9fc1dac7c280..ef695a7f49fa 100644
--- a/nixpkgs/nixos/modules/services/networking/3proxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/3proxy.nix
@@ -6,7 +6,7 @@ let
   optionalList = list: if list == [ ] then "*" else concatMapStringsSep "," toString list;
 in {
   options.services._3proxy = {
-    enable = mkEnableOption "3proxy";
+    enable = mkEnableOption (lib.mdDoc "3proxy");
     confFile = mkOption {
       type = types.path;
       example = "/var/lib/3proxy/3proxy.conf";
@@ -18,26 +18,26 @@ in {
       type = types.nullOr types.path;
       default = null;
       example = "/var/lib/3proxy/3proxy.passwd";
-      description = ''
+      description = lib.mdDoc ''
         Load users and passwords from this file.
 
         Example users file with plain-text passwords:
 
-        <literal>
+        ```
           test1:CL:password1
           test2:CL:password2
-        </literal>
+        ```
 
         Example users file with md5-crypted passwords:
 
-        <literal>
+        ```
           test1:CR:$1$tFkisVd2$1GA8JXkRmTXdLDytM/i3a1
           test2:CR:$1$rkpibm5J$Aq1.9VtYAn0JrqZ8M.1ME.
-        </literal>
+        ```
 
         You can generate md5-crypted passwords via https://unix4lyfe.org/crypt/
         Note that htpasswd tool generates incompatible md5-crypted passwords.
-        Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/How-To-(incomplete)#USERS">documentation</link> for more information.
+        Consult [documentation](https://github.com/z3APA3A/3proxy/wiki/How-To-%28incomplete%29#USERS) for more information.
       '';
     };
     services = mkOption {
@@ -55,35 +55,17 @@ in {
               "udppm"
             ];
             example = "proxy";
-            description = ''
+            description = lib.mdDoc ''
               Service type. The following values are valid:
 
-              <itemizedlist>
-                <listitem><para>
-                  <literal>"proxy"</literal>: HTTP/HTTPS proxy (default port 3128).
-                </para></listitem>
-                <listitem><para>
-                  <literal>"socks"</literal>: SOCKS 4/4.5/5 proxy (default port 1080).
-                </para></listitem>
-                <listitem><para>
-                  <literal>"pop3p"</literal>: POP3 proxy (default port 110).
-                </para></listitem>
-                <listitem><para>
-                  <literal>"ftppr"</literal>: FTP proxy (default port 21).
-                </para></listitem>
-                <listitem><para>
-                  <literal>"admin"</literal>: Web interface (default port 80).
-                </para></listitem>
-                <listitem><para>
-                  <literal>"dnspr"</literal>: Caching DNS proxy (default port 53).
-                </para></listitem>
-                <listitem><para>
-                  <literal>"tcppm"</literal>: TCP portmapper.
-                </para></listitem>
-                <listitem><para>
-                  <literal>"udppm"</literal>: UDP portmapper.
-                </para></listitem>
-              </itemizedlist>
+              - `"proxy"`: HTTP/HTTPS proxy (default port 3128).
+              - `"socks"`: SOCKS 4/4.5/5 proxy (default port 1080).
+              - `"pop3p"`: POP3 proxy (default port 110).
+              - `"ftppr"`: FTP proxy (default port 21).
+              - `"admin"`: Web interface (default port 80).
+              - `"dnspr"`: Caching DNS proxy (default port 53).
+              - `"tcppm"`: TCP portmapper.
+              - `"udppm"`: UDP portmapper.
             '';
           };
           bindAddress = mkOption {
@@ -113,24 +95,16 @@ in {
           auth = mkOption {
             type = types.listOf (types.enum [ "none" "iponly" "strong" ]);
             example = [ "iponly" "strong" ];
-            description = ''
+            description = lib.mdDoc ''
               Authentication type. The following values are valid:
 
-              <itemizedlist>
-                <listitem><para>
-                  <literal>"none"</literal>: disables both authentication and authorization. You can not use ACLs.
-                </para></listitem>
-                <listitem><para>
-                  <literal>"iponly"</literal>: specifies no authentication. ACLs authorization is used.
-                </para></listitem>
-                <listitem><para>
-                  <literal>"strong"</literal>: authentication by username/password. If user is not registered their access is denied regardless of ACLs.
-                </para></listitem>
-              </itemizedlist>
+              - `"none"`: disables both authentication and authorization. You can not use ACLs.
+              - `"iponly"`: specifies no authentication. ACLs authorization is used.
+              - `"strong"`: authentication by username/password. If user is not registered their access is denied regardless of ACLs.
 
               Double authentication is possible, e.g.
 
-              <literal>
+              ```
                 {
                   auth = [ "iponly" "strong" ];
                   acl = [
@@ -144,7 +118,7 @@ in {
                     }
                   ];
                 }
-              </literal>
+              ```
               In this example strong username authentication is not required to access 192.168.0.0/16.
             '';
           };
@@ -154,17 +128,11 @@ in {
                 rule = mkOption {
                   type = types.enum [ "allow" "deny" ];
                   example = "allow";
-                  description = ''
+                  description = lib.mdDoc ''
                     ACL rule. The following values are valid:
 
-                    <itemizedlist>
-                      <listitem><para>
-                        <literal>"allow"</literal>: connections allowed.
-                      </para></listitem>
-                      <listitem><para>
-                        <literal>"deny"</literal>: connections not allowed.
-                      </para></listitem>
-                    </itemizedlist>
+                    - `"allow"`: connections allowed.
+                    - `"deny"`: connections not allowed.
                   '';
                 };
                 users = mkOption {
@@ -187,10 +155,10 @@ in {
                   type = types.listOf types.str;
                   default = [ ];
                   example = [ "127.0.0.1" "192.168.1.0/24" ];
-                  description = ''
+                  description = lib.mdDoc ''
                     List of target IP ranges, use empty list for any.
                     May also contain host names instead of addresses.
-                    It's possible to use wildmask in the begginning and in the the end of hostname, e.g. *badsite.com or *badcontent*.
+                    It's possible to use wildmask in the beginning and in the the end of hostname, e.g. `*badsite.com` or `*badcontent*`.
                     Hostname is only checked if hostname presents in request.
                   '';
                 };
diff --git a/nixpkgs/nixos/modules/services/networking/acme-dns.nix b/nixpkgs/nixos/modules/services/networking/acme-dns.nix
new file mode 100644
index 000000000000..5c53fa2cc4f1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/acme-dns.nix
@@ -0,0 +1,154 @@
+{ lib
+, config
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.acme-dns;
+  format = pkgs.formats.toml { };
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkEnableOption
+    mkOption
+    mkPackageOptionMD
+    types
+    ;
+  domain = "acme-dns.example.com";
+in
+{
+  options.services.acme-dns = {
+    enable = mkEnableOption (mdDoc "acme-dns");
+
+    package = mkPackageOptionMD pkgs "acme-dns" { };
+
+    settings = mkOption {
+      description = mdDoc ''
+        Free-form settings written directly to the `acme-dns.cfg` file.
+        Refer to <https://github.com/joohoi/acme-dns/blob/master/README.md#configuration> for supported values.
+      '';
+
+      default = { };
+
+      type = types.submodule {
+        freeformType = format.type;
+        options = {
+          general = {
+            listen = mkOption {
+              type = types.str;
+              description = mdDoc "IP+port combination to bind and serve the DNS server on.";
+              default = "[::]:53";
+              example = "127.0.0.1:53";
+            };
+
+            protocol = mkOption {
+              type = types.enum [ "both" "both4" "both6" "udp" "udp4" "udp6" "tcp" "tcp4" "tcp6" ];
+              description = mdDoc "Protocols to serve DNS responses on.";
+              default = "both";
+            };
+
+            domain = mkOption {
+              type = types.str;
+              description = mdDoc "Domain name to serve the requests off of.";
+              example = domain;
+            };
+
+            nsname = mkOption {
+              type = types.str;
+              description = mdDoc "Zone name server.";
+              example = domain;
+            };
+
+            nsadmin = mkOption {
+              type = types.str;
+              description = mdDoc "Zone admin email address for `SOA`.";
+              example = "admin.example.com";
+            };
+
+            records = mkOption {
+              type = types.listOf types.str;
+              description = mdDoc "Predefined DNS records served in addition to the `_acme-challenge` TXT records.";
+              example = literalExpression ''
+                [
+                  # replace with your acme-dns server's public IPv4
+                  "${domain}. A 198.51.100.1"
+                  # replace with your acme-dns server's public IPv6
+                  "${domain}. AAAA 2001:db8::1"
+                  # ${domain} should resolve any *.${domain} records
+                  "${domain}. NS ${domain}."
+                ]
+              '';
+            };
+          };
+
+          database = {
+            engine = mkOption {
+              type = types.enum [ "sqlite3" "postgres" ];
+              description = mdDoc "Database engine to use.";
+              default = "sqlite3";
+            };
+            connection = mkOption {
+              type = types.str;
+              description = mdDoc "Database connection string.";
+              example = "postgres://user:password@localhost/acmedns";
+              default = "/var/lib/acme-dns/acme-dns.db";
+            };
+          };
+
+          api = {
+            ip = mkOption {
+              type = types.str;
+              description = mdDoc "IP to bind the HTTP API on.";
+              default = "[::]";
+              example = "127.0.0.1";
+            };
+
+            port = mkOption {
+              type = types.port;
+              description = mdDoc "Listen port for the HTTP API.";
+              default = 8080;
+              # acme-dns expects this value to be a string
+              apply = toString;
+            };
+
+            disable_registration = mkOption {
+              type = types.bool;
+              description = mdDoc "Whether to disable the HTTP registration endpoint.";
+              default = false;
+              example = true;
+            };
+
+            tls = mkOption {
+              type = types.enum [ "letsencrypt" "letsencryptstaging" "cert" "none" ];
+              description = mdDoc "TLS backend to use.";
+              default = "none";
+            };
+          };
+
+
+          logconfig = {
+            loglevel = mkOption {
+              type = types.enum [ "error" "warning" "info" "debug" ];
+              description = mdDoc "Level to log on.";
+              default = "info";
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ cfg.package ];
+    systemd.services.acme-dns = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = [ "" "${lib.getExe cfg.package} -c ${format.generate "acme-dns.toml" cfg.settings}" ];
+        StateDirectory = "acme-dns";
+        WorkingDirectory = "%S/acme-dns";
+        DynamicUser = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/adguardhome.nix b/nixpkgs/nixos/modules/services/networking/adguardhome.nix
index 13ef78c10c53..bda99cb7942b 100644
--- a/nixpkgs/nixos/modules/services/networking/adguardhome.nix
+++ b/nixpkgs/nixos/modules/services/networking/adguardhome.nix
@@ -12,36 +12,25 @@ let
     "--config /var/lib/AdGuardHome/AdGuardHome.yaml"
   ] ++ cfg.extraArgs);
 
-  baseConfig = {
-    bind_host = cfg.host;
-    bind_port = cfg.port;
-  };
-
   configFile = pkgs.writeTextFile {
     name = "AdGuardHome.yaml";
-    text = builtins.toJSON (recursiveUpdate cfg.settings baseConfig);
+    text = builtins.toJSON cfg.settings;
     checkPhase = "${pkgs.adguardhome}/bin/adguardhome -c $out --check-config";
   };
 
-in {
-  options.services.adguardhome = with types; {
-    enable = mkEnableOption "AdGuard Home network-wide ad blocker";
+in
+{
 
-    host = mkOption {
-      default = "0.0.0.0";
-      type = str;
-      description = lib.mdDoc ''
-        Host address to bind HTTP server to.
-      '';
-    };
+  imports =
+    let cfgPath = [ "services" "adguardhome" ];
+    in
+    [
+      (mkRenamedOptionModuleWith { sinceRelease = 2211; from = cfgPath ++ [ "host" ]; to = cfgPath ++ [ "settings" "bind_host" ]; })
+      (mkRenamedOptionModuleWith { sinceRelease = 2211; from = cfgPath ++ [ "port" ]; to = cfgPath ++ [ "settings" "bind_port" ]; })
+    ];
 
-    port = mkOption {
-      default = 3000;
-      type = port;
-      description = lib.mdDoc ''
-        Port to serve HTTP pages on.
-      '';
-    };
+  options.services.adguardhome = with types; {
+    enable = mkEnableOption (lib.mdDoc "AdGuard Home network-wide ad blocker");
 
     openFirewall = mkOption {
       default = false;
@@ -62,18 +51,49 @@ in {
     };
 
     settings = mkOption {
-      type = (pkgs.formats.yaml { }).type;
-      default = { };
-      description = ''
+      default = null;
+      type = nullOr (submodule {
+        freeformType = (pkgs.formats.yaml { }).type;
+        options = {
+          schema_version = mkOption {
+            default = pkgs.adguardhome.schema_version;
+            defaultText = literalExpression "pkgs.adguardhome.schema_version";
+            type = int;
+            description = lib.mdDoc ''
+              Schema version for the configuration.
+              Defaults to the `schema_version` supplied by `pkgs.adguardhome`.
+            '';
+          };
+          bind_host = mkOption {
+            default = "0.0.0.0";
+            type = str;
+            description = lib.mdDoc ''
+              Host address to bind HTTP server to.
+            '';
+          };
+          bind_port = mkOption {
+            default = 3000;
+            type = port;
+            description = lib.mdDoc ''
+              Port to serve HTTP pages on.
+            '';
+          };
+        };
+      });
+      description = lib.mdDoc ''
         AdGuard Home configuration. Refer to
-        <link xlink:href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#configuration-file"/>
+        <https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#configuration-file>
         for details on supported values.
 
-        <note><para>
-          On start and if <option>mutableSettings</option> is <literal>true</literal>,
-          these options are merged into the configuration file on start, taking
-          precedence over configuration changes made on the web interface.
-        </para></note>
+        ::: {.note}
+        On start and if {option}`mutableSettings` is `true`,
+        these options are merged into the configuration file on start, taking
+        precedence over configuration changes made on the web interface.
+
+        Set this to `null` (default) for a non-declarative configuration without any
+        Nix-supplied values.
+        Declarative configurations are supplied with a default `schema_version`, `bind_host`, and `bind_port`.
+        :::
       '';
     };
 
@@ -89,15 +109,15 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.settings != { }
-          -> (hasAttrByPath [ "dns" "bind_host" ] cfg.settings)
+        assertion = cfg.settings != null -> cfg.mutableSettings
+          || (hasAttrByPath [ "dns" "bind_host" ] cfg.settings)
           || (hasAttrByPath [ "dns" "bind_hosts" ] cfg.settings);
         message =
           "AdGuard setting dns.bind_host or dns.bind_hosts needs to be configured for a minimal working configuration";
       }
       {
-        assertion = cfg.settings != { }
-          -> hasAttrByPath [ "dns" "bootstrap_dns" ] cfg.settings;
+        assertion = cfg.settings != null -> cfg.mutableSettings
+          || hasAttrByPath [ "dns" "bootstrap_dns" ] cfg.settings;
         message =
           "AdGuard setting dns.bootstrap_dns needs to be configured for a minimal working configuration";
       }
@@ -112,7 +132,7 @@ in {
         StartLimitBurst = 10;
       };
 
-      preStart = optionalString (cfg.settings != { }) ''
+      preStart = optionalString (cfg.settings != null) ''
         if    [ -e "$STATE_DIRECTORY/AdGuardHome.yaml" ] \
            && [ "${toString cfg.mutableSettings}" = "1" ]; then
           # Writing directly to AdGuardHome.yaml results in empty file
@@ -135,6 +155,6 @@ in {
       };
     };
 
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.bind_port ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/alice-lg.nix b/nixpkgs/nixos/modules/services/networking/alice-lg.nix
new file mode 100644
index 000000000000..06b9ac89f12f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/alice-lg.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.alice-lg;
+  settingsFormat = pkgs.formats.ini { };
+in
+{
+  options = {
+    services.alice-lg = {
+      enable = mkEnableOption (lib.mdDoc "Alice Looking Glass");
+
+      package = mkPackageOptionMD pkgs "alice-lg" { };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        description = lib.mdDoc ''
+          alice-lg configuration, for configuration options see the example on [github](https://github.com/alice-lg/alice-lg/blob/main/etc/alice-lg/alice.example.conf)
+        '';
+        example = literalExpression ''
+          {
+            server = {
+              # configures the built-in webserver and provides global application settings
+              listen_http = "127.0.0.1:7340";
+              enable_prefix_lookup = true;
+              asn = 9033;
+              store_backend = postgres;
+              routes_store_refresh_parallelism = 5;
+              neighbors_store_refresh_parallelism = 10000;
+              routes_store_refresh_interval = 5;
+              neighbors_store_refresh_interval = 5;
+            };
+            postgres = {
+              url = "postgres://postgres:postgres@localhost:5432/alice";
+              min_connections = 2;
+              max_connections = 128;
+            };
+            pagination = {
+              routes_filtered_page_size = 250;
+              routes_accepted_page_size = 250;
+              routes_not_exported_page_size = 250;
+            };
+          }
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      etc."alice-lg/alice.conf".source = settingsFormat.generate "alice-lg.conf" cfg.settings;
+    };
+    systemd.services = {
+      alice-lg = {
+        wants = [ "network.target" ];
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        description = "Alice Looking Glass";
+        serviceConfig = {
+          DynamicUser = true;
+          Type = "simple";
+          Restart = "on-failure";
+          RestartSec = 15;
+          ExecStart = "${cfg.package}/bin/alice-lg";
+          StateDirectoryMode = "0700";
+          UMask = "0007";
+          CapabilityBoundingSet = "";
+          NoNewPrivileges = true;
+          ProtectSystem = "strict";
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+          BindReadOnlyPaths = [
+            "-/etc/resolv.conf"
+            "-/etc/nsswitch.conf"
+            "-/etc/ssl/certs"
+            "-/etc/static/ssl/certs"
+            "-/etc/hosts"
+            "-/etc/localtime"
+          ];
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/antennas.nix b/nixpkgs/nixos/modules/services/networking/antennas.nix
index e3bde2b67d26..c0e56890864a 100644
--- a/nixpkgs/nixos/modules/services/networking/antennas.nix
+++ b/nixpkgs/nixos/modules/services/networking/antennas.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.antennas = {
-      enable = mkEnableOption "Antennas";
+      enable = mkEnableOption (lib.mdDoc "Antennas");
 
       tvheadendUrl = mkOption {
         type        = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix b/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
index 56113bd34594..3a7519c7230b 100644
--- a/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.avahi;
 
-  yesNo = yes : if yes then "yes" else "no";
+  yesNo = yes: if yes then "yes" else "no";
 
   avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
     [server]
@@ -17,7 +17,8 @@ let
     browse-domains=${concatStringsSep ", " browseDomains}
     use-ipv4=${yesNo ipv4}
     use-ipv6=${yesNo ipv6}
-    ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
+    ${optionalString (allowInterfaces!=null) "allow-interfaces=${concatStringsSep "," allowInterfaces}"}
+    ${optionalString (denyInterfaces!=null) "deny-interfaces=${concatStringsSep "," denyInterfaces}"}
     ${optionalString (domainName!=null) "domain-name=${domainName}"}
     allow-point-to-point=${yesNo allowPointToPoint}
     ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
@@ -39,6 +40,10 @@ let
   '';
 in
 {
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ])
+  ];
+
   options.services.avahi = {
     enable = mkOption {
       type = types.bool;
@@ -47,7 +52,7 @@ in
         Whether to run the Avahi daemon, which allows Avahi clients
         to use Avahi's service discovery facilities and also allows
         the local machine to advertise its presence and services
-        (through the mDNS responder implemented by `avahi-daemon').
+        (through the mDNS responder implemented by `avahi-daemon`).
       '';
     };
 
@@ -91,7 +96,7 @@ in
       description = lib.mdDoc "Whether to use IPv6.";
     };
 
-    interfaces = mkOption {
+    allowInterfaces = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
       description = lib.mdDoc ''
@@ -101,18 +106,30 @@ in
       '';
     };
 
+    denyInterfaces = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = lib.mdDoc ''
+        List of network interfaces that should be ignored by the
+        {command}`avahi-daemon`. Other unspecified interfaces will be used,
+        unless {option}`allowInterfaces` is set. This option takes precedence
+        over {option}`allowInterfaces`.
+      '';
+    };
+
     openFirewall = mkOption {
       type = types.bool;
       default = true;
       description = lib.mdDoc ''
         Whether to open the firewall for UDP port 5353.
+        Disabling this setting also disables discovering of network devices.
       '';
     };
 
     allowPointToPoint = mkOption {
       type = types.bool;
       default = false;
-      description= lib.mdDoc ''
+      description = lib.mdDoc ''
         Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
         latencies with such links and opens a potential security hole by allowing mDNS access from Internet
         connections.
@@ -133,7 +150,7 @@ in
 
     extraServiceFiles = mkOption {
       type = with types; attrsOf (either str path);
-      default = {};
+      default = { };
       example = literalExpression ''
         {
           ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
@@ -204,7 +221,7 @@ in
       default = false;
       description = lib.mdDoc ''
         Whether to enable the mDNS NSS (Name Service Switch) plug-in.
-        Enabling it allows applications to resolve names in the `.local'
+        Enabling it allows applications to resolve names in the `.local`
         domain by transparently querying the Avahi daemon.
       '';
     };
@@ -235,7 +252,7 @@ in
       isSystemUser = true;
     };
 
-    users.groups.avahi = {};
+    users.groups.avahi = { };
 
     system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
     system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
@@ -245,10 +262,12 @@ in
 
     environment.systemPackages = [ pkgs.avahi ];
 
-    environment.etc = (mapAttrs' (n: v: nameValuePair
-      "avahi/services/${n}.service"
-      { ${if types.path.check v then "source" else "text"} = v; }
-    ) cfg.extraServiceFiles);
+    environment.etc = (mapAttrs'
+      (n: v: nameValuePair
+        "avahi/services/${n}.service"
+        { ${if types.path.check v then "source" else "text"} = v; }
+      )
+      cfg.extraServiceFiles);
 
     systemd.sockets.avahi-daemon = {
       description = "Avahi mDNS/DNS-SD Stack Activation Socket";
@@ -274,6 +293,7 @@ in
         BusName = "org.freedesktop.Avahi";
         Type = "dbus";
         ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
+        ConfigurationDirectory = "avahi/services";
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/babeld.nix b/nixpkgs/nixos/modules/services/networking/babeld.nix
index b393b6e05920..ff1ac6998ee9 100644
--- a/nixpkgs/nixos/modules/services/networking/babeld.nix
+++ b/nixpkgs/nixos/modules/services/networking/babeld.nix
@@ -40,7 +40,7 @@ in
 
     services.babeld = {
 
-      enable = mkEnableOption "the babeld network routing daemon";
+      enable = mkEnableOption (lib.mdDoc "the babeld network routing daemon");
 
       interfaceDefaults = mkOption {
         default = null;
diff --git a/nixpkgs/nixos/modules/services/networking/bee-clef.nix b/nixpkgs/nixos/modules/services/networking/bee-clef.nix
index 852e1396b915..75e76f019a71 100644
--- a/nixpkgs/nixos/modules/services/networking/bee-clef.nix
+++ b/nixpkgs/nixos/modules/services/networking/bee-clef.nix
@@ -14,7 +14,7 @@ in {
 
   options = {
     services.bee-clef = {
-      enable = mkEnableOption "clef external signer instance for Ethereum Swarm Bee";
+      enable = mkEnableOption (lib.mdDoc "clef external signer instance for Ethereum Swarm Bee");
 
       dataDir = mkOption {
         type = types.nullOr types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/bee.nix b/nixpkgs/nixos/modules/services/networking/bee.nix
index a99513cb8cc7..add9861ebfcd 100644
--- a/nixpkgs/nixos/modules/services/networking/bee.nix
+++ b/nixpkgs/nixos/modules/services/networking/bee.nix
@@ -15,7 +15,7 @@ in {
 
   options = {
     services.bee = {
-      enable = mkEnableOption "Ethereum Swarm Bee";
+      enable = mkEnableOption (lib.mdDoc "Ethereum Swarm Bee");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/biboumi.nix b/nixpkgs/nixos/modules/services/networking/biboumi.nix
index 24e0c0328fe6..1428856764e6 100644
--- a/nixpkgs/nixos/modules/services/networking/biboumi.nix
+++ b/nixpkgs/nixos/modules/services/networking/biboumi.nix
@@ -16,7 +16,7 @@ in
 {
   options = {
     services.biboumi = {
-      enable = mkEnableOption "the Biboumi XMPP gateway to IRC";
+      enable = mkEnableOption (lib.mdDoc "the Biboumi XMPP gateway to IRC");
 
       settings = mkOption {
         description = lib.mdDoc ''
@@ -45,7 +45,7 @@ in
             default = "/etc/ssl/certs/ca-certificates.crt";
             description = lib.mdDoc ''
               Specifies which file should be used as the list of trusted CA
-              when negociating a TLS session.
+              when negotiating a TLS session.
             '';
           };
           options.db_name = mkOption {
@@ -111,7 +111,7 @@ in
             description = lib.mdDoc ''
               A directory that should contain the policy files,
               used to customize Botan’s behaviour
-              when negociating the TLS connections with the IRC servers.
+              when negotiating the TLS connections with the IRC servers.
             '';
           };
           options.port = mkOption {
@@ -166,7 +166,7 @@ in
         example = "/run/keys/biboumi.cfg";
       };
 
-      openFirewall = mkEnableOption "opening of the identd port in the firewall";
+      openFirewall = mkEnableOption (lib.mdDoc "opening of the identd port in the firewall");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/networking/bind.nix b/nixpkgs/nixos/modules/services/networking/bind.nix
index 0966332f7d0d..f1829747bb1e 100644
--- a/nixpkgs/nixos/modules/services/networking/bind.nix
+++ b/nixpkgs/nixos/modules/services/networking/bind.nix
@@ -36,6 +36,17 @@ let
         description = lib.mdDoc "Addresses who may request zone transfers.";
         default = [ ];
       };
+      allowQuery = mkOption {
+        type = types.listOf types.str;
+        description = lib.mdDoc ''
+          List of address ranges allowed to query this zone. Instead of the address(es), this may instead
+          contain the single string "any".
+
+          NOTE: This overrides the global-level `allow-query` setting, which is set to the contents
+          of `cachenetworks`.
+        '';
+        default = [ "any" ];
+      };
       extraConfig = mkOption {
         type = types.str;
         description = lib.mdDoc "Extra zone config to be appended at the end of the zone section.";
@@ -69,7 +80,7 @@ let
       ${cfg.extraConfig}
 
       ${ concatMapStrings
-          ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
+          ({ name, file, master ? true, slaves ? [], masters ? [], allowQuery ? [], extraConfig ? "" }:
             ''
               zone "${name}" {
                 type ${if master then "master" else "slave"};
@@ -87,7 +98,7 @@ let
                      };
                    ''
                 }
-                allow-query { any; };
+                allow-query { ${concatMapStrings (ip: "${ip}; ") allowQuery}};
                 ${extraConfig}
               };
             '')
@@ -104,7 +115,7 @@ in
 
     services.bind = {
 
-      enable = mkEnableOption "BIND domain name server";
+      enable = mkEnableOption (lib.mdDoc "BIND domain name server");
 
 
       package = mkOption {
@@ -117,62 +128,64 @@ in
       cacheNetworks = mkOption {
         default = [ "127.0.0.0/24" ];
         type = types.listOf types.str;
-        description = "
+        description = lib.mdDoc ''
           What networks are allowed to use us as a resolver.  Note
           that this is for recursive queries -- all networks are
-          allowed to query zones configured with the `zones` option.
+          allowed to query zones configured with the `zones` option
+          by default (although this may be overridden within each
+          zone's configuration, via the `allowQuery` option).
           It is recommended that you limit cacheNetworks to avoid your
           server being used for DNS amplification attacks.
-        ";
+        '';
       };
 
       blockedNetworks = mkOption {
         default = [ ];
         type = types.listOf types.str;
-        description = "
+        description = lib.mdDoc ''
           What networks are just blocked.
-        ";
+        '';
       };
 
       ipv4Only = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Only use ipv4, even if the host supports ipv6.
-        ";
+        '';
       };
 
       forwarders = mkOption {
         default = config.networking.nameservers;
         defaultText = literalExpression "config.networking.nameservers";
         type = types.listOf types.str;
-        description = "
+        description = lib.mdDoc ''
           List of servers we should forward requests to.
-        ";
+        '';
       };
 
       forward = mkOption {
         default = "first";
         type = types.enum ["first" "only"];
-        description = "
+        description = lib.mdDoc ''
           Whether to forward 'first' (try forwarding but lookup directly if forwarding fails) or 'only'.
-        ";
+        '';
       };
 
       listenOn = mkOption {
         default = [ "any" ];
         type = types.listOf types.str;
-        description = "
+        description = lib.mdDoc ''
           Interfaces to listen on.
-        ";
+        '';
       };
 
       listenOnIpv6 = mkOption {
         default = [ "any" ];
         type = types.listOf types.str;
-        description = "
+        description = lib.mdDoc ''
           Ipv6 interfaces to listen on.
-        ";
+        '';
       };
 
       directory = mkOption {
@@ -184,9 +197,9 @@ in
       zones = mkOption {
         default = [ ];
         type = with types; coercedTo (listOf attrs) bindZoneCoerce (attrsOf (types.submodule bindZoneOptions));
-        description = "
+        description = lib.mdDoc ''
           List of zones we claim authority over.
-        ";
+        '';
         example = {
           "example.com" = {
             master = false;
@@ -201,9 +214,9 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the generated named configuration file.
-        ";
+        '';
       };
 
       extraOptions = mkOption {
@@ -219,10 +232,10 @@ in
         type = types.path;
         default = confFile;
         defaultText = literalExpression "confFile";
-        description = "
+        description = lib.mdDoc ''
           Overridable config file to use for named. By default, that
           generated by nixos.
-        ";
+        '';
       };
 
     };
diff --git a/nixpkgs/nixos/modules/services/networking/bird-lg.nix b/nixpkgs/nixos/modules/services/networking/bird-lg.nix
index 1440deb62b44..dc861dbfd11b 100644
--- a/nixpkgs/nixos/modules/services/networking/bird-lg.nix
+++ b/nixpkgs/nixos/modules/services/networking/bird-lg.nix
@@ -4,6 +4,49 @@ with lib;
 
 let
   cfg = config.services.bird-lg;
+
+  stringOrConcat = sep: v: if builtins.isString v then v else concatStringsSep sep v;
+
+  frontend_args = let
+    fe = cfg.frontend;
+  in {
+    "--servers" = concatStringsSep "," fe.servers;
+    "--domain" = fe.domain;
+    "--listen" = fe.listenAddress;
+    "--proxy-port" = fe.proxyPort;
+    "--whois" = fe.whois;
+    "--dns-interface" = fe.dnsInterface;
+    "--bgpmap-info" = concatStringsSep "," cfg.frontend.bgpMapInfo;
+    "--title-brand" = fe.titleBrand;
+    "--navbar-brand" = fe.navbar.brand;
+    "--navbar-brand-url" = fe.navbar.brandURL;
+    "--navbar-all-servers" = fe.navbar.allServers;
+    "--navbar-all-url" = fe.navbar.allServersURL;
+    "--net-specific-mode" = fe.netSpecificMode;
+    "--protocol-filter" = concatStringsSep "," cfg.frontend.protocolFilter;
+  };
+
+  proxy_args = let
+    px = cfg.proxy;
+  in {
+    "--allowed" = concatStringsSep "," px.allowedIPs;
+    "--bird" = px.birdSocket;
+    "--listen" = px.listenAddress;
+    "--traceroute_bin" = px.traceroute.binary;
+    "--traceroute_flags" = concatStringsSep " " px.traceroute.flags;
+    "--traceroute_raw" = px.traceroute.rawOutput;
+  };
+
+  mkArgValue = value:
+    if isString value
+      then escapeShellArg value
+      else if isBool value
+        then boolToString value
+        else toString value;
+
+  filterNull = filterAttrs (_: v: v != "" && v != null && v != []);
+
+  argsAttrToList = args: mapAttrsToList (name: value: "${name} " + mkArgValue value ) (filterNull args);
 in
 {
   options = {
@@ -28,7 +71,7 @@ in
       };
 
       frontend = {
-        enable = mkEnableOption "Bird Looking Glass Frontend Webserver";
+        enable = mkEnableOption (lib.mdDoc "Bird Looking Glass Frontend Webserver");
 
         listenAddress = mkOption {
           type = types.str;
@@ -44,14 +87,12 @@ in
 
         domain = mkOption {
           type = types.str;
-          default = "";
           example = "dn42.lantian.pub";
           description = lib.mdDoc "Server name domain suffixes.";
         };
 
         servers = mkOption {
           type = types.listOf types.str;
-          default = [ ];
           example = [ "gigsgigscloud" "hostdare" ];
           description = lib.mdDoc "Server name prefixes.";
         };
@@ -134,16 +175,20 @@ in
         };
 
         extraArgs = mkOption {
-          type = types.lines;
-          default = "";
+          type = with types; either lines (listOf str);
+          default = [ ];
           description = lib.mdDoc ''
             Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#frontend).
+
+            :::{.note}
+            Passing lines (plain strings) is deprecated in favour of passing lists of strings.
+            :::
           '';
         };
       };
 
       proxy = {
-        enable = mkEnableOption "Bird Looking Glass Proxy";
+        enable = mkEnableOption (lib.mdDoc "Bird Looking Glass Proxy");
 
         listenAddress = mkOption {
           type = types.str;
@@ -160,8 +205,7 @@ in
 
         birdSocket = mkOption {
           type = types.str;
-          default = "/run/bird.ctl";
-          example = "/var/run/bird/bird.ctl";
+          default = "/var/run/bird/bird.ctl";
           description = lib.mdDoc "Bird control socket path.";
         };
 
@@ -173,6 +217,12 @@ in
             description = lib.mdDoc "Traceroute's binary path.";
           };
 
+          flags = mkOption {
+            type = with types; listOf str;
+            default = [ ];
+            description = lib.mdDoc "Flags for traceroute process";
+          };
+
           rawOutput = mkOption {
             type = types.bool;
             default = false;
@@ -181,10 +231,14 @@ in
         };
 
         extraArgs = mkOption {
-          type = types.lines;
-          default = "";
+          type = with types; either lines (listOf str);
+          default = [ ];
           description = lib.mdDoc ''
             Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#proxy).
+
+            :::{.note}
+            Passing lines (plain strings) is deprecated in favour of passing lists of strings.
+            :::
           '';
         };
       };
@@ -194,6 +248,16 @@ in
   ###### implementation
 
   config = {
+
+    warnings =
+      lib.optional (cfg.frontend.enable  && builtins.isString cfg.frontend.extraArgs) ''
+        Passing strings to `services.bird-lg.frontend.extraOptions' is deprecated. Please pass a list of strings instead.
+      ''
+      ++ lib.optional (cfg.proxy.enable  && builtins.isString cfg.proxy.extraArgs) ''
+        Passing strings to `services.bird-lg.proxy.extraOptions' is deprecated. Please pass a list of strings instead.
+      ''
+    ;
+
     systemd.services = {
       bird-lg-frontend = mkIf cfg.frontend.enable {
         enable = true;
@@ -211,23 +275,8 @@ in
         };
         script = ''
           ${cfg.package}/bin/frontend \
-            --servers ${concatStringsSep "," cfg.frontend.servers } \
-            --domain ${cfg.frontend.domain} \
-            --listen ${cfg.frontend.listenAddress} \
-            --proxy-port ${toString cfg.frontend.proxyPort} \
-            --whois ${cfg.frontend.whois} \
-            --dns-interface ${cfg.frontend.dnsInterface} \
-            --bgpmap-info ${concatStringsSep "," cfg.frontend.bgpMapInfo } \
-            --title-brand ${cfg.frontend.titleBrand} \
-            --navbar-brand ${cfg.frontend.navbar.brand} \
-            --navbar-brand-url ${cfg.frontend.navbar.brandURL} \
-            --navbar-all-servers ${cfg.frontend.navbar.allServers} \
-            --navbar-all-url ${cfg.frontend.navbar.allServersURL} \
-            --net-specific-mode ${cfg.frontend.netSpecificMode} \
-            --protocol-filter ${concatStringsSep "," cfg.frontend.protocolFilter } \
-            --name-filter ${cfg.frontend.nameFilter} \
-            --time-out ${toString cfg.frontend.timeout} \
-            ${cfg.frontend.extraArgs}
+            ${concatStringsSep " \\\n  " (argsAttrToList frontend_args)} \
+            ${stringOrConcat " " cfg.frontend.extraArgs}
         '';
       };
 
@@ -247,12 +296,8 @@ in
         };
         script = ''
           ${cfg.package}/bin/proxy \
-          --allowed ${concatStringsSep "," cfg.proxy.allowedIPs } \
-          --bird ${cfg.proxy.birdSocket} \
-          --listen ${cfg.proxy.listenAddress} \
-          --traceroute_bin ${cfg.proxy.traceroute.binary}
-          --traceroute_raw ${boolToString cfg.proxy.traceroute.rawOutput}
-          ${cfg.proxy.extraArgs}
+            ${concatStringsSep " \\\n  " (argsAttrToList proxy_args)} \
+            ${stringOrConcat " " cfg.proxy.extraArgs}
         '';
       };
     };
@@ -266,4 +311,9 @@ in
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [
+    e1mo
+    tchekda
+  ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/bird.nix b/nixpkgs/nixos/modules/services/networking/bird.nix
index 7708aaa476f6..77e0b3f8af9b 100644
--- a/nixpkgs/nixos/modules/services/networking/bird.nix
+++ b/nixpkgs/nixos/modules/services/networking/bird.nix
@@ -10,7 +10,7 @@ in
   ###### interface
   options = {
     services.bird2 = {
-      enable = mkEnableOption "BIRD Internet Routing Daemon";
+      enable = mkEnableOption (lib.mdDoc "BIRD Internet Routing Daemon");
       config = mkOption {
         type = types.lines;
         description = lib.mdDoc ''
diff --git a/nixpkgs/nixos/modules/services/networking/birdwatcher.nix b/nixpkgs/nixos/modules/services/networking/birdwatcher.nix
new file mode 100644
index 000000000000..a129b7a2b4cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/birdwatcher.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.birdwatcher;
+in
+{
+  options = {
+    services.birdwatcher = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.birdwatcher;
+        defaultText = literalExpression "pkgs.birdwatcher";
+        description = lib.mdDoc "The Birdwatcher package to use.";
+      };
+      enable = mkEnableOption (lib.mdDoc "Birdwatcher");
+      flags = mkOption {
+        default = [ ];
+        type = types.listOf types.str;
+        example = [ "-worker-pool-size 16" "-6" ];
+        description = lib.mdDoc ''
+          Flags to append to the program call
+        '';
+      };
+
+      settings = mkOption {
+        type = types.lines;
+        default = { };
+        description = lib.mdDoc ''
+          birdwatcher configuration, for configuration options see the example on [github](https://github.com/alice-lg/birdwatcher/blob/master/etc/birdwatcher/birdwatcher.conf)
+        '';
+        example = literalExpression ''
+          [server]
+          allow_from = []
+          allow_uncached = false
+          modules_enabled = ["status",
+                             "protocols",
+                             "protocols_bgp",
+                             "protocols_short",
+                             "routes_protocol",
+                             "routes_peer",
+                             "routes_table",
+                             "routes_table_filtered",
+                             "routes_table_peer",
+                             "routes_filtered",
+                             "routes_prefixed",
+                             "routes_noexport",
+                             "routes_pipe_filtered_count",
+                             "routes_pipe_filtered"
+                            ]
+
+          [status]
+          reconfig_timestamp_source = "bird"
+          reconfig_timestamp_match = "# created: (.*)"
+
+          filter_fields = []
+
+          [bird]
+          listen = "0.0.0.0:29184"
+          config = "/etc/bird/bird2.conf"
+          birdc  = "''${pkgs.bird}/bin/birdc"
+          ttl = 5 # time to live (in minutes) for caching of cli output
+
+          [parser]
+          filter_fields = []
+
+          [cache]
+          use_redis = false # if not using redis cache, activate housekeeping to save memory!
+
+          [housekeeping]
+          interval = 5
+          force_release_memory = true
+        '';
+      };
+    };
+  };
+
+  config =
+    let flagsStr = escapeShellArgs cfg.flags;
+    in lib.mkIf cfg.enable {
+      environment.etc."birdwatcher/birdwatcher.conf".source = pkgs.writeTextFile {
+        name = "birdwatcher.conf";
+        text = cfg.settings;
+      };
+      systemd.services = {
+        birdwatcher = {
+          wants = [ "network.target" ];
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          description = "Birdwatcher";
+          serviceConfig = {
+            Type = "simple";
+            Restart = "on-failure";
+            RestartSec = 15;
+            ExecStart = "${cfg.package}/bin/birdwatcher";
+            StateDirectoryMode = "0700";
+            UMask = "0117";
+            NoNewPrivileges = true;
+            ProtectSystem = "strict";
+            PrivateTmp = true;
+            PrivateDevices = true;
+            ProtectHostname = true;
+            ProtectClock = true;
+            ProtectKernelTunables = true;
+            ProtectKernelModules = true;
+            ProtectKernelLogs = true;
+            ProtectControlGroups = true;
+            RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+            PrivateMounts = true;
+            SystemCallArchitectures = "native";
+            SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+            BindReadOnlyPaths = [
+              "-/etc/resolv.conf"
+              "-/etc/nsswitch.conf"
+              "-/etc/ssl/certs"
+              "-/etc/static/ssl/certs"
+              "-/etc/hosts"
+              "-/etc/localtime"
+            ];
+          };
+        };
+      };
+    };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bitcoind.nix b/nixpkgs/nixos/modules/services/networking/bitcoind.nix
index 1788d5fcf58d..a86d52b7202d 100644
--- a/nixpkgs/nixos/modules/services/networking/bitcoind.nix
+++ b/nixpkgs/nixos/modules/services/networking/bitcoind.nix
@@ -18,12 +18,12 @@ let
       passwordHMAC = mkOption {
         type = types.uniq (types.strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
         example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
-        description = ''
+        description = lib.mdDoc ''
           Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
-          format &lt;SALT-HEX&gt;$&lt;HMAC-HEX&gt;.
+          format \<SALT-HEX\>$\<HMAC-HEX\>.
 
           Tool (Python script) for HMAC generation is available here:
-          <link xlink:href="https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py"/>
+          <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py>
         '';
       };
     };
@@ -35,7 +35,7 @@ let
   bitcoindOpts = { config, lib, name, ...}: {
     options = {
 
-      enable = mkEnableOption "Bitcoin daemon";
+      enable = mkEnableOption (lib.mdDoc "Bitcoin daemon");
 
       package = mkOption {
         type = types.package;
@@ -95,7 +95,7 @@ let
             }
           '';
           type = types.attrsOf (types.submodule rpcUserOpts);
-          description = lib.mdDoc "RPC user information for JSON-RPC connnections.";
+          description = lib.mdDoc "RPC user information for JSON-RPC connections.";
         };
       };
 
@@ -204,7 +204,7 @@ in
         '';
       in {
         description = "Bitcoin daemon";
-        after = [ "network.target" ];
+        after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
           User = cfg.user;
diff --git a/nixpkgs/nixos/modules/services/networking/bitlbee.nix b/nixpkgs/nixos/modules/services/networking/bitlbee.nix
index e2844feda028..146bffaa6edf 100644
--- a/nixpkgs/nixos/modules/services/networking/bitlbee.nix
+++ b/nixpkgs/nixos/modules/services/networking/bitlbee.nix
@@ -59,16 +59,16 @@ in
       interface = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
-          The interface the BitlBee deamon will be listening to.  If `127.0.0.1',
-          only clients on the local host can connect to it; if `0.0.0.0', clients
+        description = lib.mdDoc ''
+          The interface the BitlBee daemon will be listening to.  If `127.0.0.1`,
+          only clients on the local host can connect to it; if `0.0.0.0`, clients
           can access it from any network interface.
         '';
       };
 
       portNumber = mkOption {
         default = 6667;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           Number of the port BitlBee will be listening to.
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/blockbook-frontend.nix b/nixpkgs/nixos/modules/services/networking/blockbook-frontend.nix
index 0164883c7470..46b26195d211 100644
--- a/nixpkgs/nixos/modules/services/networking/blockbook-frontend.nix
+++ b/nixpkgs/nixos/modules/services/networking/blockbook-frontend.nix
@@ -10,7 +10,7 @@ let
 
     options = {
 
-      enable = mkEnableOption "blockbook-frontend application.";
+      enable = mkEnableOption (lib.mdDoc "blockbook-frontend application");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/blocky.nix b/nixpkgs/nixos/modules/services/networking/blocky.nix
index 42eab1459667..30a41fa6a421 100644
--- a/nixpkgs/nixos/modules/services/networking/blocky.nix
+++ b/nixpkgs/nixos/modules/services/networking/blocky.nix
@@ -10,7 +10,7 @@ let
 in
 {
   options.services.blocky = {
-    enable = mkEnableOption "Fast and lightweight DNS proxy as ad-blocker for local network with many features";
+    enable = mkEnableOption (lib.mdDoc "blocky, a fast and lightweight DNS proxy as ad-blocker for local network with many features");
 
     settings = mkOption {
       type = format.type;
@@ -31,6 +31,7 @@ in
       serviceConfig = {
         DynamicUser = true;
         ExecStart = "${pkgs.blocky}/bin/blocky --config ${configFile}";
+        Restart = "on-failure";
 
         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
diff --git a/nixpkgs/nixos/modules/services/networking/cgit.nix b/nixpkgs/nixos/modules/services/networking/cgit.nix
new file mode 100644
index 000000000000..672b0b030eee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/cgit.nix
@@ -0,0 +1,203 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfgs = config.services.cgit;
+
+  settingType = with types; oneOf [ bool int str ];
+
+  genAttrs' = names: f: listToAttrs (map f names);
+
+  regexEscape =
+    let
+      # taken from https://github.com/python/cpython/blob/05cb728d68a278d11466f9a6c8258d914135c96c/Lib/re.py#L251-L266
+      special = [
+        "(" ")" "[" "]" "{" "}" "?" "*" "+" "-" "|" "^" "$" "\\" "." "&" "~"
+        "#" " " "\t" "\n" "\r" "\v" "\f"
+      ];
+    in
+      replaceStrings special (map (c: "\\${c}") special);
+
+  stripLocation = cfg: removeSuffix "/" cfg.nginx.location;
+
+  regexLocation = cfg: regexEscape (stripLocation cfg);
+
+  mkFastcgiPass = cfg: ''
+    ${if cfg.nginx.location == "/" then ''
+      fastcgi_param PATH_INFO $uri;
+    '' else ''
+      fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
+      fastcgi_param PATH_INFO $fastcgi_path_info;
+    ''
+    }fastcgi_pass unix:${config.services.fcgiwrap.socketAddress};
+  '';
+
+  cgitrcLine = name: value: "${name}=${
+    if value == true then
+      "1"
+    else if value == false then
+      "0"
+    else
+      toString value
+  }";
+
+  mkCgitrc = cfg: pkgs.writeText "cgitrc" ''
+    # global settings
+    ${concatStringsSep "\n" (
+        mapAttrsToList
+          cgitrcLine
+          ({ virtual-root = cfg.nginx.location; } // cfg.settings)
+      )
+    }
+    ${optionalString (cfg.scanPath != null) (cgitrcLine "scan-path" cfg.scanPath)}
+
+    # repository settings
+    ${concatStrings (
+        mapAttrsToList
+          (url: settings: ''
+            ${cgitrcLine "repo.url" url}
+            ${concatStringsSep "\n" (
+                mapAttrsToList (name: cgitrcLine "repo.${name}") settings
+              )
+            }
+          '')
+          cfg.repos
+      )
+    }
+
+    # extra config
+    ${cfg.extraConfig}
+  '';
+
+  mkCgitReposDir = cfg:
+    if cfg.scanPath != null then
+      cfg.scanPath
+    else
+      pkgs.runCommand "cgit-repos" {
+        preferLocalBuild = true;
+        allowSubstitutes = false;
+      } ''
+        mkdir -p "$out"
+        ${
+          concatStrings (
+            mapAttrsToList
+              (name: value: ''
+                ln -s ${escapeShellArg value.path} "$out"/${escapeShellArg name}
+              '')
+              cfg.repos
+          )
+        }
+      '';
+
+in
+{
+  options = {
+    services.cgit = mkOption {
+      description = mdDoc "Configure cgit instances.";
+      default = {};
+      type = types.attrsOf (types.submodule ({ config, ... }: {
+        options = {
+          enable = mkEnableOption (mdDoc "cgit");
+
+          package = mkPackageOptionMD pkgs "cgit" {};
+
+          nginx.virtualHost = mkOption {
+            description = mdDoc "VirtualHost to serve cgit on, defaults to the attribute name.";
+            type = types.str;
+            default = config._module.args.name;
+            example = "git.example.com";
+          };
+
+          nginx.location = mkOption {
+            description = mdDoc "Location to serve cgit under.";
+            type = types.str;
+            default = "/";
+            example = "/git/";
+          };
+
+          repos = mkOption {
+            description = mdDoc "cgit repository settings, see cgitrc(5)";
+            type = with types; attrsOf (attrsOf settingType);
+            default = {};
+            example = {
+              blah = {
+                path = "/var/lib/git/example";
+                desc = "An example repository";
+              };
+            };
+          };
+
+          scanPath = mkOption {
+            description = mdDoc "A path which will be scanned for repositories.";
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/lib/git";
+          };
+
+          settings = mkOption {
+            description = mdDoc "cgit configuration, see cgitrc(5)";
+            type = types.attrsOf settingType;
+            default = {};
+            example = literalExpression ''
+              {
+                enable-follow-links = true;
+                source-filter = "''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py";
+              }
+            '';
+          };
+
+          extraConfig = mkOption {
+            description = mdDoc "These lines go to the end of cgitrc verbatim.";
+            type = types.lines;
+            default = "";
+          };
+        };
+      }));
+    };
+  };
+
+  config = mkIf (any (cfg: cfg.enable) (attrValues cfgs)) {
+    assertions = mapAttrsToList (vhost: cfg: {
+      assertion = !cfg.enable || (cfg.scanPath == null) != (cfg.repos == {});
+      message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set.";
+    }) cfgs;
+
+    services.fcgiwrap.enable = true;
+
+    services.nginx.enable = true;
+
+    services.nginx.virtualHosts = mkMerge (mapAttrsToList (_: cfg: {
+      ${cfg.nginx.virtualHost} = {
+        locations = (
+          genAttrs'
+            [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ]
+            (name: nameValuePair "= ${stripLocation cfg}/${name}" {
+              extraConfig = ''
+                alias ${cfg.package}/cgit/${name};
+              '';
+            })
+        ) // {
+          "~ ${regexLocation cfg}/.+/(info/refs|git-upload-pack)" = {
+            fastcgiParams = rec {
+              SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend";
+              GIT_HTTP_EXPORT_ALL = "1";
+              GIT_PROJECT_ROOT = mkCgitReposDir cfg;
+              HOME = GIT_PROJECT_ROOT;
+            };
+            extraConfig = mkFastcgiPass cfg;
+          };
+          "${stripLocation cfg}/" = {
+            fastcgiParams = {
+              SCRIPT_FILENAME = "${cfg.package}/cgit/cgit.cgi";
+              QUERY_STRING = "$args";
+              HTTP_HOST = "$server_name";
+              CGIT_CONFIG = mkCgitrc cfg;
+            };
+            extraConfig = mkFastcgiPass cfg;
+          };
+        };
+      };
+    }) cfgs);
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/charybdis.nix b/nixpkgs/nixos/modules/services/networking/charybdis.nix
index c875557a1a38..168da243dba1 100644
--- a/nixpkgs/nixos/modules/services/networking/charybdis.nix
+++ b/nixpkgs/nixos/modules/services/networking/charybdis.nix
@@ -18,7 +18,7 @@ in
 
     services.charybdis = {
 
-      enable = mkEnableOption "Charybdis IRC daemon";
+      enable = mkEnableOption (lib.mdDoc "Charybdis IRC daemon");
 
       config = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/chisel-server.nix b/nixpkgs/nixos/modules/services/networking/chisel-server.nix
new file mode 100644
index 000000000000..134c71430cd0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/chisel-server.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.chisel-server;
+
+in {
+  options = {
+    services.chisel-server = {
+      enable = mkEnableOption (mdDoc "Chisel Tunnel Server");
+      host = mkOption {
+        description = mdDoc "Address to listen on, falls back to 0.0.0.0";
+        type = with types; nullOr str;
+        default = null;
+        example = "[::1]";
+      };
+      port = mkOption {
+        description = mdDoc "Port to listen on, falls back to 8080";
+        type = with types; nullOr port;
+        default = null;
+      };
+      authfile = mkOption {
+        description = mdDoc "Path to auth.json file";
+        type = with types; nullOr path;
+        default = null;
+      };
+      keepalive  = mkOption {
+        description = mdDoc "Keepalive interval, falls back to 25s";
+        type = with types; nullOr str;
+        default = null;
+        example = "5s";
+      };
+      backend = mkOption {
+        description = mdDoc "HTTP server to proxy normal requests to";
+        type = with types; nullOr str;
+        default = null;
+        example = "http://127.0.0.1:8888";
+      };
+      socks5 = mkOption {
+        description = mdDoc "Allow clients access to internal SOCKS5 proxy";
+        type = types.bool;
+        default = false;
+      };
+      reverse = mkOption {
+        description = mdDoc "Allow clients reverse port forwarding";
+        type = types.bool;
+        default = false;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.chisel-server = {
+      description = "Chisel Tunnel Server";
+      wantedBy = [ "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.chisel}/bin/chisel server " + concatStringsSep " " (
+          optional (cfg.host != null) "--host ${cfg.host}"
+          ++ optional (cfg.port != null) "--port ${builtins.toString cfg.port}"
+          ++ optional (cfg.authfile != null) "--authfile ${cfg.authfile}"
+          ++ optional (cfg.keepalive != null) "--keepalive ${cfg.keepalive}"
+          ++ optional (cfg.backend != null) "--backend ${cfg.backend}"
+          ++ optional cfg.socks5 "--socks5"
+          ++ optional cfg.reverse "--reverse"
+        );
+
+        # Security Hardening
+        # Refer to systemd.exec(5) for option descriptions.
+        CapabilityBoundingSet = "";
+
+        # implies RemoveIPC=, PrivateTmp=, NoNewPrivileges=, RestrictSUIDSGID=,
+        # ProtectSystem=strict, ProtectHome=read-only
+        DynamicUser = true;
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "~@clock @cpu-emulation @debug @mount @obsolete @reboot @swap @privileged @resources";
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ clerie ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/cloudflare-dyndns.nix b/nixpkgs/nixos/modules/services/networking/cloudflare-dyndns.nix
index 5dd90cfe35ba..627fdb880a67 100644
--- a/nixpkgs/nixos/modules/services/networking/cloudflare-dyndns.nix
+++ b/nixpkgs/nixos/modules/services/networking/cloudflare-dyndns.nix
@@ -8,12 +8,12 @@ in
 {
   options = {
     services.cloudflare-dyndns = {
-      enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+      enable = mkEnableOption (lib.mdDoc "Cloudflare Dynamic DNS Client");
 
       apiTokenFile = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to a file containing the CloudFlare API token.
 
           The file must have the form `CLOUDFLARE_API_TOKEN=...`
diff --git a/nixpkgs/nixos/modules/services/networking/cloudflared.nix b/nixpkgs/nixos/modules/services/networking/cloudflared.nix
new file mode 100644
index 000000000000..b3f0e37d8e9e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/cloudflared.nix
@@ -0,0 +1,331 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cloudflared;
+
+  originRequest = {
+    connectTimeout = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "30s";
+      description = lib.mdDoc ''
+        Timeout for establishing a new TCP connection to your origin server. This excludes the time taken to establish TLS, which is controlled by [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/#tlstimeout](tlsTimeout).
+      '';
+    };
+
+    tlsTimeout = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "10s";
+      description = lib.mdDoc ''
+        Timeout for completing a TLS handshake to your origin server, if you have chosen to connect Tunnel to an HTTPS server.
+      '';
+    };
+
+    tcpKeepAlive = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "30s";
+      description = lib.mdDoc ''
+        The timeout after which a TCP keepalive packet is sent on a connection between Tunnel and the origin server.
+      '';
+    };
+
+    noHappyEyeballs = mkOption {
+      type = with types; nullOr bool;
+      default = null;
+      example = false;
+      description = lib.mdDoc ''
+        Disable the “happy eyeballs” algorithm for IPv4/IPv6 fallback if your local network has misconfigured one of the protocols.
+      '';
+    };
+
+    keepAliveConnections = mkOption {
+      type = with types; nullOr int;
+      default = null;
+      example = 100;
+      description = lib.mdDoc ''
+        Maximum number of idle keepalive connections between Tunnel and your origin. This does not restrict the total number of concurrent connections.
+      '';
+    };
+
+    keepAliveTimeout = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "1m30s";
+      description = lib.mdDoc ''
+        Timeout after which an idle keepalive connection can be discarded.
+      '';
+    };
+
+    httpHostHeader = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "";
+      description = lib.mdDoc ''
+        Sets the HTTP `Host` header on requests sent to the local service.
+      '';
+    };
+
+    originServerName = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "";
+      description = lib.mdDoc ''
+        Hostname that `cloudflared` should expect from your origin server certificate.
+      '';
+    };
+
+    caPool = mkOption {
+      type = with types; nullOr (either str path);
+      default = null;
+      example = "";
+      description = lib.mdDoc ''
+        Path to the certificate authority (CA) for the certificate of your origin. This option should be used only if your certificate is not signed by Cloudflare.
+      '';
+    };
+
+    noTLSVerify = mkOption {
+      type = with types; nullOr bool;
+      default = null;
+      example = false;
+      description = lib.mdDoc ''
+        Disables TLS verification of the certificate presented by your origin. Will allow any certificate from the origin to be accepted.
+      '';
+    };
+
+    disableChunkedEncoding = mkOption {
+      type = with types; nullOr bool;
+      default = null;
+      example = false;
+      description = lib.mdDoc ''
+        Disables chunked transfer encoding. Useful if you are running a WSGI server.
+      '';
+    };
+
+    proxyAddress = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "127.0.0.1";
+      description = lib.mdDoc ''
+        `cloudflared` starts a proxy server to translate HTTP traffic into TCP when proxying, for example, SSH or RDP. This configures the listen address for that proxy.
+      '';
+    };
+
+    proxyPort = mkOption {
+      type = with types; nullOr int;
+      default = null;
+      example = 0;
+      description = lib.mdDoc ''
+        `cloudflared` starts a proxy server to translate HTTP traffic into TCP when proxying, for example, SSH or RDP. This configures the listen port for that proxy. If set to zero, an unused port will randomly be chosen.
+      '';
+    };
+
+    proxyType = mkOption {
+      type = with types; nullOr (enum [ "" "socks" ]);
+      default = null;
+      example = "";
+      description = lib.mdDoc ''
+        `cloudflared` starts a proxy server to translate HTTP traffic into TCP when proxying, for example, SSH or RDP. This configures what type of proxy will be started. Valid options are:
+
+        - `""` for the regular proxy
+        - `"socks"` for a SOCKS5 proxy. Refer to the [https://developers.cloudflare.com/cloudflare-one/tutorials/kubectl/](tutorial on connecting through Cloudflare Access using kubectl) for more information.
+      '';
+    };
+  };
+in
+{
+  options.services.cloudflared = {
+    enable = mkEnableOption (lib.mdDoc "Cloudflare Tunnel client daemon (formerly Argo Tunnel)");
+
+    user = mkOption {
+      type = types.str;
+      default = "cloudflared";
+      description = lib.mdDoc "User account under which Cloudflared runs.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "cloudflared";
+      description = lib.mdDoc "Group under which cloudflared runs.";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.cloudflared;
+      defaultText = "pkgs.cloudflared";
+      description = lib.mdDoc "The package to use for Cloudflared.";
+    };
+
+    tunnels = mkOption {
+      description = lib.mdDoc ''
+        Cloudflare tunnels.
+      '';
+      type = types.attrsOf (types.submodule ({ name, ... }: {
+        options = {
+          inherit originRequest;
+
+          credentialsFile = mkOption {
+            type = types.str;
+            description = lib.mdDoc ''
+              Credential file.
+
+              See [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-useful-terms/#credentials-file](Credentials file).
+            '';
+          };
+
+          warp-routing = {
+            enabled = mkOption {
+              type = with types; nullOr bool;
+              default = null;
+              description = lib.mdDoc ''
+                Enable warp routing.
+
+                See [https://developers.cloudflare.com/cloudflare-one/tutorials/warp-to-tunnel/](Connect from WARP to a private network on Cloudflare using Cloudflare Tunnel).
+              '';
+            };
+          };
+
+          default = mkOption {
+            type = types.str;
+            description = lib.mdDoc ''
+              Catch-all service if no ingress matches.
+
+              See `service`.
+            '';
+            example = "http_status:404";
+          };
+
+          ingress = mkOption {
+            type = with types; attrsOf (either str (submodule ({ hostname, ... }: {
+              options = {
+                inherit originRequest;
+
+                service = mkOption {
+                  type = with types; nullOr str;
+                  default = null;
+                  description = lib.mdDoc ''
+                    Service to pass the traffic.
+
+                    See [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/#supported-protocols](Supported protocols).
+                  '';
+                  example = "http://localhost:80, tcp://localhost:8000, unix:/home/production/echo.sock, hello_world or http_status:404";
+                };
+
+                path = mkOption {
+                  type = with types; nullOr str;
+                  default = null;
+                  description = lib.mdDoc ''
+                    Path filter.
+
+                    If not specified, all paths will be matched.
+                  '';
+                  example = "/*.(jpg|png|css|js)";
+                };
+
+              };
+            })));
+            default = { };
+            description = lib.mdDoc ''
+              Ingress rules.
+
+              See [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/](Ingress rules).
+            '';
+            example = {
+              "*.domain.com" = "http://localhost:80";
+              "*.anotherone.com" = "http://localhost:80";
+            };
+          };
+        };
+      }));
+
+      default = { };
+      example = {
+        "00000000-0000-0000-0000-000000000000" = {
+          credentialsFile = "/tmp/test";
+          ingress = {
+            "*.domain1.com" = {
+              service = "http://localhost:80";
+            };
+          };
+          default = "http_status:404";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.targets =
+      mapAttrs'
+        (name: tunnel:
+          nameValuePair "cloudflared-tunnel-${name}" {
+            description = "Cloudflare tunnel '${name}' target";
+            requires = [ "cloudflared-tunnel-${name}.service" ];
+            after = [ "cloudflared-tunnel-${name}.service" ];
+            unitConfig.StopWhenUnneeded = true;
+          }
+        )
+        config.services.cloudflared.tunnels;
+
+    systemd.services =
+      mapAttrs'
+        (name: tunnel:
+          let
+            filterConfig = lib.attrsets.filterAttrsRecursive (_: v: ! builtins.elem v [ null [ ] { } ]);
+
+            filterIngressSet = filterAttrs (_: v: builtins.typeOf v == "set");
+            filterIngressStr = filterAttrs (_: v: builtins.typeOf v == "string");
+
+            ingressesSet = filterIngressSet tunnel.ingress;
+            ingressesStr = filterIngressStr tunnel.ingress;
+
+            fullConfig = {
+              tunnel = name;
+              "credentials-file" = tunnel.credentialsFile;
+              ingress =
+                (map
+                  (key: {
+                    hostname = key;
+                  } // getAttr key (filterConfig (filterConfig ingressesSet)))
+                  (attrNames ingressesSet))
+                ++
+                (map
+                  (key: {
+                    hostname = key;
+                    service = getAttr key ingressesStr;
+                  })
+                  (attrNames ingressesStr))
+                ++ [{ service = tunnel.default; }];
+            };
+            mkConfigFile = pkgs.writeText "cloudflared.yml" (builtins.toJSON fullConfig);
+          in
+          nameValuePair "cloudflared-tunnel-${name}" ({
+            after = [ "network.target" "network-online.target" ];
+            wants = [ "network.target" "network-online.target" ];
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              User = cfg.user;
+              Group = cfg.group;
+              ExecStart = "${cfg.package}/bin/cloudflared tunnel --config=${mkConfigFile} --no-autoupdate run";
+              Restart = "on-failure";
+            };
+          })
+        )
+        config.services.cloudflared.tunnels;
+
+    users.users = mkIf (cfg.user == "cloudflared") {
+      cloudflared = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "cloudflared") {
+      cloudflared = { };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ bbigras ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/cntlm.nix b/nixpkgs/nixos/modules/services/networking/cntlm.nix
index 2b5d0583c653..41510a8f074d 100644
--- a/nixpkgs/nixos/modules/services/networking/cntlm.nix
+++ b/nixpkgs/nixos/modules/services/networking/cntlm.nix
@@ -33,7 +33,7 @@ in
 
   options.services.cntlm = {
 
-    enable = mkEnableOption "cntlm, which starts a local proxy";
+    enable = mkEnableOption (lib.mdDoc "cntlm, which starts a local proxy");
 
     username = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/consul.nix b/nixpkgs/nixos/modules/services/networking/consul.nix
index 16f1b5eec879..955463b9031e 100644
--- a/nixpkgs/nixos/modules/services/networking/consul.nix
+++ b/nixpkgs/nixos/modules/services/networking/consul.nix
@@ -126,7 +126,7 @@ in
       };
 
       alerts = {
-        enable = mkEnableOption "consul-alerts";
+        enable = mkEnableOption (lib.mdDoc "consul-alerts");
 
         package = mkOption {
           description = lib.mdDoc "Package to use for consul-alerts.";
@@ -142,7 +142,7 @@ in
         };
 
         consulAddr = mkOption {
-          description = lib.mdDoc "Consul api listening adddress";
+          description = lib.mdDoc "Consul api listening address";
           default = "localhost:8500";
           type = types.str;
         };
@@ -199,18 +199,18 @@ in
             (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
 
         serviceConfig = {
-          ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d"
+          ExecStart = "@${lib.getExe cfg.package} consul agent -config-dir /etc/consul.d"
             + concatMapStrings (n: " -config-file ${n}") configFiles;
-          ExecReload = "${cfg.package}/bin/consul reload";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           PermissionsStartOnly = true;
           User = if cfg.dropPrivileges then "consul" else null;
           Restart = "on-failure";
           TimeoutStartSec = "infinity";
         } // (optionalAttrs (cfg.leaveOnStop) {
-          ExecStop = "${cfg.package}/bin/consul leave";
+          ExecStop = "${lib.getExe cfg.package} leave";
         });
 
-        path = with pkgs; [ iproute2 gnugrep gawk consul ];
+        path = with pkgs; [ iproute2 gawk cfg.package ];
         preStart = let
           family = if cfg.forceAddrFamily == "ipv6" then
             "-6"
@@ -269,7 +269,7 @@ in
 
         serviceConfig = {
           ExecStart = ''
-            ${cfg.alerts.package}/bin/consul-alerts start \
+            ${lib.getExe cfg.alerts.package} start \
               --alert-addr=${cfg.alerts.listenAddr} \
               --consul-addr=${cfg.alerts.consulAddr} \
               ${optionalString cfg.alerts.watchChecks "--watch-checks"} \
diff --git a/nixpkgs/nixos/modules/services/networking/coredns.nix b/nixpkgs/nixos/modules/services/networking/coredns.nix
index deaba69e99fa..f928cdf96143 100644
--- a/nixpkgs/nixos/modules/services/networking/coredns.nix
+++ b/nixpkgs/nixos/modules/services/networking/coredns.nix
@@ -7,7 +7,7 @@ let
   configFile = pkgs.writeText "Corefile" cfg.config;
 in {
   options.services.coredns = {
-    enable = mkEnableOption "Coredns dns server";
+    enable = mkEnableOption (lib.mdDoc "Coredns dns server");
 
     config = mkOption {
       default = "";
diff --git a/nixpkgs/nixos/modules/services/networking/corerad.nix b/nixpkgs/nixos/modules/services/networking/corerad.nix
index 88428eba5589..0c6fb7a17cab 100644
--- a/nixpkgs/nixos/modules/services/networking/corerad.nix
+++ b/nixpkgs/nixos/modules/services/networking/corerad.nix
@@ -10,7 +10,7 @@ in {
   meta.maintainers = with maintainers; [ mdlayher ];
 
   options.services.corerad = {
-    enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon";
+    enable = mkEnableOption (lib.mdDoc "CoreRAD IPv6 NDP RA daemon");
 
     settings = mkOption {
       type = settingsFormat.type;
diff --git a/nixpkgs/nixos/modules/services/networking/coturn.nix b/nixpkgs/nixos/modules/services/networking/coturn.nix
index 788c51aed6b0..2f34a72377ce 100644
--- a/nixpkgs/nixos/modules/services/networking/coturn.nix
+++ b/nixpkgs/nixos/modules/services/networking/coturn.nix
@@ -40,7 +40,7 @@ ${cfg.extraConfig}
 in {
   options = {
     services.coturn = {
-      enable = mkEnableOption "coturn TURN server";
+      enable = mkEnableOption (lib.mdDoc "coturn TURN server");
       listening-port = mkOption {
         type = types.int;
         default = 3478;
@@ -335,9 +335,10 @@ in {
         preStart = ''
           cat ${configFile} > ${runConfig}
           ${optionalString (cfg.static-auth-secret-file != null) ''
-            STATIC_AUTH_SECRET="$(head -n1 ${cfg.static-auth-secret-file} || :)"
-            sed -e "s,#static-auth-secret#,$STATIC_AUTH_SECRET,g" \
-              -i ${runConfig}
+            ${pkgs.replace-secret}/bin/replace-secret \
+              "#static-auth-secret#" \
+              ${cfg.static-auth-secret-file} \
+              ${runConfig}
           '' }
           chmod 640 ${runConfig}
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/create_ap.nix b/nixpkgs/nixos/modules/services/networking/create_ap.nix
index a3c330fab007..e772cf21ec57 100644
--- a/nixpkgs/nixos/modules/services/networking/create_ap.nix
+++ b/nixpkgs/nixos/modules/services/networking/create_ap.nix
@@ -8,13 +8,13 @@ let
 in {
   options = {
     services.create_ap = {
-      enable = mkEnableOption "setup wifi hotspots using create_ap";
+      enable = mkEnableOption (lib.mdDoc "setup wifi hotspots using create_ap");
       settings = mkOption {
         type = with types; attrsOf (oneOf [ int bool str ]);
         default = {};
-        description = ''
-          Configuration for <package>create_ap</package>.
-          See <link xlink:href="https://raw.githubusercontent.com/lakinduakash/linux-wifi-hotspot/master/src/scripts/create_ap.conf">upstream example configuration</link>
+        description = lib.mdDoc ''
+          Configuration for `create_ap`.
+          See [upstream example configuration](https://raw.githubusercontent.com/lakinduakash/linux-wifi-hotspot/master/src/scripts/create_ap.conf)
           for supported values.
         '';
         example = {
diff --git a/nixpkgs/nixos/modules/services/networking/croc.nix b/nixpkgs/nixos/modules/services/networking/croc.nix
index 82035856733c..45bfd447da45 100644
--- a/nixpkgs/nixos/modules/services/networking/croc.nix
+++ b/nixpkgs/nixos/modules/services/networking/croc.nix
@@ -6,7 +6,7 @@ let
 in
 {
   options.services.croc = {
-    enable = lib.mkEnableOption "croc relay";
+    enable = lib.mkEnableOption (lib.mdDoc "croc relay");
     ports = lib.mkOption {
       type = with types; listOf port;
       default = [9009 9010 9011 9012 9013];
@@ -17,8 +17,8 @@ in
       default = "pass123";
       description = lib.mdDoc "Password or passwordfile for the relay.";
     };
-    openFirewall = lib.mkEnableOption "opening of the peer port(s) in the firewall";
-    debug = lib.mkEnableOption "debug logs";
+    openFirewall = lib.mkEnableOption (lib.mdDoc "opening of the peer port(s) in the firewall");
+    debug = lib.mkEnableOption (lib.mdDoc "debug logs");
   };
 
   config = lib.mkIf cfg.enable {
@@ -72,7 +72,7 @@ in
         RuntimeDirectoryMode = "700";
         SystemCallFilter = [
           "@system-service"
-          "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@setuid" "~@sync" "~@timer"
+          "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@setuid" "~@sync" "~@timer"
         ];
         SystemCallArchitectures = "native";
         SystemCallErrorNumber = "EPERM";
diff --git a/nixpkgs/nixos/modules/services/networking/dante.nix b/nixpkgs/nixos/modules/services/networking/dante.nix
index 5ddbee88609b..605f2d74f827 100644
--- a/nixpkgs/nixos/modules/services/networking/dante.nix
+++ b/nixpkgs/nixos/modules/services/networking/dante.nix
@@ -19,7 +19,7 @@ in
 
   options = {
     services.dante = {
-      enable = mkEnableOption "Dante SOCKS proxy";
+      enable = mkEnableOption (lib.mdDoc "Dante SOCKS proxy");
 
       config = mkOption {
         type        = types.lines;
diff --git a/nixpkgs/nixos/modules/services/networking/ddclient.nix b/nixpkgs/nixos/modules/services/networking/ddclient.nix
index a0e9405343ba..7caee8a8eb3d 100644
--- a/nixpkgs/nixos/modules/services/networking/ddclient.nix
+++ b/nixpkgs/nixos/modules/services/networking/ddclient.nix
@@ -29,9 +29,9 @@ let
   configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
 
   preStart = ''
-    install ${configFile} /run/${RuntimeDirectory}/ddclient.conf
+    install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf
     ${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
-      install ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
+      install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
     '' else if (cfg.passwordFile != null) then ''
       "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
     '' else ''
@@ -71,7 +71,7 @@ with lib;
       package = mkOption {
         type = package;
         default = pkgs.ddclient;
-        defaultText = "pkgs.ddclient";
+        defaultText = lib.literalExpression "pkgs.ddclient";
         description = lib.mdDoc ''
           The ddclient executable package run by the service.
         '';
@@ -200,6 +200,10 @@ with lib;
         type = lines;
         description = lib.mdDoc ''
           Extra configuration. Contents will be added verbatim to the configuration file.
+
+          ::: {.note}
+          `daemon` should not be added here because it does not work great with the systemd-timer approach the service uses.
+          :::
         '';
       };
     };
@@ -214,6 +218,7 @@ with lib;
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       restartTriggers = optional (cfg.configFile != null) cfg.configFile;
+      path = lib.optional (lib.hasPrefix "if," cfg.use) pkgs.iproute2;
 
       serviceConfig = {
         DynamicUser = true;
diff --git a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
index 6b7ce828919c..8b6d3fc55f3e 100644
--- a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
@@ -33,6 +33,13 @@ let
     (if !config.networking.useDHCP && enableDHCP then
       map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
 
+  staticIPv6Addresses = map (i: i.name) (filter (i: i.ipv6.addresses != [ ]) interfaces);
+
+  noIPv6rs = concatStringsSep "\n" (map (name: ''
+    interface ${name}
+    noipv6rs
+  '') staticIPv6Addresses);
+
   # Config file adapted from the one that ships with dhcpcd.
   dhcpcdConf = pkgs.writeText "dhcpcd.conf"
     ''
@@ -74,6 +81,11 @@ let
         noipv6
       ''}
 
+      ${optionalString (config.networking.enableIPv6 && cfg.IPv6rs == null && staticIPv6Addresses != [ ]) noIPv6rs}
+      ${optionalString (config.networking.enableIPv6 && cfg.IPv6rs == false) ''
+        noipv6rs
+      ''}
+
       ${cfg.extraConfig}
     '';
 
@@ -151,11 +163,21 @@ in
       '';
     };
 
+    networking.dhcpcd.IPv6rs = mkOption {
+      type = types.nullOr types.bool;
+      default = null;
+      description = lib.mdDoc ''
+        Force enable or disable solicitation and receipt of IPv6 Router Advertisements.
+        This is required, for example, when using a static unique local IPv6 address (ULA)
+        and global IPv6 address auto-configuration with SLAAC.
+      '';
+    };
+
     networking.dhcpcd.runHook = mkOption {
       type = types.lines;
       default = "";
       example = "if [[ $reason =~ BOUND ]]; then echo $interface: Routers are $new_routers - were $old_routers; fi";
-      description = ''
+      description = lib.mdDoc ''
          Shell code that will be run after all other hooks. See
          `man dhcpcd-run-hooks` for details on what is possible.
       '';
diff --git a/nixpkgs/nixos/modules/services/networking/dhcpd.nix b/nixpkgs/nixos/modules/services/networking/dhcpd.nix
index 0bd5e4ef5535..a981a255c3ee 100644
--- a/nixpkgs/nixos/modules/services/networking/dhcpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/dhcpd.nix
@@ -218,6 +218,13 @@ in
 
     systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
 
+    warnings = [
+      ''
+        The dhcpd4 and dhcpd6 modules will be removed from NixOS 23.11, because ISC DHCP reached its end of life.
+        See https://www.isc.org/blogs/isc-dhcp-eol/ for details.
+        Please switch to a different implementation like kea, systemd-networkd or dnsmasq.
+      ''
+    ];
   };
 
 }
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix
index ff7934101039..de1ca0d2f206 100644
--- a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -6,7 +6,7 @@ in
 
 {
   options.services.dnscrypt-proxy2 = {
-    enable = mkEnableOption "dnscrypt-proxy2";
+    enable = mkEnableOption (lib.mdDoc "dnscrypt-proxy2");
 
     settings = mkOption {
       description = lib.mdDoc ''
@@ -56,7 +56,7 @@ in
         ''}
         ${pkgs.remarshal}/bin/json2toml < config.json > $out
       '';
-      defaultText = literalDocBook "TOML file generated from <option>services.dnscrypt-proxy2.settings</option>";
+      defaultText = literalMD "TOML file generated from {option}`services.dnscrypt-proxy2.settings`";
     };
   };
 
@@ -111,7 +111,6 @@ in
           "~@aio"
           "~@keyring"
           "~@memlock"
-          "~@resources"
           "~@setuid"
           "~@timer"
         ];
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
index 5df1e8b51a52..082e0195093e 100644
--- a/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -124,7 +124,7 @@ in {
   ###### interface
 
   options.services.dnscrypt-wrapper = {
-    enable = mkEnableOption "DNSCrypt wrapper";
+    enable = mkEnableOption (lib.mdDoc "DNSCrypt wrapper");
 
     address = mkOption {
       type = types.str;
@@ -135,7 +135,7 @@ in {
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 5353;
       description = lib.mdDoc ''
         The DNSCrypt wrapper will listen for DNS queries on this port.
@@ -182,7 +182,7 @@ in {
     };
 
     upstream.port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 53;
       description = lib.mdDoc ''
         The port of the upstream DNS server DNSCrypt will "wrap".
diff --git a/nixpkgs/nixos/modules/services/networking/dnsdist.nix b/nixpkgs/nixos/modules/services/networking/dnsdist.nix
index 44503248cf89..483300111df9 100644
--- a/nixpkgs/nixos/modules/services/networking/dnsdist.nix
+++ b/nixpkgs/nixos/modules/services/networking/dnsdist.nix
@@ -11,7 +11,7 @@ let
 in {
   options = {
     services.dnsdist = {
-      enable = mkEnableOption "dnsdist domain name server";
+      enable = mkEnableOption (lib.mdDoc "dnsdist domain name server");
 
       listenAddress = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/dnsmasq.nix b/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
index cfc37b74b9a9..4886654e8c03 100644
--- a/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
@@ -7,15 +7,27 @@ let
   dnsmasq = pkgs.dnsmasq;
   stateDir = "/var/lib/dnsmasq";
 
+  # True values are just put as `name` instead of `name=true`, and false values
+  # are turned to comments (false values are expected to be overrides e.g.
+  # mkForce)
+  formatKeyValue =
+    name: value:
+    if value == true
+    then name
+    else if value == false
+    then "# setting `${name}` explicitly set to false"
+    else generators.mkKeyValueDefault { } "=" name value;
+
+  settingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = formatKeyValue;
+    listsAsDuplicateKeys = true;
+  };
+
+  # Because formats.generate is outputting a file, we use of conf-file. Once
+  # `extraConfig` is deprecated we can just use
+  # `dnsmasqConf = format.generate "dnsmasq.conf" cfg.settings`
   dnsmasqConf = pkgs.writeText "dnsmasq.conf" ''
-    dhcp-leasefile=${stateDir}/dnsmasq.leases
-    ${optionalString cfg.resolveLocalQueries ''
-      conf-file=/etc/dnsmasq-conf.conf
-      resolv-file=/etc/dnsmasq-resolv.conf
-    ''}
-    ${flip concatMapStrings cfg.servers (server: ''
-      server=${server}
-    '')}
+    conf-file=${settingsFormat.generate "dnsmasq.conf" cfg.settings}
     ${cfg.extraConfig}
   '';
 
@@ -23,6 +35,10 @@ in
 
 {
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "dnsmasq" "servers" ] [ "services" "dnsmasq" "settings" "server" ])
+  ];
+
   ###### interface
 
   options = {
@@ -46,15 +62,6 @@ in
         '';
       };
 
-      servers = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "8.8.8.8" "8.8.4.4" ];
-        description = lib.mdDoc ''
-          The DNS servers which dnsmasq should query.
-        '';
-      };
-
       alwaysKeepRunning = mkOption {
         type = types.bool;
         default = false;
@@ -63,12 +70,49 @@ in
         '';
       };
 
+      settings = mkOption {
+        type = types.submodule {
+
+          freeformType = settingsFormat.type;
+
+          options.server = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "8.8.8.8" "8.8.4.4" ];
+            description = lib.mdDoc ''
+              The DNS servers which dnsmasq should query.
+            '';
+          };
+
+        };
+        default = { };
+        description = lib.mdDoc ''
+          Configuration of dnsmasq. Lists get added one value per line (empty
+          lists and false values don't get added, though false values get
+          turned to comments). Gets merged with
+
+              {
+                dhcp-leasefile = "${stateDir}/dnsmasq.leases";
+                conf-file = optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf";
+                resolv-file = optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf";
+              }
+        '';
+        example = literalExpression ''
+          {
+            domain-needed = true;
+            dhcp-range = [ "192.168.0.2,192.168.0.254" ];
+          }
+        '';
+      };
+
       extraConfig = mkOption {
         type = types.lines;
         default = "";
         description = lib.mdDoc ''
           Extra configuration directives that should be added to
           `dnsmasq.conf`.
+
+          This option is deprecated, please use {option}`settings` instead.
         '';
       };
 
@@ -81,6 +125,14 @@ in
 
   config = mkIf cfg.enable {
 
+    warnings = lib.optional (cfg.extraConfig != "") "Text based config is deprecated, dnsmasq now supports `services.dnsmasq.settings` for an attribute-set based config";
+
+    services.dnsmasq.settings = {
+      dhcp-leasefile = mkDefault "${stateDir}/dnsmasq.leases";
+      conf-file = mkDefault (optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf");
+      resolv-file = mkDefault (optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf");
+    };
+
     networking.nameservers =
       optional cfg.resolveLocalQueries "127.0.0.1";
 
diff --git a/nixpkgs/nixos/modules/services/networking/doh-proxy-rust.nix b/nixpkgs/nixos/modules/services/networking/doh-proxy-rust.nix
index bfd88430d78c..7f8bbb8a7699 100644
--- a/nixpkgs/nixos/modules/services/networking/doh-proxy-rust.nix
+++ b/nixpkgs/nixos/modules/services/networking/doh-proxy-rust.nix
@@ -10,7 +10,7 @@ in {
 
   options.services.doh-proxy-rust = {
 
-    enable = mkEnableOption "doh-proxy-rust";
+    enable = mkEnableOption (lib.mdDoc "doh-proxy-rust");
 
     flags = mkOption {
       type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/envoy.nix b/nixpkgs/nixos/modules/services/networking/envoy.nix
index 6f3080d19e2b..c68ceab9619c 100644
--- a/nixpkgs/nixos/modules/services/networking/envoy.nix
+++ b/nixpkgs/nixos/modules/services/networking/envoy.nix
@@ -6,17 +6,28 @@ let
   cfg = config.services.envoy;
   format = pkgs.formats.json { };
   conf = format.generate "envoy.json" cfg.settings;
-  validateConfig = file:
+  validateConfig = required: file:
     pkgs.runCommand "validate-envoy-conf" { } ''
-      ${pkgs.envoy}/bin/envoy --log-level error --mode validate -c "${file}"
+      ${cfg.package}/bin/envoy --log-level error --mode validate -c "${file}" ${lib.optionalString (!required) "|| true"}
       cp "${file}" "$out"
     '';
-
 in
 
 {
   options.services.envoy = {
-    enable = mkEnableOption "Envoy reverse proxy";
+    enable = mkEnableOption (lib.mdDoc "Envoy reverse proxy");
+
+    package = mkPackageOptionMD pkgs "envoy" { };
+
+    requireValidConfig = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Whether a failure during config validation at build time is fatal.
+        When the config can't be checked during build time, for example when it includes
+        other files, disable this option.
+      '';
+    };
 
     settings = mkOption {
       type = format.type;
@@ -46,38 +57,44 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.envoy ];
+    environment.systemPackages = [ cfg.package ];
     systemd.services.envoy = {
       description = "Envoy reverse proxy";
       after = [ "network-online.target" ];
       requires = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.envoy}/bin/envoy -c ${validateConfig conf}";
-        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/envoy -c ${validateConfig cfg.requireValidConfig conf}";
+        CacheDirectory = [ "envoy" ];
+        LogsDirectory = [ "envoy" ];
         Restart = "no";
-        CacheDirectory = "envoy";
-        LogsDirectory = "envoy";
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK AF_XDP";
-        SystemCallArchitectures = "native";
+        # Hardening
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        DynamicUser = true;
         LockPersonality = true;
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        PrivateUsers = false;  # breaks CAP_NET_BIND_SERVICE
+        MemoryDenyWriteExecute = false; # at least wasmr needs WX permission
         PrivateDevices = true;
+        PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE
+        ProcSubset = "pid";
         ProtectClock = true;
         ProtectControlGroups = true;
         ProtectHome = true;
+        ProtectHostname = true;
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
         ProtectProc = "ptraceable";
-        ProtectHostname = true;
         ProtectSystem = "strict";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" "AF_XDP" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
         UMask = "0066";
-        SystemCallFilter = "~@clock @module @mount @reboot @swap @obsolete @cpu-emulation";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/networking/epmd.nix b/nixpkgs/nixos/modules/services/networking/epmd.nix
index 534b80906211..0bc8c71f4eaa 100644
--- a/nixpkgs/nixos/modules/services/networking/epmd.nix
+++ b/nixpkgs/nixos/modules/services/networking/epmd.nix
@@ -32,7 +32,7 @@ in
         default = "[::]:4369";
         description = lib.mdDoc ''
           the listenStream used by the systemd socket.
-          see https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream= for more informations.
+          see https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream= for more information.
           use this to change the port epmd will run on.
           if not defined, epmd will use "[::]:4369"
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/ergo.nix b/nixpkgs/nixos/modules/services/networking/ergo.nix
index 0dbb862b8ecd..033d4d9caf8a 100644
--- a/nixpkgs/nixos/modules/services/networking/ergo.nix
+++ b/nixpkgs/nixos/modules/services/networking/ergo.nix
@@ -33,7 +33,7 @@ in {
   options = {
 
     services.ergo = {
-      enable = mkEnableOption "Ergo service";
+      enable = mkEnableOption (lib.mdDoc "Ergo service");
 
       dataDir = mkOption {
         type = types.path;
diff --git a/nixpkgs/nixos/modules/services/networking/ergochat.nix b/nixpkgs/nixos/modules/services/networking/ergochat.nix
index 5e815a9eff20..a003512677eb 100644
--- a/nixpkgs/nixos/modules/services/networking/ergochat.nix
+++ b/nixpkgs/nixos/modules/services/networking/ergochat.nix
@@ -4,7 +4,7 @@ in {
   options = {
     services.ergochat = {
 
-      enable = lib.mkEnableOption "Ergo IRC daemon";
+      enable = lib.mkEnableOption (lib.mdDoc "Ergo IRC daemon");
 
       openFilesLimit = lib.mkOption {
         type = lib.types.int;
@@ -17,10 +17,10 @@ in {
       configFile = lib.mkOption {
         type = lib.types.path;
         default = (pkgs.formats.yaml {}).generate "ergo.conf" cfg.settings;
-        defaultText = "generated config file from <literal>.settings</literal>";
+        defaultText = lib.literalMD "generated config file from `settings`";
         description = lib.mdDoc ''
           Path to configuration file.
-          Setting this will skip any configuration done via `.settings`
+          Setting this will skip any configuration done via `settings`
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
index e90b6103a218..c6b6b04dcf72 100644
--- a/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
+++ b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
@@ -16,11 +16,11 @@ in
 
     services.eternal-terminal = {
 
-      enable = mkEnableOption "Eternal Terminal server";
+      enable = mkEnableOption (lib.mdDoc "Eternal Terminal server");
 
       port = mkOption {
         default = 2022;
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc ''
           The port the server should listen on. Will use the server's default (2022) if not specified.
 
diff --git a/nixpkgs/nixos/modules/services/networking/ferm.nix b/nixpkgs/nixos/modules/services/networking/ferm.nix
index 7faebcef6300..09151eb0b544 100644
--- a/nixpkgs/nixos/modules/services/networking/ferm.nix
+++ b/nixpkgs/nixos/modules/services/networking/ferm.nix
@@ -20,7 +20,7 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Ferm Firewall.
           *Warning*: Enabling this service WILL disable the existing NixOS
           firewall! Default firewall rules provided by packages are not
@@ -30,7 +30,7 @@ in {
       config = mkOption {
         description = lib.mdDoc "Verbatim ferm.conf configuration.";
         default = "";
-        defaultText = literalDocBook "empty firewall, allows any traffic";
+        defaultText = literalMD "empty firewall, allows any traffic";
         type = types.lines;
       };
       package = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/firefox-syncserver.nix b/nixpkgs/nixos/modules/services/networking/firefox-syncserver.nix
index 254d5c1dc670..42924d7f6993 100644
--- a/nixpkgs/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixpkgs/nixos/modules/services/networking/firefox-syncserver.nix
@@ -11,14 +11,19 @@ let
 
   format = pkgs.formats.toml {};
   settings = {
-    database_url = dbURL;
     human_logs = true;
+    syncstorage = {
+      database_url = dbURL;
+    };
     tokenserver = {
       node_type = "mysql";
       database_url = dbURL;
       fxa_email_domain = "api.accounts.firefox.com";
       fxa_oauth_server_url = "https://oauth.accounts.firefox.com/v1";
       run_migrations = true;
+      # if JWK caching is not enabled the token server must verify tokens
+      # using the fxa api, on a thread pool with a static size.
+      additional_blocking_threads_for_fxa_requests = 10;
     } // lib.optionalAttrs cfg.singleNode.enable {
       # Single-node mode is likely to be used on small instances with little
       # capacity. The default value (0.1) can only ever release capacity when
@@ -29,33 +34,71 @@ let
     };
   };
   configFile = format.generate "syncstorage.toml" (lib.recursiveUpdate settings cfg.settings);
+  setupScript = pkgs.writeShellScript "firefox-syncserver-setup" ''
+        set -euo pipefail
+        shopt -s inherit_errexit
+
+        schema_configured() {
+          mysql ${cfg.database.name} -Ne 'SHOW TABLES' | grep -q services
+        }
+
+        update_config() {
+          mysql ${cfg.database.name} <<"EOF"
+            BEGIN;
+
+            INSERT INTO `services` (`id`, `service`, `pattern`)
+              VALUES (1, 'sync-1.5', '{node}/1.5/{uid}')
+              ON DUPLICATE KEY UPDATE service='sync-1.5', pattern='{node}/1.5/{uid}';
+            INSERT INTO `nodes` (`id`, `service`, `node`, `available`, `current_load`,
+                                 `capacity`, `downed`, `backoff`)
+              VALUES (1, 1, '${cfg.singleNode.url}', ${toString cfg.singleNode.capacity},
+              0, ${toString cfg.singleNode.capacity}, 0, 0)
+              ON DUPLICATE KEY UPDATE node = '${cfg.singleNode.url}', capacity=${toString cfg.singleNode.capacity};
+
+            COMMIT;
+        EOF
+        }
+
+
+        for (( try = 0; try < 60; try++ )); do
+          if ! schema_configured; then
+            sleep 2
+          else
+            update_config
+            exit 0
+          fi
+        done
+
+        echo "Single-node setup failed"
+        exit 1
+      '';
 in
 
 {
   options = {
     services.firefox-syncserver = {
-      enable = lib.mkEnableOption ''
+      enable = lib.mkEnableOption (lib.mdDoc ''
         the Firefox Sync storage service.
 
         Out of the box this will not be very useful unless you also configure at least
         one service and one nodes by inserting them into the mysql database manually, e.g.
         by running
 
-        <programlisting>
+        ```
           INSERT INTO `services` (`id`, `service`, `pattern`) VALUES ('1', 'sync-1.5', '{node}/1.5/{uid}');
           INSERT INTO `nodes` (`id`, `service`, `node`, `available`, `current_load`,
               `capacity`, `downed`, `backoff`)
             VALUES ('1', '1', 'https://mydomain.tld', '1', '0', '10', '0', '0');
-        </programlisting>
+        ```
 
-        <option>${opt.singleNode.enable}</option> does this automatically when enabled
-      '';
+        {option}`${opt.singleNode.enable}` does this automatically when enabled
+      '');
 
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.syncstorage-rs;
         defaultText = lib.literalExpression "pkgs.syncstorage-rs";
-        description = ''
+        description = lib.mdDoc ''
           Package to use.
         '';
       };
@@ -66,16 +109,16 @@ in
         # behavior ever change.
         type = lib.types.strMatching "[a-z_][a-z0-9_]*";
         default = defaultDatabase;
-        description = ''
+        description = lib.mdDoc ''
           Database to use for storage. Will be created automatically if it does not exist
-          and <literal>config.${opt.database.createLocally}</literal> is set.
+          and `config.${opt.database.createLocally}` is set.
         '';
       };
 
       database.user = lib.mkOption {
         type = lib.types.str;
         default = defaultUser;
-        description = ''
+        description = lib.mdDoc ''
           Username for database connections.
         '';
       };
@@ -83,8 +126,8 @@ in
       database.host = lib.mkOption {
         type = lib.types.str;
         default = "localhost";
-        description = ''
-          Database host name. <literal>localhost</literal> is treated specially and inserts
+        description = lib.mdDoc ''
+          Database host name. `localhost` is treated specially and inserts
           systemd dependencies, other hostnames or IP addresses of the local machine do not.
         '';
       };
@@ -92,7 +135,7 @@ in
       database.createLocally = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create database and user on the local machine if they do not exist.
           This includes enabling unix domain socket authentication for the configured user.
         '';
@@ -101,32 +144,32 @@ in
       logLevel = lib.mkOption {
         type = lib.types.str;
         default = "error";
-        description = ''
-          Log level to run with. This can be a simple log level like <literal>error</literal>
-          or <literal>trace</literal>, or a more complicated logging expression.
+        description = lib.mdDoc ''
+          Log level to run with. This can be a simple log level like `error`
+          or `trace`, or a more complicated logging expression.
         '';
       };
 
       secrets = lib.mkOption {
         type = lib.types.path;
-        description = ''
+        description = lib.mdDoc ''
           A file containing the various secrets. Should be in the format expected by systemd's
-          <literal>EnvironmentFile</literal> directory. Two secrets are currently available:
-          <literal>SYNC_MASTER_SECRET</literal> and
-          <literal>SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET</literal>.
+          `EnvironmentFile` directory. Two secrets are currently available:
+          `SYNC_MASTER_SECRET` and
+          `SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET`.
         '';
       };
 
       singleNode = {
-        enable = lib.mkEnableOption "auto-configuration for a simple single-node setup";
+        enable = lib.mkEnableOption (lib.mdDoc "auto-configuration for a simple single-node setup");
 
-        enableTLS = lib.mkEnableOption "automatic TLS setup";
+        enableTLS = lib.mkEnableOption (lib.mdDoc "automatic TLS setup");
 
-        enableNginx = lib.mkEnableOption "nginx virtualhost definitions";
+        enableNginx = lib.mkEnableOption (lib.mdDoc "nginx virtualhost definitions");
 
         hostname = lib.mkOption {
           type = lib.types.str;
-          description = ''
+          description = lib.mdDoc ''
             Host name to use for this service.
           '';
         };
@@ -134,7 +177,7 @@ in
         capacity = lib.mkOption {
           type = lib.types.ints.unsigned;
           default = 10;
-          description = ''
+          description = lib.mdDoc ''
             How many sync accounts are allowed on this server. Setting this value
             equal to or less than the number of currently active accounts will
             effectively deny service to accounts not yet registered here.
@@ -147,7 +190,7 @@ in
           defaultText = lib.literalExpression ''
             ''${if cfg.singleNode.enableTLS then "https" else "http"}://''${config.${opt.singleNode.hostname}}
           '';
-          description = ''
+          description = lib.mdDoc ''
             URL of the host. If you are not using the automatic webserver proxy setup you will have
             to change this setting or your sync server may not be functional.
           '';
@@ -162,7 +205,7 @@ in
             port = lib.mkOption {
               type = lib.types.port;
               default = 5000;
-              description = ''
+              description = lib.mdDoc ''
                 Port to bind to.
               '';
             };
@@ -170,21 +213,21 @@ in
             tokenserver.enabled = lib.mkOption {
               type = lib.types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to enable the token service as well.
               '';
             };
           };
         };
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           Settings for the sync server. These take priority over values computed
           from NixOS options.
 
-          See the doc comments on the <literal>Settings</literal> structs in
-          <link xlink:href="https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/settings.rs" />
+          See the doc comments on the `Settings` structs in
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/settings.rs>
           and
-          <link xlink:href="https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/tokenserver/settings.rs" />
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/tokenserver/settings.rs>
           for available options.
         '';
       };
@@ -207,12 +250,12 @@ in
       wantedBy = [ "multi-user.target" ];
       requires = lib.mkIf dbIsLocal [ "mysql.service" ];
       after = lib.mkIf dbIsLocal [ "mysql.service" ];
+      restartTriggers = lib.optional cfg.singleNode.enable setupScript;
       environment.RUST_LOG = cfg.logLevel;
       serviceConfig = {
         User = defaultUser;
         Group = defaultUser;
-        ExecStart = "${cfg.package}/bin/syncstorage --config ${configFile}";
-        Stderr = "journal";
+        ExecStart = "${cfg.package}/bin/syncserver --config ${configFile}";
         EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
 
         # hardening
@@ -252,56 +295,7 @@ in
       requires = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
       after = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
       path = [ config.services.mysql.package ];
-      script = ''
-        set -euo pipefail
-        shopt -s inherit_errexit
-
-        schema_configured() {
-          mysql ${cfg.database.name} -Ne 'SHOW TABLES' | grep -q services
-        }
-
-        services_configured() {
-          [ 1 != $(mysql ${cfg.database.name} -Ne 'SELECT COUNT(*) < 1 FROM `services`') ]
-        }
-
-        create_services() {
-          mysql ${cfg.database.name} <<"EOF"
-            BEGIN;
-
-            INSERT INTO `services` (`id`, `service`, `pattern`)
-              VALUES (1, 'sync-1.5', '{node}/1.5/{uid}');
-            INSERT INTO `nodes` (`id`, `service`, `node`, `available`, `current_load`,
-                                 `capacity`, `downed`, `backoff`)
-              VALUES (1, 1, '${cfg.singleNode.url}', ${toString cfg.singleNode.capacity},
-                      0, ${toString cfg.singleNode.capacity}, 0, 0);
-
-            COMMIT;
-        EOF
-        }
-
-        update_nodes() {
-          mysql ${cfg.database.name} <<"EOF"
-            UPDATE `nodes`
-              SET `capacity` = ${toString cfg.singleNode.capacity}
-              WHERE `id` = 1;
-        EOF
-        }
-
-        for (( try = 0; try < 60; try++ )); do
-          if ! schema_configured; then
-            sleep 2
-          elif services_configured; then
-            update_nodes
-            exit 0
-          else
-            create_services
-            exit 0
-          fi
-        done
-
-        echo "Single-node setup failed"
-        exit 1
-      '';
+      serviceConfig.ExecStart = [ "${setupScript}" ];
     };
 
     services.nginx.virtualHosts = lib.mkIf cfg.singleNode.enableNginx {
@@ -309,11 +303,11 @@ in
         enableACME = cfg.singleNode.enableTLS;
         forceSSL = cfg.singleNode.enableTLS;
         locations."/" = {
-          proxyPass = "http://localhost:${toString cfg.settings.port}";
-          # source mentions that this header should be set
-          extraConfig = ''
-            add_header X-Content-Type-Options nosniff;
-          '';
+          proxyPass = "http://127.0.0.1:${toString cfg.settings.port}";
+          # We need to pass the Host header that matches the original Host header. Otherwise,
+          # Hawk authentication will fail (because it assumes that the client and server see
+          # the same value of the Host header).
+          recommendedProxySettings = true;
         };
       };
     };
@@ -321,8 +315,6 @@ in
 
   meta = {
     maintainers = with lib.maintainers; [ pennae ];
-    # Don't edit the docbook xml directly, edit the md and generate it:
-    # `pandoc firefox-syncserver.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > firefox-syncserver.xml`
-    doc = ./firefox-syncserver.xml;
+    doc = ./firefox-syncserver.md;
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/firefox-syncserver.xml b/nixpkgs/nixos/modules/services/networking/firefox-syncserver.xml
deleted file mode 100644
index 66c812266951..000000000000
--- a/nixpkgs/nixos/modules/services/networking/firefox-syncserver.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-firefox-syncserver">
-  <title>Firefox Sync server</title>
-  <para>
-    A storage server for Firefox Sync that you can easily host yourself.
-  </para>
-  <section xml:id="module-services-firefox-syncserver-quickstart">
-    <title>Quickstart</title>
-    <para>
-      The absolute minimal configuration for the sync server looks like
-      this:
-    </para>
-    <programlisting language="nix">
-services.mysql.package = pkgs.mariadb;
-
-services.firefox-syncserver = {
-  enable = true;
-  secrets = builtins.toFile &quot;sync-secrets&quot; ''
-    SYNC_MASTER_SECRET=this-secret-is-actually-leaked-to-/nix/store
-  '';
-  singleNode = {
-    enable = true;
-    hostname = &quot;localhost&quot;;
-    url = &quot;http://localhost:5000&quot;;
-  };
-};
-</programlisting>
-    <para>
-      This will start a sync server that is only accessible locally.
-      Once the services is running you can navigate to
-      <literal>about:config</literal> in your Firefox profile and set
-      <literal>identity.sync.tokenserver.uri</literal> to
-      <literal>http://localhost:5000/1.0/sync/1.5</literal>. Your
-      browser will now use your local sync server for data storage.
-    </para>
-    <warning>
-      <para>
-        This configuration should never be used in production. It is not
-        encrypted and stores its secrets in a world-readable location.
-      </para>
-    </warning>
-  </section>
-  <section xml:id="module-services-firefox-syncserver-configuration">
-    <title>More detailed setup</title>
-    <para>
-      The <literal>firefox-syncserver</literal> service provides a
-      number of options to make setting up small deployment easier.
-      These are grouped under the <literal>singleNode</literal> element
-      of the option tree and allow simple configuration of the most
-      important parameters.
-    </para>
-    <para>
-      Single node setup is split into two kinds of options: those that
-      affect the sync server itself, and those that affect its
-      surroundings. Options that affect the sync server are
-      <literal>capacity</literal>, which configures how many accounts
-      may be active on this instance, and <literal>url</literal>, which
-      holds the URL under which the sync server can be accessed. The
-      <literal>url</literal> can be configured automatically when using
-      nginx.
-    </para>
-    <para>
-      Options that affect the surroundings of the sync server are
-      <literal>enableNginx</literal>, <literal>enableTLS</literal> and
-      <literal>hostnam</literal>. If <literal>enableNginx</literal> is
-      set the sync server module will automatically add an nginx virtual
-      host to the system using <literal>hostname</literal> as the domain
-      and set <literal>url</literal> accordingly. If
-      <literal>enableTLS</literal> is set the module will also enable
-      ACME certificates on the new virtual host and force all
-      connections to be made via TLS.
-    </para>
-    <para>
-      For actual deployment it is also recommended to store the
-      <literal>secrets</literal> file in a secure location.
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/networking/fireqos.nix b/nixpkgs/nixos/modules/services/networking/fireqos.nix
index 5469bce58c60..b7f51a89c0e1 100644
--- a/nixpkgs/nixos/modules/services/networking/fireqos.nix
+++ b/nixpkgs/nixos/modules/services/networking/fireqos.nix
@@ -10,7 +10,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If enabled, FireQOS will be launched with the specified
         configuration given in `config`.
       '';
diff --git a/nixpkgs/nixos/modules/services/networking/firewall-iptables.nix b/nixpkgs/nixos/modules/services/networking/firewall-iptables.nix
new file mode 100644
index 000000000000..63e952194d67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/firewall-iptables.nix
@@ -0,0 +1,334 @@
+/* This module enables a simple firewall.
+
+   The firewall can be customised in arbitrary ways by setting
+   ‘networking.firewall.extraCommands’.  For modularity, the firewall
+   uses several chains:
+
+   - ‘nixos-fw’ is the main chain for input packet processing.
+
+   - ‘nixos-fw-accept’ is called for accepted packets.  If you want
+   additional logging, or want to reject certain packets anyway, you
+   can insert rules at the start of this chain.
+
+   - ‘nixos-fw-log-refuse’ and ‘nixos-fw-refuse’ are called for
+   refused packets.  (The former jumps to the latter after logging
+   the packet.)  If you want additional logging, or want to accept
+   certain packets anyway, you can insert rules at the start of
+   this chain.
+
+   - ‘nixos-fw-rpfilter’ is used as the main chain in the mangle table,
+   called from the built-in ‘PREROUTING’ chain.  If the kernel
+   supports it and `cfg.checkReversePath` is set this chain will
+   perform a reverse path filter test.
+
+   - ‘nixos-drop’ is used while reloading the firewall in order to drop
+   all traffic.  Since reloading isn't implemented in an atomic way
+   this'll prevent any traffic from leaking through while reloading
+   the firewall.  However, if the reloading fails, the ‘firewall-stop’
+   script will be called which in return will effectively disable the
+   complete firewall (in the default configuration).
+
+*/
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.firewall;
+
+  inherit (config.boot.kernelPackages) kernel;
+
+  kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
+
+  helpers = import ./helpers.nix { inherit config lib; };
+
+  writeShScript = name: text:
+    let
+      dir = pkgs.writeScriptBin name ''
+        #! ${pkgs.runtimeShell} -e
+        ${text}
+      '';
+    in
+    "${dir}/bin/${name}";
+
+  startScript = writeShScript "firewall-start" ''
+    ${helpers}
+
+    # Flush the old firewall rules.  !!! Ideally, updating the
+    # firewall would be atomic.  Apparently that's possible
+    # with iptables-restore.
+    ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
+    for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse; do
+      ip46tables -F "$chain" 2> /dev/null || true
+      ip46tables -X "$chain" 2> /dev/null || true
+    done
+
+
+    # The "nixos-fw-accept" chain just accepts packets.
+    ip46tables -N nixos-fw-accept
+    ip46tables -A nixos-fw-accept -j ACCEPT
+
+
+    # The "nixos-fw-refuse" chain rejects or drops packets.
+    ip46tables -N nixos-fw-refuse
+
+    ${if cfg.rejectPackets then ''
+      # Send a reset for existing TCP connections that we've
+      # somehow forgotten about.  Send ICMP "port unreachable"
+      # for everything else.
+      ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
+      ip46tables -A nixos-fw-refuse -j REJECT
+    '' else ''
+      ip46tables -A nixos-fw-refuse -j DROP
+    ''}
+
+
+    # The "nixos-fw-log-refuse" chain performs logging, then
+    # jumps to the "nixos-fw-refuse" chain.
+    ip46tables -N nixos-fw-log-refuse
+
+    ${optionalString cfg.logRefusedConnections ''
+      ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "refused connection: "
+    ''}
+    ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
+      ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
+        -j LOG --log-level info --log-prefix "refused broadcast: "
+      ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
+        -j LOG --log-level info --log-prefix "refused multicast: "
+    ''}
+    ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
+    ${optionalString cfg.logRefusedPackets ''
+      ip46tables -A nixos-fw-log-refuse \
+        -j LOG --log-level info --log-prefix "refused packet: "
+    ''}
+    ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
+
+
+    # The "nixos-fw" chain does the actual work.
+    ip46tables -N nixos-fw
+
+    # Clean up rpfilter rules
+    ip46tables -t mangle -D PREROUTING -j nixos-fw-rpfilter 2> /dev/null || true
+    ip46tables -t mangle -F nixos-fw-rpfilter 2> /dev/null || true
+    ip46tables -t mangle -X nixos-fw-rpfilter 2> /dev/null || true
+
+    ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
+      # Perform a reverse-path test to refuse spoofers
+      # For now, we just drop, as the mangle table doesn't have a log-refuse yet
+      ip46tables -t mangle -N nixos-fw-rpfilter 2> /dev/null || true
+      ip46tables -t mangle -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN
+
+      # Allows this host to act as a DHCP4 client without first having to use APIPA
+      iptables -t mangle -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
+
+      # Allows this host to act as a DHCPv4 server
+      iptables -t mangle -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
+
+      ${optionalString cfg.logReversePathDrops ''
+        ip46tables -t mangle -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
+      ''}
+      ip46tables -t mangle -A nixos-fw-rpfilter -j DROP
+
+      ip46tables -t mangle -A PREROUTING -j nixos-fw-rpfilter
+    ''}
+
+    # Accept all traffic on the trusted interfaces.
+    ${flip concatMapStrings cfg.trustedInterfaces (iface: ''
+      ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
+    '')}
+
+    # Accept packets from established or related connections.
+    ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
+
+    # Accept connections to the allowed TCP ports.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (port:
+        ''
+          ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedTCPPorts
+    ) cfg.allInterfaces)}
+
+    # Accept connections to the allowed TCP port ranges.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (rangeAttr:
+        let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
+        ''
+          ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedTCPPortRanges
+    ) cfg.allInterfaces)}
+
+    # Accept packets on the allowed UDP ports.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (port:
+        ''
+          ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedUDPPorts
+    ) cfg.allInterfaces)}
+
+    # Accept packets on the allowed UDP port ranges.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (rangeAttr:
+        let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
+        ''
+          ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedUDPPortRanges
+    ) cfg.allInterfaces)}
+
+    # Optionally respond to ICMPv4 pings.
+    ${optionalString cfg.allowPing ''
+      iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
+        "-m limit ${cfg.pingLimit} "
+      }-j nixos-fw-accept
+    ''}
+
+    ${optionalString config.networking.enableIPv6 ''
+      # Accept all ICMPv6 messages except redirects and node
+      # information queries (type 139).  See RFC 4890, section
+      # 4.4.
+      ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
+      ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
+      ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
+
+      # Allow this host to act as a DHCPv6 client
+      ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
+    ''}
+
+    ${cfg.extraCommands}
+
+    # Reject/drop everything else.
+    ip46tables -A nixos-fw -j nixos-fw-log-refuse
+
+
+    # Enable the firewall.
+    ip46tables -A INPUT -j nixos-fw
+  '';
+
+  stopScript = writeShScript "firewall-stop" ''
+    ${helpers}
+
+    # Clean up in case reload fails
+    ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+
+    # Clean up after added ruleset
+    ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
+
+    ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
+      ip46tables -t mangle -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
+    ''}
+
+    ${cfg.extraStopCommands}
+  '';
+
+  reloadScript = writeShScript "firewall-reload" ''
+    ${helpers}
+
+    # Create a unique drop rule
+    ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+    ip46tables -F nixos-drop 2>/dev/null || true
+    ip46tables -X nixos-drop 2>/dev/null || true
+    ip46tables -N nixos-drop
+    ip46tables -A nixos-drop -j DROP
+
+    # Don't allow traffic to leak out until the script has completed
+    ip46tables -A INPUT -j nixos-drop
+
+    ${cfg.extraStopCommands}
+
+    if ${startScript}; then
+      ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+    else
+      echo "Failed to reload firewall... Stopping"
+      ${stopScript}
+      exit 1
+    fi
+  '';
+
+in
+
+{
+
+  options = {
+
+    networking.firewall = {
+      extraCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example = "iptables -A INPUT -p icmp -j ACCEPT";
+        description = lib.mdDoc ''
+          Additional shell commands executed as part of the firewall
+          initialisation script.  These are executed just before the
+          final "reject" firewall rule is added, so they can be used
+          to allow packets that would otherwise be refused.
+
+          This option only works with the iptables based firewall.
+        '';
+      };
+
+      extraStopCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example = "iptables -P INPUT ACCEPT";
+        description = lib.mdDoc ''
+          Additional shell commands executed as part of the firewall
+          shutdown script.  These are executed just after the removal
+          of the NixOS input rule, or if the service enters a failed
+          state.
+
+          This option only works with the iptables based firewall.
+        '';
+      };
+    };
+
+  };
+
+  # FIXME: Maybe if `enable' is false, the firewall should still be
+  # built but not started by default?
+  config = mkIf (cfg.enable && config.networking.nftables.enable == false) {
+
+    assertions = [
+      # This is approximately "checkReversePath -> kernelHasRPFilter",
+      # but the checkReversePath option can include non-boolean
+      # values.
+      {
+        assertion = cfg.checkReversePath == false || kernelHasRPFilter;
+        message = "This kernel does not support rpfilter";
+      }
+    ];
+
+    networking.firewall.checkReversePath = mkIf (!kernelHasRPFilter) (mkDefault false);
+
+    systemd.services.firewall = {
+      description = "Firewall";
+      wantedBy = [ "sysinit.target" ];
+      wants = [ "network-pre.target" ];
+      before = [ "network-pre.target" ];
+      after = [ "systemd-modules-load.service" ];
+
+      path = [ cfg.package ] ++ cfg.extraPackages;
+
+      # FIXME: this module may also try to load kernel modules, but
+      # containers don't have CAP_SYS_MODULE.  So the host system had
+      # better have all necessary modules already loaded.
+      unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+      unitConfig.DefaultDependencies = false;
+
+      reloadIfChanged = true;
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "@${startScript} firewall-start";
+        ExecReload = "@${reloadScript} firewall-reload";
+        ExecStop = "@${stopScript} firewall-stop";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/firewall-nftables.nix b/nixpkgs/nixos/modules/services/networking/firewall-nftables.nix
new file mode 100644
index 000000000000..452dd97d89d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/firewall-nftables.nix
@@ -0,0 +1,179 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.firewall;
+
+  ifaceSet = concatStringsSep ", " (
+    map (x: ''"${x}"'') cfg.trustedInterfaces
+  );
+
+  portsToNftSet = ports: portRanges: concatStringsSep ", " (
+    map (x: toString x) ports
+    ++ map (x: "${toString x.from}-${toString x.to}") portRanges
+  );
+
+in
+
+{
+
+  options = {
+
+    networking.firewall = {
+      extraInputRules = mkOption {
+        type = types.lines;
+        default = "";
+        example = "ip6 saddr { fc00::/7, fe80::/10 } tcp dport 24800 accept";
+        description = lib.mdDoc ''
+          Additional nftables rules to be appended to the input-allow
+          chain.
+
+          This option only works with the nftables based firewall.
+        '';
+      };
+
+      extraForwardRules = mkOption {
+        type = types.lines;
+        default = "";
+        example = "iifname wg0 accept";
+        description = lib.mdDoc ''
+          Additional nftables rules to be appended to the forward-allow
+          chain.
+
+          This option only works with the nftables based firewall.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf (cfg.enable && config.networking.nftables.enable) {
+
+    assertions = [
+      {
+        assertion = cfg.extraCommands == "";
+        message = "extraCommands is incompatible with the nftables based firewall: ${cfg.extraCommands}";
+      }
+      {
+        assertion = cfg.extraStopCommands == "";
+        message = "extraStopCommands is incompatible with the nftables based firewall: ${cfg.extraStopCommands}";
+      }
+      {
+        assertion = cfg.pingLimit == null || !(hasPrefix "--" cfg.pingLimit);
+        message = "nftables syntax like \"2/second\" should be used in networking.firewall.pingLimit";
+      }
+      {
+        assertion = config.networking.nftables.rulesetFile == null;
+        message = "networking.nftables.rulesetFile conflicts with the firewall";
+      }
+    ];
+
+    networking.nftables.ruleset = ''
+
+      table inet nixos-fw {
+
+        ${optionalString (cfg.checkReversePath != false) ''
+          chain rpfilter {
+            type filter hook prerouting priority mangle + 10; policy drop;
+
+            meta nfproto ipv4 udp sport . udp dport { 67 . 68, 68 . 67 } accept comment "DHCPv4 client/server"
+            fib saddr . mark ${optionalString (cfg.checkReversePath != "loose") ". iif"} oif exists accept
+
+            ${optionalString cfg.logReversePathDrops ''
+              log level info prefix "rpfilter drop: "
+            ''}
+
+          }
+        ''}
+
+        chain input {
+          type filter hook input priority filter; policy drop;
+
+          ${optionalString (ifaceSet != "") ''iifname { ${ifaceSet} } accept comment "trusted interfaces"''}
+
+          # Some ICMPv6 types like NDP is untracked
+          ct state vmap {
+            invalid : drop,
+            established : accept,
+            related : accept,
+            new : jump input-allow,
+            untracked: jump input-allow,
+          }
+
+          ${optionalString cfg.logRefusedConnections ''
+            tcp flags syn / fin,syn,rst,ack log level info prefix "refused connection: "
+          ''}
+          ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
+            pkttype broadcast log level info prefix "refused broadcast: "
+            pkttype multicast log level info prefix "refused multicast: "
+          ''}
+          ${optionalString cfg.logRefusedPackets ''
+            pkttype host log level info prefix "refused packet: "
+          ''}
+
+          ${optionalString cfg.rejectPackets ''
+            meta l4proto tcp reject with tcp reset
+            reject
+          ''}
+
+        }
+
+        chain input-allow {
+
+          ${concatStrings (mapAttrsToList (iface: cfg:
+            let
+              ifaceExpr = optionalString (iface != "default") "iifname ${iface}";
+              tcpSet = portsToNftSet cfg.allowedTCPPorts cfg.allowedTCPPortRanges;
+              udpSet = portsToNftSet cfg.allowedUDPPorts cfg.allowedUDPPortRanges;
+            in
+            ''
+              ${optionalString (tcpSet != "") "${ifaceExpr} tcp dport { ${tcpSet} } accept"}
+              ${optionalString (udpSet != "") "${ifaceExpr} udp dport { ${udpSet} } accept"}
+            ''
+          ) cfg.allInterfaces)}
+
+          ${optionalString cfg.allowPing ''
+            icmp type echo-request ${optionalString (cfg.pingLimit != null) "limit rate ${cfg.pingLimit}"} accept comment "allow ping"
+          ''}
+
+          icmpv6 type != { nd-redirect, 139 } accept comment "Accept all ICMPv6 messages except redirects and node information queries (type 139).  See RFC 4890, section 4.4."
+          ip6 daddr fe80::/64 udp dport 546 accept comment "DHCPv6 client"
+
+          ${cfg.extraInputRules}
+
+        }
+
+        ${optionalString cfg.filterForward ''
+          chain forward {
+            type filter hook forward priority filter; policy drop;
+
+            ct state vmap {
+              invalid : drop,
+              established : accept,
+              related : accept,
+              new : jump forward-allow,
+              untracked : jump forward-allow,
+            }
+
+          }
+
+          chain forward-allow {
+
+            icmpv6 type != { router-renumbering, 139 } accept comment "Accept all ICMPv6 messages except renumbering and node information queries (type 139).  See RFC 4890, section 4.3."
+
+            ct status dnat accept comment "allow port forward"
+
+            ${cfg.extraForwardRules}
+
+          }
+        ''}
+
+      }
+
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/firewall.nix b/nixpkgs/nixos/modules/services/networking/firewall.nix
index 48cb83e344ec..ac02a93836b8 100644
--- a/nixpkgs/nixos/modules/services/networking/firewall.nix
+++ b/nixpkgs/nixos/modules/services/networking/firewall.nix
@@ -1,35 +1,3 @@
-/* This module enables a simple firewall.
-
-   The firewall can be customised in arbitrary ways by setting
-   ‘networking.firewall.extraCommands’.  For modularity, the firewall
-   uses several chains:
-
-   - ‘nixos-fw’ is the main chain for input packet processing.
-
-   - ‘nixos-fw-accept’ is called for accepted packets.  If you want
-     additional logging, or want to reject certain packets anyway, you
-     can insert rules at the start of this chain.
-
-   - ‘nixos-fw-log-refuse’ and ‘nixos-fw-refuse’ are called for
-     refused packets.  (The former jumps to the latter after logging
-     the packet.)  If you want additional logging, or want to accept
-     certain packets anyway, you can insert rules at the start of
-     this chain.
-
-   - ‘nixos-fw-rpfilter’ is used as the main chain in the raw table,
-     called from the built-in ‘PREROUTING’ chain.  If the kernel
-     supports it and `cfg.checkReversePath` is set this chain will
-     perform a reverse path filter test.
-
-   - ‘nixos-drop’ is used while reloading the firewall in order to drop
-     all traffic.  Since reloading isn't implemented in an atomic way
-     this'll prevent any traffic from leaking through while reloading
-     the firewall.  However, if the reloading fails, the ‘firewall-stop’
-     script will be called which in return will effectively disable the
-     complete firewall (in the default configuration).
-
-*/
-
 { config, lib, pkgs, ... }:
 
 with lib;
@@ -38,216 +6,6 @@ let
 
   cfg = config.networking.firewall;
 
-  inherit (config.boot.kernelPackages) kernel;
-
-  kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
-
-  helpers = import ./helpers.nix { inherit config lib; };
-
-  writeShScript = name: text: let dir = pkgs.writeScriptBin name ''
-    #! ${pkgs.runtimeShell} -e
-    ${text}
-  ''; in "${dir}/bin/${name}";
-
-  defaultInterface = { default = mapAttrs (name: value: cfg.${name}) commonOptions; };
-  allInterfaces = defaultInterface // cfg.interfaces;
-
-  startScript = writeShScript "firewall-start" ''
-    ${helpers}
-
-    # Flush the old firewall rules.  !!! Ideally, updating the
-    # firewall would be atomic.  Apparently that's possible
-    # with iptables-restore.
-    ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
-    for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse; do
-      ip46tables -F "$chain" 2> /dev/null || true
-      ip46tables -X "$chain" 2> /dev/null || true
-    done
-
-
-    # The "nixos-fw-accept" chain just accepts packets.
-    ip46tables -N nixos-fw-accept
-    ip46tables -A nixos-fw-accept -j ACCEPT
-
-
-    # The "nixos-fw-refuse" chain rejects or drops packets.
-    ip46tables -N nixos-fw-refuse
-
-    ${if cfg.rejectPackets then ''
-      # Send a reset for existing TCP connections that we've
-      # somehow forgotten about.  Send ICMP "port unreachable"
-      # for everything else.
-      ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
-      ip46tables -A nixos-fw-refuse -j REJECT
-    '' else ''
-      ip46tables -A nixos-fw-refuse -j DROP
-    ''}
-
-
-    # The "nixos-fw-log-refuse" chain performs logging, then
-    # jumps to the "nixos-fw-refuse" chain.
-    ip46tables -N nixos-fw-log-refuse
-
-    ${optionalString cfg.logRefusedConnections ''
-      ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "refused connection: "
-    ''}
-    ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
-      ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
-        -j LOG --log-level info --log-prefix "refused broadcast: "
-      ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
-        -j LOG --log-level info --log-prefix "refused multicast: "
-    ''}
-    ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
-    ${optionalString cfg.logRefusedPackets ''
-      ip46tables -A nixos-fw-log-refuse \
-        -j LOG --log-level info --log-prefix "refused packet: "
-    ''}
-    ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
-
-
-    # The "nixos-fw" chain does the actual work.
-    ip46tables -N nixos-fw
-
-    # Clean up rpfilter rules
-    ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2> /dev/null || true
-    ip46tables -t raw -F nixos-fw-rpfilter 2> /dev/null || true
-    ip46tables -t raw -X nixos-fw-rpfilter 2> /dev/null || true
-
-    ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
-      # Perform a reverse-path test to refuse spoofers
-      # For now, we just drop, as the raw table doesn't have a log-refuse yet
-      ip46tables -t raw -N nixos-fw-rpfilter 2> /dev/null || true
-      ip46tables -t raw -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN
-
-      # Allows this host to act as a DHCP4 client without first having to use APIPA
-      iptables -t raw -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
-
-      # Allows this host to act as a DHCPv4 server
-      iptables -t raw -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
-
-      ${optionalString cfg.logReversePathDrops ''
-        ip46tables -t raw -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
-      ''}
-      ip46tables -t raw -A nixos-fw-rpfilter -j DROP
-
-      ip46tables -t raw -A PREROUTING -j nixos-fw-rpfilter
-    ''}
-
-    # Accept all traffic on the trusted interfaces.
-    ${flip concatMapStrings cfg.trustedInterfaces (iface: ''
-      ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
-    '')}
-
-    # Accept packets from established or related connections.
-    ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
-
-    # Accept connections to the allowed TCP ports.
-    ${concatStrings (mapAttrsToList (iface: cfg:
-      concatMapStrings (port:
-        ''
-          ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
-        ''
-      ) cfg.allowedTCPPorts
-    ) allInterfaces)}
-
-    # Accept connections to the allowed TCP port ranges.
-    ${concatStrings (mapAttrsToList (iface: cfg:
-      concatMapStrings (rangeAttr:
-        let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
-        ''
-          ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
-        ''
-      ) cfg.allowedTCPPortRanges
-    ) allInterfaces)}
-
-    # Accept packets on the allowed UDP ports.
-    ${concatStrings (mapAttrsToList (iface: cfg:
-      concatMapStrings (port:
-        ''
-          ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
-        ''
-      ) cfg.allowedUDPPorts
-    ) allInterfaces)}
-
-    # Accept packets on the allowed UDP port ranges.
-    ${concatStrings (mapAttrsToList (iface: cfg:
-      concatMapStrings (rangeAttr:
-        let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
-        ''
-          ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
-        ''
-      ) cfg.allowedUDPPortRanges
-    ) allInterfaces)}
-
-    # Optionally respond to ICMPv4 pings.
-    ${optionalString cfg.allowPing ''
-      iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
-        "-m limit ${cfg.pingLimit} "
-      }-j nixos-fw-accept
-    ''}
-
-    ${optionalString config.networking.enableIPv6 ''
-      # Accept all ICMPv6 messages except redirects and node
-      # information queries (type 139).  See RFC 4890, section
-      # 4.4.
-      ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
-      ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
-      ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
-
-      # Allow this host to act as a DHCPv6 client
-      ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
-    ''}
-
-    ${cfg.extraCommands}
-
-    # Reject/drop everything else.
-    ip46tables -A nixos-fw -j nixos-fw-log-refuse
-
-
-    # Enable the firewall.
-    ip46tables -A INPUT -j nixos-fw
-  '';
-
-  stopScript = writeShScript "firewall-stop" ''
-    ${helpers}
-
-    # Clean up in case reload fails
-    ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
-
-    # Clean up after added ruleset
-    ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
-
-    ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
-      ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
-    ''}
-
-    ${cfg.extraStopCommands}
-  '';
-
-  reloadScript = writeShScript "firewall-reload" ''
-    ${helpers}
-
-    # Create a unique drop rule
-    ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
-    ip46tables -F nixos-drop 2>/dev/null || true
-    ip46tables -X nixos-drop 2>/dev/null || true
-    ip46tables -N nixos-drop
-    ip46tables -A nixos-drop -j DROP
-
-    # Don't allow traffic to leak out until the script has completed
-    ip46tables -A INPUT -j nixos-drop
-
-    ${cfg.extraStopCommands}
-
-    if ${startScript}; then
-      ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
-    else
-      echo "Failed to reload firewall... Stopping"
-      ${stopScript}
-      exit 1
-    fi
-  '';
-
   canonicalizePortList =
     ports: lib.unique (builtins.sort builtins.lessThan ports);
 
@@ -257,22 +15,20 @@ let
       default = [ ];
       apply = canonicalizePortList;
       example = [ 22 80 ];
-      description =
-        lib.mdDoc ''
-          List of TCP ports on which incoming connections are
-          accepted.
-        '';
+      description = lib.mdDoc ''
+        List of TCP ports on which incoming connections are
+        accepted.
+      '';
     };
 
     allowedTCPPortRanges = mkOption {
       type = types.listOf (types.attrsOf types.port);
       default = [ ];
-      example = [ { from = 8999; to = 9003; } ];
-      description =
-        lib.mdDoc ''
-          A range of TCP ports on which incoming connections are
-          accepted.
-        '';
+      example = [{ from = 8999; to = 9003; }];
+      description = lib.mdDoc ''
+        A range of TCP ports on which incoming connections are
+        accepted.
+      '';
     };
 
     allowedUDPPorts = mkOption {
@@ -280,20 +36,18 @@ let
       default = [ ];
       apply = canonicalizePortList;
       example = [ 53 ];
-      description =
-        lib.mdDoc ''
-          List of open UDP ports.
-        '';
+      description = lib.mdDoc ''
+        List of open UDP ports.
+      '';
     };
 
     allowedUDPPortRanges = mkOption {
       type = types.listOf (types.attrsOf types.port);
       default = [ ];
-      example = [ { from = 60000; to = 61000; } ];
-      description =
-        lib.mdDoc ''
-          Range of open UDP ports.
-        '';
+      example = [{ from = 60000; to = 61000; }];
+      description = lib.mdDoc ''
+        Range of open UDP ports.
+      '';
     };
   };
 
@@ -301,240 +55,226 @@ in
 
 {
 
-  ###### interface
-
   options = {
 
     networking.firewall = {
       enable = mkOption {
         type = types.bool;
         default = true;
-        description =
-          lib.mdDoc ''
-            Whether to enable the firewall.  This is a simple stateful
-            firewall that blocks connection attempts to unauthorised TCP
-            or UDP ports on this machine.  It does not affect packet
-            forwarding.
-          '';
+        description = lib.mdDoc ''
+          Whether to enable the firewall.  This is a simple stateful
+          firewall that blocks connection attempts to unauthorised TCP
+          or UDP ports on this machine.
+        '';
       };
 
       package = mkOption {
         type = types.package;
-        default = pkgs.iptables;
-        defaultText = literalExpression "pkgs.iptables";
+        default = if config.networking.nftables.enable then pkgs.nftables else pkgs.iptables;
+        defaultText = literalExpression ''if config.networking.nftables.enable then "pkgs.nftables" else "pkgs.iptables"'';
         example = literalExpression "pkgs.iptables-legacy";
-        description =
-          lib.mdDoc ''
-            The iptables package to use for running the firewall service."
-          '';
+        description = lib.mdDoc ''
+          The package to use for running the firewall service.
+        '';
       };
 
       logRefusedConnections = mkOption {
         type = types.bool;
         default = true;
-        description =
-          lib.mdDoc ''
-            Whether to log rejected or dropped incoming connections.
-            Note: The logs are found in the kernel logs, i.e. dmesg
-            or journalctl -k.
-          '';
+        description = lib.mdDoc ''
+          Whether to log rejected or dropped incoming connections.
+          Note: The logs are found in the kernel logs, i.e. dmesg
+          or journalctl -k.
+        '';
       };
 
       logRefusedPackets = mkOption {
         type = types.bool;
         default = false;
-        description =
-          lib.mdDoc ''
-            Whether to log all rejected or dropped incoming packets.
-            This tends to give a lot of log messages, so it's mostly
-            useful for debugging.
-            Note: The logs are found in the kernel logs, i.e. dmesg
-            or journalctl -k.
-          '';
+        description = lib.mdDoc ''
+          Whether to log all rejected or dropped incoming packets.
+          This tends to give a lot of log messages, so it's mostly
+          useful for debugging.
+          Note: The logs are found in the kernel logs, i.e. dmesg
+          or journalctl -k.
+        '';
       };
 
       logRefusedUnicastsOnly = mkOption {
         type = types.bool;
         default = true;
-        description =
-          lib.mdDoc ''
-            If {option}`networking.firewall.logRefusedPackets`
-            and this option are enabled, then only log packets
-            specifically directed at this machine, i.e., not broadcasts
-            or multicasts.
-          '';
+        description = lib.mdDoc ''
+          If {option}`networking.firewall.logRefusedPackets`
+          and this option are enabled, then only log packets
+          specifically directed at this machine, i.e., not broadcasts
+          or multicasts.
+        '';
       };
 
       rejectPackets = mkOption {
         type = types.bool;
         default = false;
-        description =
-          lib.mdDoc ''
-            If set, refused packets are rejected rather than dropped
-            (ignored).  This means that an ICMP "port unreachable" error
-            message is sent back to the client (or a TCP RST packet in
-            case of an existing connection).  Rejecting packets makes
-            port scanning somewhat easier.
-          '';
+        description = lib.mdDoc ''
+          If set, refused packets are rejected rather than dropped
+          (ignored).  This means that an ICMP "port unreachable" error
+          message is sent back to the client (or a TCP RST packet in
+          case of an existing connection).  Rejecting packets makes
+          port scanning somewhat easier.
+        '';
       };
 
       trustedInterfaces = mkOption {
         type = types.listOf types.str;
         default = [ ];
         example = [ "enp0s2" ];
-        description =
-          lib.mdDoc ''
-            Traffic coming in from these interfaces will be accepted
-            unconditionally.  Traffic from the loopback (lo) interface
-            will always be accepted.
-          '';
+        description = lib.mdDoc ''
+          Traffic coming in from these interfaces will be accepted
+          unconditionally.  Traffic from the loopback (lo) interface
+          will always be accepted.
+        '';
       };
 
       allowPing = mkOption {
         type = types.bool;
         default = true;
-        description =
-          lib.mdDoc ''
-            Whether to respond to incoming ICMPv4 echo requests
-            ("pings").  ICMPv6 pings are always allowed because the
-            larger address space of IPv6 makes network scanning much
-            less effective.
-          '';
+        description = lib.mdDoc ''
+          Whether to respond to incoming ICMPv4 echo requests
+          ("pings").  ICMPv6 pings are always allowed because the
+          larger address space of IPv6 makes network scanning much
+          less effective.
+        '';
       };
 
       pingLimit = mkOption {
         type = types.nullOr (types.separatedString " ");
         default = null;
         example = "--limit 1/minute --limit-burst 5";
-        description =
-          lib.mdDoc ''
-            If pings are allowed, this allows setting rate limits
-            on them.  If non-null, this option should be in the form of
-            flags like "--limit 1/minute --limit-burst 5"
-          '';
+        description = lib.mdDoc ''
+          If pings are allowed, this allows setting rate limits on them.
+
+          For the iptables based firewall, it should be set like
+          "--limit 1/minute --limit-burst 5".
+
+          For the nftables based firewall, it should be set like
+          "2/second" or "1/minute burst 5 packets".
+        '';
       };
 
       checkReversePath = mkOption {
-        type = types.either types.bool (types.enum ["strict" "loose"]);
-        default = kernelHasRPFilter;
-        defaultText = literalDocBook "<literal>true</literal> if supported by the chosen kernel";
+        type = types.either types.bool (types.enum [ "strict" "loose" ]);
+        default = true;
+        defaultText = literalMD "`true` except if the iptables based firewall is in use and the kernel lacks rpfilter support";
         example = "loose";
-        description =
-          lib.mdDoc ''
-            Performs a reverse path filter test on a packet.  If a reply
-            to the packet would not be sent via the same interface that
-            the packet arrived on, it is refused.
-
-            If using asymmetric routing or other complicated routing, set
-            this option to loose mode or disable it and setup your own
-            counter-measures.
-
-            This option can be either true (or "strict"), "loose" (only
-            drop the packet if the source address is not reachable via any
-            interface) or false.  Defaults to the value of
-            kernelHasRPFilter.
-          '';
+        description = lib.mdDoc ''
+          Performs a reverse path filter test on a packet.  If a reply
+          to the packet would not be sent via the same interface that
+          the packet arrived on, it is refused.
+
+          If using asymmetric routing or other complicated routing, set
+          this option to loose mode or disable it and setup your own
+          counter-measures.
+
+          This option can be either true (or "strict"), "loose" (only
+          drop the packet if the source address is not reachable via any
+          interface) or false.
+        '';
       };
 
       logReversePathDrops = mkOption {
         type = types.bool;
         default = false;
-        description =
-          lib.mdDoc ''
-            Logs dropped packets failing the reverse path filter test if
-            the option networking.firewall.checkReversePath is enabled.
-          '';
+        description = lib.mdDoc ''
+          Logs dropped packets failing the reverse path filter test if
+          the option networking.firewall.checkReversePath is enabled.
+        '';
+      };
+
+      filterForward = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable filtering in IP forwarding.
+
+          This option only works with the nftables based firewall.
+        '';
       };
 
       connectionTrackingModules = mkOption {
         type = types.listOf types.str;
         default = [ ];
         example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ];
-        description =
-          lib.mdDoc ''
-            List of connection-tracking helpers that are auto-loaded.
-            The complete list of possible values is given in the example.
-
-            As helpers can pose as a security risk, it is advised to
-            set this to an empty list and disable the setting
-            networking.firewall.autoLoadConntrackHelpers unless you
-            know what you are doing. Connection tracking is disabled
-            by default.
-
-            Loading of helpers is recommended to be done through the
-            CT target.  More info:
-            https://home.regit.org/netfilter-en/secure-use-of-helpers/
-          '';
+        description = lib.mdDoc ''
+          List of connection-tracking helpers that are auto-loaded.
+          The complete list of possible values is given in the example.
+
+          As helpers can pose as a security risk, it is advised to
+          set this to an empty list and disable the setting
+          networking.firewall.autoLoadConntrackHelpers unless you
+          know what you are doing. Connection tracking is disabled
+          by default.
+
+          Loading of helpers is recommended to be done through the
+          CT target.  More info:
+          https://home.regit.org/netfilter-en/secure-use-of-helpers/
+        '';
       };
 
       autoLoadConntrackHelpers = mkOption {
         type = types.bool;
         default = false;
-        description =
-          lib.mdDoc ''
-            Whether to auto-load connection-tracking helpers.
-            See the description at networking.firewall.connectionTrackingModules
-
-            (needs kernel 3.5+)
-          '';
-      };
+        description = lib.mdDoc ''
+          Whether to auto-load connection-tracking helpers.
+          See the description at networking.firewall.connectionTrackingModules
 
-      extraCommands = mkOption {
-        type = types.lines;
-        default = "";
-        example = "iptables -A INPUT -p icmp -j ACCEPT";
-        description =
-          lib.mdDoc ''
-            Additional shell commands executed as part of the firewall
-            initialisation script.  These are executed just before the
-            final "reject" firewall rule is added, so they can be used
-            to allow packets that would otherwise be refused.
-          '';
+          (needs kernel 3.5+)
+        '';
       };
 
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = [ ];
         example = literalExpression "[ pkgs.ipset ]";
-        description =
-          lib.mdDoc ''
-            Additional packages to be included in the environment of the system
-            as well as the path of networking.firewall.extraCommands.
-          '';
-      };
-
-      extraStopCommands = mkOption {
-        type = types.lines;
-        default = "";
-        example = "iptables -P INPUT ACCEPT";
-        description =
-          lib.mdDoc ''
-            Additional shell commands executed as part of the firewall
-            shutdown script.  These are executed just after the removal
-            of the NixOS input rule, or if the service enters a failed
-            state.
-          '';
+        description = lib.mdDoc ''
+          Additional packages to be included in the environment of the system
+          as well as the path of networking.firewall.extraCommands.
+        '';
       };
 
       interfaces = mkOption {
         default = { };
-        type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
-        description =
-          lib.mdDoc ''
-            Interface-specific open ports.
-          '';
+        type = with types; attrsOf (submodule [{ options = commonOptions; }]);
+        description = lib.mdDoc ''
+          Interface-specific open ports.
+        '';
+      };
+
+      allInterfaces = mkOption {
+        internal = true;
+        visible = false;
+        default = { default = mapAttrs (name: value: cfg.${name}) commonOptions; } // cfg.interfaces;
+        type = with types; attrsOf (submodule [{ options = commonOptions; }]);
+        description = lib.mdDoc ''
+          All open ports.
+        '';
       };
     } // commonOptions;
 
   };
 
 
-  ###### implementation
-
-  # FIXME: Maybe if `enable' is false, the firewall should still be
-  # built but not started by default?
   config = mkIf cfg.enable {
 
+    assertions = [
+      {
+        assertion = cfg.filterForward -> config.networking.nftables.enable;
+        message = "filterForward only works with the nftables based firewall";
+      }
+      {
+        assertion = cfg.autoLoadConntrackHelpers -> lib.versionOlder config.boot.kernelPackages.kernel.version "6";
+        message = "conntrack helper autoloading has been removed from kernel 6.0 and newer";
+      }
+    ];
+
     networking.firewall.trustedInterfaces = [ "lo" ];
 
     environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
@@ -545,40 +285,6 @@ in
       options nf_conntrack nf_conntrack_helper=1
     '';
 
-    assertions = [
-      # This is approximately "checkReversePath -> kernelHasRPFilter",
-      # but the checkReversePath option can include non-boolean
-      # values.
-      { assertion = cfg.checkReversePath == false || kernelHasRPFilter;
-        message = "This kernel does not support rpfilter"; }
-    ];
-
-    systemd.services.firewall = {
-      description = "Firewall";
-      wantedBy = [ "sysinit.target" ];
-      wants = [ "network-pre.target" ];
-      before = [ "network-pre.target" ];
-      after = [ "systemd-modules-load.service" ];
-
-      path = [ cfg.package ] ++ cfg.extraPackages;
-
-      # FIXME: this module may also try to load kernel modules, but
-      # containers don't have CAP_SYS_MODULE.  So the host system had
-      # better have all necessary modules already loaded.
-      unitConfig.ConditionCapability = "CAP_NET_ADMIN";
-      unitConfig.DefaultDependencies = false;
-
-      reloadIfChanged = true;
-
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        ExecStart = "@${startScript} firewall-start";
-        ExecReload = "@${reloadScript} firewall-reload";
-        ExecStop = "@${stopScript} firewall-stop";
-      };
-    };
-
   };
 
 }
diff --git a/nixpkgs/nixos/modules/services/networking/flannel.nix b/nixpkgs/nixos/modules/services/networking/flannel.nix
index 547b6e03949b..6ed4f78ddc92 100644
--- a/nixpkgs/nixos/modules/services/networking/flannel.nix
+++ b/nixpkgs/nixos/modules/services/networking/flannel.nix
@@ -14,7 +14,7 @@ let
   };
 in {
   options.services.flannel = {
-    enable = mkEnableOption "flannel";
+    enable = mkEnableOption (lib.mdDoc "flannel");
 
     package = mkOption {
       description = lib.mdDoc "Package to use for flannel";
@@ -83,7 +83,7 @@ in {
     };
 
     network = mkOption {
-      description = " IPv4 network in CIDR format to use for the entire flannel network.";
+      description = lib.mdDoc " IPv4 network in CIDR format to use for the entire flannel network.";
       type = types.str;
     };
 
@@ -92,10 +92,8 @@ in {
         Needed when running with Kubernetes as backend as this cannot be auto-detected";
       '';
       type = types.nullOr types.str;
-      default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
-      defaultText = literalExpression ''
-        with config.networking; (hostName + optionalString (domain != null) ".''${domain}")
-      '';
+      default = config.networking.fqdnOrHostName;
+      defaultText = literalExpression "config.networking.fqdnOrHostName";
       example = "node1.example.com";
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/freeradius.nix b/nixpkgs/nixos/modules/services/networking/freeradius.nix
index 6c6777c8a572..419a683cb774 100644
--- a/nixpkgs/nixos/modules/services/networking/freeradius.nix
+++ b/nixpkgs/nixos/modules/services/networking/freeradius.nix
@@ -33,7 +33,7 @@ let
   };
 
   freeradiusConfig = {
-    enable = mkEnableOption "the freeradius server";
+    enable = mkEnableOption (lib.mdDoc "the freeradius server");
 
     configDir = mkOption {
       type = types.path;
diff --git a/nixpkgs/nixos/modules/services/networking/frr.nix b/nixpkgs/nixos/modules/services/networking/frr.nix
index 71b66b71ee42..d350fe3548ae 100644
--- a/nixpkgs/nixos/modules/services/networking/frr.nix
+++ b/nixpkgs/nixos/modules/services/networking/frr.nix
@@ -51,7 +51,7 @@ let
 
   serviceOptions = service:
     {
-      enable = mkEnableOption "the FRR ${toUpper service} routing protocol";
+      enable = mkEnableOption (lib.mdDoc "the FRR ${toUpper service} routing protocol");
 
       configFile = mkOption {
         type = types.nullOr types.path;
diff --git a/nixpkgs/nixos/modules/services/networking/gateone.nix b/nixpkgs/nixos/modules/services/networking/gateone.nix
index dc4a65f020d4..ac3f3c9bbf2c 100644
--- a/nixpkgs/nixos/modules/services/networking/gateone.nix
+++ b/nixpkgs/nixos/modules/services/networking/gateone.nix
@@ -6,7 +6,7 @@ in
 {
 options = {
     services.gateone = {
-      enable = mkEnableOption "GateOne server";
+      enable = mkEnableOption (lib.mdDoc "GateOne server");
       pidDir = mkOption {
         default = "/run/gateone";
         type = types.path;
diff --git a/nixpkgs/nixos/modules/services/networking/gdomap.nix b/nixpkgs/nixos/modules/services/networking/gdomap.nix
index 3d829cb69135..53ea8b6875d8 100644
--- a/nixpkgs/nixos/modules/services/networking/gdomap.nix
+++ b/nixpkgs/nixos/modules/services/networking/gdomap.nix
@@ -8,7 +8,7 @@ with lib;
   #
   options = {
     services.gdomap = {
-      enable = mkEnableOption "GNUstep Distributed Objects name server";
+      enable = mkEnableOption (lib.mdDoc "GNUstep Distributed Objects name server");
    };
   };
 
diff --git a/nixpkgs/nixos/modules/services/networking/ghostunnel.nix b/nixpkgs/nixos/modules/services/networking/ghostunnel.nix
index 79cf80e57bef..4902367e2a6a 100644
--- a/nixpkgs/nixos/modules/services/networking/ghostunnel.nix
+++ b/nixpkgs/nixos/modules/services/networking/ghostunnel.nix
@@ -37,12 +37,12 @@ let
         };
 
         keystore = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Path to keystore (combined PEM with cert/key, or PKCS12 keystore).
 
-            NB: storepass is not supported because it would expose credentials via <literal>/proc/*/cmdline</literal>.
+            NB: storepass is not supported because it would expose credentials via `/proc/*/cmdline`.
 
-            Specify this or <literal>cert</literal> and <literal>key</literal>.
+            Specify this or `cert` and `key`.
           '';
           type = types.nullOr types.str;
           default = null;
@@ -213,7 +213,7 @@ in
 {
 
   options = {
-    services.ghostunnel.enable = mkEnableOption "ghostunnel";
+    services.ghostunnel.enable = mkEnableOption (lib.mdDoc "ghostunnel");
 
     services.ghostunnel.package = mkOption {
       description = lib.mdDoc "The ghostunnel package to use.";
diff --git a/nixpkgs/nixos/modules/services/networking/globalprotect-vpn.nix b/nixpkgs/nixos/modules/services/networking/globalprotect-vpn.nix
index bb60916244fd..36aa93780402 100644
--- a/nixpkgs/nixos/modules/services/networking/globalprotect-vpn.nix
+++ b/nixpkgs/nixos/modules/services/networking/globalprotect-vpn.nix
@@ -14,14 +14,12 @@ in
 
 {
   options.services.globalprotect = {
-    enable = mkEnableOption "globalprotect";
+    enable = mkEnableOption (lib.mdDoc "globalprotect");
 
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         GlobalProtect-openconnect configuration. For more information, visit
-        <link
-        xlink:href="https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration"
-        />.
+        <https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration>.
       '';
       default = { };
       example = {
diff --git a/nixpkgs/nixos/modules/services/networking/gnunet.nix b/nixpkgs/nixos/modules/services/networking/gnunet.nix
index 2ab9097e7f7d..fdb353fd3443 100644
--- a/nixpkgs/nixos/modules/services/networking/gnunet.nix
+++ b/nixpkgs/nixos/modules/services/networking/gnunet.nix
@@ -123,9 +123,9 @@ in
       extraOptions = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Additional options that will be copied verbatim in `gnunet.conf'.
-          See `gnunet.conf(5)' for details.
+        description = lib.mdDoc ''
+          Additional options that will be copied verbatim in `gnunet.conf`.
+          See {manpage}`gnunet.conf(5)` for details.
         '';
       };
     };
@@ -155,7 +155,7 @@ in
       description = "GNUnet";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      restartTriggers = [ configFile ];
+      restartTriggers = [ config.environment.etc."gnunet.conf".source ];
       path = [ cfg.package pkgs.miniupnpc ];
       serviceConfig.ExecStart = "${cfg.package}/lib/gnunet/libexec/gnunet-service-arm -c /etc/gnunet.conf";
       serviceConfig.User = "gnunet";
diff --git a/nixpkgs/nixos/modules/services/networking/go-autoconfig.nix b/nixpkgs/nixos/modules/services/networking/go-autoconfig.nix
new file mode 100644
index 000000000000..07c628ae2cad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/go-autoconfig.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.go-autoconfig;
+  format = pkgs.formats.yaml { };
+  configFile = format.generate "config.yml" cfg.settings;
+
+in {
+  options = {
+    services.go-autoconfig = {
+
+      enable = mkEnableOption (mdDoc "IMAP/SMTP autodiscover feature for mail clients");
+
+      settings = mkOption {
+        default = { };
+        description = mdDoc ''
+          Configuration for go-autoconfig. See
+          <https://github.com/L11R/go-autoconfig/blob/master/config.yml>
+          for more information.
+        '';
+        type = types.submodule {
+          freeformType = format.type;
+        };
+        example = literalExpression ''
+          {
+            service_addr = ":1323";
+            domain = "autoconfig.example.org";
+            imap = {
+              server = "example.org";
+              port = 993;
+            };
+            smtp = {
+              server = "example.org";
+              port = 465;
+            };
+          }
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      services.go-autoconfig = {
+        wantedBy = [ "multi-user.target" ];
+        description = "IMAP/SMTP autodiscover server";
+        after = [ "network.target" ];
+        serviceConfig = {
+          ExecStart = "${pkgs.go-autoconfig}/bin/go-autoconfig -config ${configFile}";
+          Restart = "on-failure";
+          WorkingDirectory = ''${pkgs.go-autoconfig}/'';
+          DynamicUser = true;
+        };
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ onny ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/go-neb.nix b/nixpkgs/nixos/modules/services/networking/go-neb.nix
index ffa7923d6fbe..b65bb5f548ee 100644
--- a/nixpkgs/nixos/modules/services/networking/go-neb.nix
+++ b/nixpkgs/nixos/modules/services/networking/go-neb.nix
@@ -9,7 +9,7 @@ let
   configFile = settingsFormat.generate "config.yaml" cfg.config;
 in {
   options.services.go-neb = {
-    enable = mkEnableOption "Extensible matrix bot written in Go";
+    enable = mkEnableOption (lib.mdDoc "Extensible matrix bot written in Go");
 
     bindAddress = mkOption {
       type = types.str;
@@ -60,13 +60,12 @@ in {
 
       serviceConfig = {
         ExecStartPre = lib.optional (cfg.secretFile != null)
-          (pkgs.writeShellScript "pre-start" ''
+          ("+" + pkgs.writeShellScript "pre-start" ''
             umask 077
             export $(xargs < ${cfg.secretFile})
             ${pkgs.envsubst}/bin/envsubst -i "${configFile}" > ${finalConfigFile}
             chown go-neb ${finalConfigFile}
           '');
-        PermissionsStartOnly = true;
         RuntimeDirectory = "go-neb";
         ExecStart = "${pkgs.go-neb}/bin/go-neb";
         User = "go-neb";
diff --git a/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix b/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix
index e3f99f68d61f..d9c4a2421d72 100644
--- a/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix
+++ b/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix
@@ -5,7 +5,7 @@ let
   cfg = config.services.go-shadowsocks2.server;
 in {
   options.services.go-shadowsocks2.server = {
-    enable = mkEnableOption "go-shadowsocks2 server";
+    enable = mkEnableOption (lib.mdDoc "go-shadowsocks2 server");
 
     listenAddress = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/gobgpd.nix b/nixpkgs/nixos/modules/services/networking/gobgpd.nix
index f1e2095708f9..b22242edaade 100644
--- a/nixpkgs/nixos/modules/services/networking/gobgpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/gobgpd.nix
@@ -8,7 +8,7 @@ let
   confFile = format.generate "gobgpd.conf" cfg.settings;
 in {
   options.services.gobgpd = {
-    enable = mkEnableOption "GoBGP Routing Daemon";
+    enable = mkEnableOption (lib.mdDoc "GoBGP Routing Daemon");
 
     settings = mkOption {
       type = format.type;
diff --git a/nixpkgs/nixos/modules/services/networking/gvpe.nix b/nixpkgs/nixos/modules/services/networking/gvpe.nix
index 5ecf78d09efc..2279ceee2f58 100644
--- a/nixpkgs/nixos/modules/services/networking/gvpe.nix
+++ b/nixpkgs/nixos/modules/services/networking/gvpe.nix
@@ -42,7 +42,7 @@ in
 {
   options = {
     services.gvpe = {
-      enable = lib.mkEnableOption "gvpe";
+      enable = lib.mkEnableOption (lib.mdDoc "gvpe");
 
       nodename = mkOption {
         default = null;
diff --git a/nixpkgs/nixos/modules/services/networking/hans.nix b/nixpkgs/nixos/modules/services/networking/hans.nix
index ffb2ee841c64..3ea95b3bdae9 100644
--- a/nixpkgs/nixos/modules/services/networking/hans.nix
+++ b/nixpkgs/nixos/modules/services/networking/hans.nix
@@ -55,7 +55,7 @@ in
             passwordFile = mkOption {
               type = types.str;
               default = "";
-              description = lib.mdDoc "File that containts password";
+              description = lib.mdDoc "File that contains password";
             };
 
           };
@@ -92,7 +92,7 @@ in
         passwordFile = mkOption {
           type = types.str;
           default = "";
-          description = lib.mdDoc "File that containts password";
+          description = lib.mdDoc "File that contains password";
         };
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/harmonia.nix b/nixpkgs/nixos/modules/services/networking/harmonia.nix
new file mode 100644
index 000000000000..144fa6c708e2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/harmonia.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.harmonia;
+  format = pkgs.formats.toml { };
+in
+{
+  options = {
+    services.harmonia = {
+      enable = lib.mkEnableOption (lib.mdDoc "Harmonia: Nix binary cache written in Rust");
+
+      signKeyPath = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        description = lib.mdDoc "Path to the signing key that will be used for signing the cache";
+      };
+
+      package = lib.mkPackageOptionMD pkgs "harmonia" { };
+
+      settings = lib.mkOption {
+        inherit (format) type;
+        default = { };
+        description = lib.mdDoc ''
+          Settings to merge with the default configuration.
+          For the list of the default configuration, see <https://github.com/nix-community/harmonia/tree/master#configuration>.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.harmonia = {
+      description = "harmonia binary cache service";
+
+      requires = [ "nix-daemon.socket" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        CONFIG_FILE = format.generate "harmonia.toml" cfg.settings;
+        SIGN_KEY_PATH = lib.mkIf (cfg.signKeyPath != null) "%d/sign-key";
+        # Note: it's important to set this for nix-store, because it wants to use
+        # $HOME in order to use a temporary cache dir. bizarre failures will occur
+        # otherwise
+        HOME = "/run/harmonia";
+      };
+
+      serviceConfig = {
+        ExecStart = lib.getExe cfg.package;
+        User = "harmonia";
+        Group = "harmonia";
+        DynamicUser = true;
+        PrivateUsers = true;
+        DeviceAllow = [ "" ];
+        UMask = "0066";
+        RuntimeDirectory = "harmonia";
+        LoadCredential = lib.mkIf (cfg.signKeyPath != null) [ "sign-key:${cfg.signKeyPath}" ];
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        CapabilityBoundingSet = "";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        RestrictRealtime = true;
+        MemoryDenyWriteExecute = true;
+        ProcSubset = "pid";
+        ProtectProc = "invisible";
+        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
+        PrivateNetwork = false;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        LockPersonality = true;
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+        LimitNOFILE = 65536;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/headscale.nix b/nixpkgs/nixos/modules/services/networking/headscale.nix
index ab07e7c14b8c..78253dd9d112 100644
--- a/nixpkgs/nixos/modules/services/networking/headscale.nix
+++ b/nixpkgs/nixos/modules/services/networking/headscale.nix
@@ -1,18 +1,21 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
   cfg = config.services.headscale;
 
   dataDir = "/var/lib/headscale";
   runDir = "/run/headscale";
 
-  settingsFormat = pkgs.formats.yaml { };
+  settingsFormat = pkgs.formats.yaml {};
   configFile = settingsFormat.generate "headscale.yaml" cfg.settings;
-in
-{
+in {
   options = {
     services.headscale = {
-      enable = mkEnableOption "headscale, Open Source coordination server for Tailscale";
+      enable = mkEnableOption (lib.mdDoc "headscale, Open Source coordination server for Tailscale");
 
       package = mkOption {
         type = types.package;
@@ -26,36 +29,29 @@ in
       user = mkOption {
         default = "headscale";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User account under which headscale runs.
-          <note><para>
+
+          ::: {.note}
           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 headscale service starts.
-          </para></note>
+          :::
         '';
       };
 
       group = mkOption {
         default = "headscale";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Group under which headscale runs.
-          <note><para>
+
+          ::: {.note}
           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 headscale service starts.
-          </para></note>
-        '';
-      };
-
-      serverUrl = mkOption {
-        type = types.str;
-        default = "http://127.0.0.1:8080";
-        description = lib.mdDoc ''
-          The url clients will connect to.
+          :::
         '';
-        example = "https://myheadscale.example.com:443";
       };
 
       address = mkOption {
@@ -76,337 +72,383 @@ in
         example = 443;
       };
 
-      privateKeyFile = mkOption {
-        type = types.path;
-        default = "${dataDir}/private.key";
-        description = lib.mdDoc ''
-          Path to private key file, generated automatically if it does not exist.
-        '';
-      };
-
-      derp = {
-        urls = mkOption {
-          type = types.listOf types.str;
-          default = [ "https://controlplane.tailscale.com/derpmap/default" ];
-          description = lib.mdDoc ''
-            List of urls containing DERP maps.
-            See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
-          '';
-        };
-
-        paths = mkOption {
-          type = types.listOf types.path;
-          default = [ ];
-          description = lib.mdDoc ''
-            List of file paths containing DERP maps.
-            See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
-          '';
-        };
-
-
-        autoUpdate = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to automatically update DERP maps on a set frequency.
-          '';
-          example = false;
-        };
-
-        updateFrequency = mkOption {
-          type = types.str;
-          default = "24h";
-          description = lib.mdDoc ''
-            Frequency to update DERP maps.
-          '';
-          example = "5m";
-        };
-
-      };
-
-      ephemeralNodeInactivityTimeout = mkOption {
-        type = types.str;
-        default = "30m";
-        description = lib.mdDoc ''
-          Time before an inactive ephemeral node is deleted.
-        '';
-        example = "5m";
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum [ "sqlite3" "postgres" ];
-          example = "postgres";
-          default = "sqlite3";
-          description = lib.mdDoc "Database engine to use.";
-        };
-
-        host = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "127.0.0.1";
-          description = lib.mdDoc "Database host address.";
-        };
-
-        port = mkOption {
-          type = types.nullOr types.port;
-          default = null;
-          example = 3306;
-          description = lib.mdDoc "Database host port.";
-        };
-
-        name = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "headscale";
-          description = lib.mdDoc "Database name.";
-        };
-
-        user = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "headscale";
-          description = lib.mdDoc "Database user.";
-        };
-
-        passwordFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          example = "/run/keys/headscale-dbpassword";
-          description = lib.mdDoc ''
-            A file containing the password corresponding to
-            {option}`database.user`.
-          '';
-        };
-
-        path = mkOption {
-          type = types.nullOr types.str;
-          default = "${dataDir}/db.sqlite";
-          description = lib.mdDoc "Path to the sqlite3 database file.";
-        };
-      };
-
-      logLevel = mkOption {
-        type = types.str;
-        default = "info";
-        description = lib.mdDoc ''
-          headscale log level.
-        '';
-        example = "debug";
-      };
-
-      dns = {
-        nameservers = mkOption {
-          type = types.listOf types.str;
-          default = [ "1.1.1.1" ];
-          description = lib.mdDoc ''
-            List of nameservers to pass to Tailscale clients.
-          '';
-        };
-
-        domains = mkOption {
-          type = types.listOf types.str;
-          default = [ ];
-          description = lib.mdDoc ''
-            Search domains to inject to Tailscale clients.
-          '';
-          example = [ "mydomain.internal" ];
-        };
-
-        magicDns = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
-            Only works if there is at least a nameserver defined.
-          '';
-          example = false;
-        };
-
-        baseDomain = mkOption {
-          type = types.str;
-          default = "";
-          description = lib.mdDoc ''
-            Defines the base domain to create the hostnames for MagicDNS.
-            {option}`baseDomain` must be a FQDNs, without the trailing dot.
-            The FQDN of the hosts will be
-            `hostname.namespace.base_domain` (e.g.
-            `myhost.mynamespace.example.com`).
-          '';
-        };
-      };
-
-      openIdConnect = {
-        issuer = mkOption {
-          type = types.str;
-          default = "";
-          description = lib.mdDoc ''
-            URL to OpenID issuer.
-          '';
-          example = "https://openid.example.com";
-        };
-
-        clientId = mkOption {
-          type = types.str;
-          default = "";
-          description = lib.mdDoc ''
-            OpenID Connect client ID.
-          '';
-        };
-
-        clientSecretFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to OpenID Connect client secret file.
-          '';
-        };
-
-        domainMap = mkOption {
-          type = types.attrsOf types.str;
-          default = { };
-          description = lib.mdDoc ''
-            Domain map is used to map incomming users (by their email) to
-            a namespace. The key can be a string, or regex.
-          '';
-          example = {
-            ".*" = "default-namespace";
-          };
-        };
-
-      };
-
-      tls = {
-        letsencrypt = {
-          hostname = mkOption {
-            type = types.nullOr types.str;
-            default = "";
-            description = lib.mdDoc ''
-              Domain name to request a TLS certificate for.
-            '';
-          };
-          challengeType = mkOption {
-            type = types.enum [ "TLS-ALPN-01" "HTTP-01" ];
-            default = "HTTP-01";
-            description = lib.mdDoc ''
-              Type of ACME challenge to use, currently supported types:
-              `HTTP-01` or `TLS-ALPN-01`.
-            '';
-          };
-          httpListen = mkOption {
-            type = types.nullOr types.str;
-            default = ":http";
-            description = lib.mdDoc ''
-              When HTTP-01 challenge is chosen, letsencrypt must set up a
-              verification endpoint, and it will be listening on:
-              `:http = port 80`.
-            '';
-          };
-        };
-
-        certFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to already created certificate.
-          '';
-        };
-        keyFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to key for already created certificate.
-          '';
-        };
-      };
-
-      aclPolicyFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = lib.mdDoc ''
-          Path to a file containg ACL policies.
-        '';
-      };
-
       settings = mkOption {
-        type = settingsFormat.type;
-        default = { };
         description = lib.mdDoc ''
           Overrides to {file}`config.yaml` as a Nix attribute set.
-          This option is ideal for overriding settings not exposed as Nix options.
           Check the [example config](https://github.com/juanfont/headscale/blob/main/config-example.yaml)
           for possible options.
         '';
+        type = types.submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            server_url = mkOption {
+              type = types.str;
+              default = "http://127.0.0.1:8080";
+              description = lib.mdDoc ''
+                The url clients will connect to.
+              '';
+              example = "https://myheadscale.example.com:443";
+            };
+
+            private_key_path = mkOption {
+              type = types.path;
+              default = "${dataDir}/private.key";
+              description = lib.mdDoc ''
+                Path to private key file, generated automatically if it does not exist.
+              '';
+            };
+
+            noise.private_key_path = mkOption {
+              type = types.path;
+              default = "${dataDir}/noise_private.key";
+              description = lib.mdDoc ''
+                Path to noise private key file, generated automatically if it does not exist.
+              '';
+            };
+
+            derp = {
+              urls = mkOption {
+                type = types.listOf types.str;
+                default = ["https://controlplane.tailscale.com/derpmap/default"];
+                description = lib.mdDoc ''
+                  List of urls containing DERP maps.
+                  See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
+                '';
+              };
+
+              paths = mkOption {
+                type = types.listOf types.path;
+                default = [];
+                description = lib.mdDoc ''
+                  List of file paths containing DERP maps.
+                  See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
+                '';
+              };
+
+              auto_update_enable = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether to automatically update DERP maps on a set frequency.
+                '';
+                example = false;
+              };
+
+              update_frequency = mkOption {
+                type = types.str;
+                default = "24h";
+                description = lib.mdDoc ''
+                  Frequency to update DERP maps.
+                '';
+                example = "5m";
+              };
+            };
+
+            ephemeral_node_inactivity_timeout = mkOption {
+              type = types.str;
+              default = "30m";
+              description = lib.mdDoc ''
+                Time before an inactive ephemeral node is deleted.
+              '';
+              example = "5m";
+            };
+
+            db_type = mkOption {
+              type = types.enum ["sqlite3" "postgres"];
+              example = "postgres";
+              default = "sqlite3";
+              description = lib.mdDoc "Database engine to use.";
+            };
+
+            db_host = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "127.0.0.1";
+              description = lib.mdDoc "Database host address.";
+            };
+
+            db_port = mkOption {
+              type = types.nullOr types.port;
+              default = null;
+              example = 3306;
+              description = lib.mdDoc "Database host port.";
+            };
+
+            db_name = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "headscale";
+              description = lib.mdDoc "Database name.";
+            };
+
+            db_user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "headscale";
+              description = lib.mdDoc "Database user.";
+            };
+
+            db_password_file = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              example = "/run/keys/headscale-dbpassword";
+              description = lib.mdDoc ''
+                A file containing the password corresponding to
+                {option}`database.user`.
+              '';
+            };
+
+            db_path = mkOption {
+              type = types.nullOr types.str;
+              default = "${dataDir}/db.sqlite";
+              description = lib.mdDoc "Path to the sqlite3 database file.";
+            };
+
+            log.level = mkOption {
+              type = types.str;
+              default = "info";
+              description = lib.mdDoc ''
+                headscale log level.
+              '';
+              example = "debug";
+            };
+
+            log.format = mkOption {
+              type = types.str;
+              default = "text";
+              description = lib.mdDoc ''
+                headscale log format.
+              '';
+              example = "json";
+            };
+
+            dns_config = {
+              nameservers = mkOption {
+                type = types.listOf types.str;
+                default = ["1.1.1.1"];
+                description = lib.mdDoc ''
+                  List of nameservers to pass to Tailscale clients.
+                '';
+              };
+
+              override_local_dns = mkOption {
+                type = types.bool;
+                default = false;
+                description = lib.mdDoc ''
+                  Whether to use [Override local DNS](https://tailscale.com/kb/1054/dns/).
+                '';
+                example = true;
+              };
+
+              domains = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = lib.mdDoc ''
+                  Search domains to inject to Tailscale clients.
+                '';
+                example = ["mydomain.internal"];
+              };
+
+              magic_dns = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
+                  Only works if there is at least a nameserver defined.
+                '';
+                example = false;
+              };
+
+              base_domain = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  Defines the base domain to create the hostnames for MagicDNS.
+                  {option}`baseDomain` must be a FQDNs, without the trailing dot.
+                  The FQDN of the hosts will be
+                  `hostname.namespace.base_domain` (e.g.
+                  `myhost.mynamespace.example.com`).
+                '';
+              };
+            };
+
+            oidc = {
+              issuer = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  URL to OpenID issuer.
+                '';
+                example = "https://openid.example.com";
+              };
+
+              client_id = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  OpenID Connect client ID.
+                '';
+              };
+
+              client_secret_path = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = lib.mdDoc ''
+                  Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}.
+                '';
+              };
+
+              scope = mkOption {
+                type = types.listOf types.str;
+                default = ["openid" "profile" "email"];
+                description = lib.mdDoc ''
+                  Scopes used in the OIDC flow.
+                '';
+              };
+
+              extra_params = mkOption {
+                type = types.attrsOf types.str;
+                default = { };
+                description = lib.mdDoc ''
+                  Custom query parameters to send with the Authorize Endpoint request.
+                '';
+                example = {
+                  domain_hint = "example.com";
+                };
+              };
+
+              allowed_domains = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+                description = lib.mdDoc ''
+                  Allowed principal domains. if an authenticated user's domain
+                  is not in this list authentication request will be rejected.
+                '';
+                example = [ "example.com" ];
+              };
+
+              allowed_users = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+                description = lib.mdDoc ''
+                  Users allowed to authenticate even if not in allowedDomains.
+                '';
+                example = [ "alice@example.com" ];
+              };
+
+              strip_email_domain = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether the domain part of the email address should be removed when generating namespaces.
+                '';
+              };
+            };
+
+            tls_letsencrypt_hostname = mkOption {
+              type = types.nullOr types.str;
+              default = "";
+              description = lib.mdDoc ''
+                Domain name to request a TLS certificate for.
+              '';
+            };
+
+            tls_letsencrypt_challenge_type = mkOption {
+              type = types.enum ["TLS-ALPN-01" "HTTP-01"];
+              default = "HTTP-01";
+              description = lib.mdDoc ''
+                Type of ACME challenge to use, currently supported types:
+                `HTTP-01` or `TLS-ALPN-01`.
+              '';
+            };
+
+            tls_letsencrypt_listen = mkOption {
+              type = types.nullOr types.str;
+              default = ":http";
+              description = lib.mdDoc ''
+                When HTTP-01 challenge is chosen, letsencrypt must set up a
+                verification endpoint, and it will be listening on:
+                `:http = port 80`.
+              '';
+            };
+
+            tls_cert_path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+                Path to already created certificate.
+              '';
+            };
+
+            tls_key_path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+                Path to key for already created certificate.
+              '';
+            };
+
+            acl_policy_path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+                Path to a file containing ACL policies.
+              '';
+            };
+          };
+        };
       };
-
-
     };
-
   };
-  config = mkIf cfg.enable {
 
+  imports = [
+    # TODO address + port = listen_addr
+    (mkRenamedOptionModule ["services" "headscale" "serverUrl"] ["services" "headscale" "settings" "server_url"])
+    (mkRenamedOptionModule ["services" "headscale" "privateKeyFile"] ["services" "headscale" "settings" "private_key_path"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "urls"] ["services" "headscale" "settings" "derp" "urls"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "paths"] ["services" "headscale" "settings" "derp" "paths"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "autoUpdate"] ["services" "headscale" "settings" "derp" "auto_update_enable"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "updateFrequency"] ["services" "headscale" "settings" "derp" "update_frequency"])
+    (mkRenamedOptionModule ["services" "headscale" "ephemeralNodeInactivityTimeout"] ["services" "headscale" "settings" "ephemeral_node_inactivity_timeout"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "type"] ["services" "headscale" "settings" "db_type"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "path"] ["services" "headscale" "settings" "db_path"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "host"] ["services" "headscale" "settings" "db_host"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "port"] ["services" "headscale" "settings" "db_port"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "name"] ["services" "headscale" "settings" "db_name"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "user"] ["services" "headscale" "settings" "db_user"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "passwordFile"] ["services" "headscale" "settings" "db_password_file"])
+    (mkRenamedOptionModule ["services" "headscale" "logLevel"] ["services" "headscale" "settings" "log" "level"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "nameservers"] ["services" "headscale" "settings" "dns_config" "nameservers"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "domains"] ["services" "headscale" "settings" "dns_config" "domains"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "magicDns"] ["services" "headscale" "settings" "dns_config" "magic_dns"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_path"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "certFile"] ["services" "headscale" "settings" "tls_cert_path"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "keyFile"] ["services" "headscale" "settings" "tls_key_path"])
+    (mkRenamedOptionModule ["services" "headscale" "aclPolicyFile"] ["services" "headscale" "settings" "acl_policy_path"])
+
+    (mkRemovedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ''
+      Headscale no longer uses domain_map. If you're using an old version of headscale you can still set this option via services.headscale.settings.oidc.domain_map.
+    '')
+  ];
+
+  config = mkIf cfg.enable {
     services.headscale.settings = {
-      server_url = mkDefault cfg.serverUrl;
       listen_addr = mkDefault "${cfg.address}:${toString cfg.port}";
 
-      private_key_path = mkDefault cfg.privateKeyFile;
-
-      derp = {
-        urls = mkDefault cfg.derp.urls;
-        paths = mkDefault cfg.derp.paths;
-        auto_update_enable = mkDefault cfg.derp.autoUpdate;
-        update_frequency = mkDefault cfg.derp.updateFrequency;
-      };
-
       # Turn off update checks since the origin of our package
       # is nixpkgs and not Github.
       disable_check_updates = true;
 
-      ephemeral_node_inactivity_timeout = mkDefault cfg.ephemeralNodeInactivityTimeout;
-
-      db_type = mkDefault cfg.database.type;
-      db_path = mkDefault cfg.database.path;
-
-      log_level = mkDefault cfg.logLevel;
-
-      dns_config = {
-        nameservers = mkDefault cfg.dns.nameservers;
-        domains = mkDefault cfg.dns.domains;
-        magic_dns = mkDefault cfg.dns.magicDns;
-        base_domain = mkDefault cfg.dns.baseDomain;
-      };
-
       unix_socket = "${runDir}/headscale.sock";
 
-      # OpenID Connect
-      oidc = {
-        issuer = mkDefault cfg.openIdConnect.issuer;
-        client_id = mkDefault cfg.openIdConnect.clientId;
-        domain_map = mkDefault cfg.openIdConnect.domainMap;
-      };
-
       tls_letsencrypt_cache_dir = "${dataDir}/.cache";
-
-    } // optionalAttrs (cfg.database.host != null) {
-      db_host = mkDefault cfg.database.host;
-    } // optionalAttrs (cfg.database.port != null) {
-      db_port = mkDefault cfg.database.port;
-    } // optionalAttrs (cfg.database.name != null) {
-      db_name = mkDefault cfg.database.name;
-    } // optionalAttrs (cfg.database.user != null) {
-      db_user = mkDefault cfg.database.user;
-    } // optionalAttrs (cfg.tls.letsencrypt.hostname != null) {
-      tls_letsencrypt_hostname = mkDefault cfg.tls.letsencrypt.hostname;
-    } // optionalAttrs (cfg.tls.letsencrypt.challengeType != null) {
-      tls_letsencrypt_challenge_type = mkDefault cfg.tls.letsencrypt.challengeType;
-    } // optionalAttrs (cfg.tls.letsencrypt.httpListen != null) {
-      tls_letsencrypt_listen = mkDefault cfg.tls.letsencrypt.httpListen;
-    } // optionalAttrs (cfg.tls.certFile != null) {
-      tls_cert_path = mkDefault cfg.tls.certFile;
-    } // optionalAttrs (cfg.tls.keyFile != null) {
-      tls_key_path = mkDefault cfg.tls.keyFile;
-    } // optionalAttrs (cfg.aclPolicyFile != null) {
-      acl_policy_path = mkDefault cfg.aclPolicyFile;
     };
 
     # Setup the headscale configuration in a known path in /etc to
@@ -414,7 +456,7 @@ in
     # for communication.
     environment.etc."headscale/config.yaml".source = configFile;
 
-    users.groups.headscale = mkIf (cfg.group == "headscale") { };
+    users.groups.headscale = mkIf (cfg.group == "headscale") {};
 
     users.users.headscale = mkIf (cfg.user == "headscale") {
       description = "headscale user";
@@ -425,70 +467,65 @@ in
 
     systemd.services.headscale = {
       description = "headscale coordination server for Tailscale";
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      restartTriggers = [ configFile ];
+      after = ["network-online.target"];
+      wantedBy = ["multi-user.target"];
+      restartTriggers = [configFile];
 
       environment.GIN_MODE = "release";
 
       script = ''
-        ${optionalString (cfg.database.passwordFile != null) ''
-          export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.database.passwordFile})"
+        ${optionalString (cfg.settings.db_password_file != null) ''
+          export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})"
         ''}
 
-        ${optionalString (cfg.openIdConnect.clientSecretFile != null) ''
-          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
-        ''}
         exec ${cfg.package}/bin/headscale serve
       '';
 
-      serviceConfig =
-        let
-          capabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
-        in
-        {
-          Restart = "always";
-          Type = "simple";
-          User = cfg.user;
-          Group = cfg.group;
-
-          # Hardening options
-          RuntimeDirectory = "headscale";
-          # Allow headscale group access so users can be added and use the CLI.
-          RuntimeDirectoryMode = "0750";
-
-          StateDirectory = "headscale";
-          StateDirectoryMode = "0750";
-
-          ProtectSystem = "strict";
-          ProtectHome = true;
-          PrivateTmp = true;
-          PrivateDevices = true;
-          ProtectKernelTunables = true;
-          ProtectControlGroups = true;
-          RestrictSUIDSGID = true;
-          PrivateMounts = true;
-          ProtectKernelModules = true;
-          ProtectKernelLogs = true;
-          ProtectHostname = true;
-          ProtectClock = true;
-          ProtectProc = "invisible";
-          ProcSubset = "pid";
-          RestrictNamespaces = true;
-          RemoveIPC = true;
-          UMask = "0077";
-
-          CapabilityBoundingSet = capabilityBoundingSet;
-          AmbientCapabilities = capabilityBoundingSet;
-          NoNewPrivileges = true;
-          LockPersonality = true;
-          RestrictRealtime = true;
-          SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
-          SystemCallArchitectures = "native";
-          RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
-        };
+      serviceConfig = let
+        capabilityBoundingSet = ["CAP_CHOWN"] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
+      in {
+        Restart = "always";
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+
+        # Hardening options
+        RuntimeDirectory = "headscale";
+        # Allow headscale group access so users can be added and use the CLI.
+        RuntimeDirectoryMode = "0750";
+
+        StateDirectory = "headscale";
+        StateDirectoryMode = "0750";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        RestrictNamespaces = true;
+        RemoveIPC = true;
+        UMask = "0077";
+
+        CapabilityBoundingSet = capabilityBoundingSet;
+        AmbientCapabilities = capabilityBoundingSet;
+        NoNewPrivileges = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+      };
     };
   };
 
-  meta.maintainers = with maintainers; [ kradalby ];
+  meta.maintainers = with maintainers; [kradalby misterio77];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/hostapd.nix b/nixpkgs/nixos/modules/services/networking/hostapd.nix
index ec1a7a58b1e0..ecc158f8151d 100644
--- a/nixpkgs/nixos/modules/services/networking/hostapd.nix
+++ b/nixpkgs/nixos/modules/services/networking/hostapd.nix
@@ -20,6 +20,8 @@ let
     ssid=${cfg.ssid}
     hw_mode=${cfg.hwMode}
     channel=${toString cfg.channel}
+    ieee80211n=1
+    ieee80211ac=1
     ${optionalString (cfg.countryCode != null) "country_code=${cfg.countryCode}"}
     ${optionalString (cfg.countryCode != null) "ieee80211d=1"}
 
@@ -34,6 +36,7 @@ let
 
     ${optionalString cfg.wpa ''
       wpa=2
+      wpa_pairwise=CCMP
       wpa_passphrase=${cfg.wpaPassphrase}
     ''}
     ${optionalString cfg.noScan "noscan=1"}
@@ -66,7 +69,6 @@ in
       };
 
       interface = mkOption {
-        default = "";
         example = "wlp2s0";
         type = types.str;
         description = lib.mdDoc ''
@@ -94,7 +96,8 @@ in
       };
 
       ssid = mkOption {
-        default = "nixos";
+        default = config.system.nixos.distroId;
+        defaultText = literalExpression "config.system.nixos.distroId";
         example = "mySpecialSSID";
         type = types.str;
         description = lib.mdDoc "SSID to be used in IEEE 802.11 management frames.";
@@ -199,7 +202,7 @@ in
 
     environment.systemPackages =  [ pkgs.hostapd ];
 
-    services.udev.packages = optional (cfg.countryCode != null) [ pkgs.crda ];
+    services.udev.packages = optionals (cfg.countryCode != null) [ pkgs.crda ];
 
     systemd.services.hostapd =
       { description = "hostapd wireless AP";
diff --git a/nixpkgs/nixos/modules/services/networking/https-dns-proxy.nix b/nixpkgs/nixos/modules/services/networking/https-dns-proxy.nix
index 4b6e302e445f..87eb23ea4585 100644
--- a/nixpkgs/nixos/modules/services/networking/https-dns-proxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/https-dns-proxy.nix
@@ -20,19 +20,23 @@ let
       ips = [ "9.9.9.9" "149.112.112.112" ];
       url = "https://dns.quad9.net/dns-query";
     };
+    opendns = {
+      ips = [ "208.67.222.222" "208.67.220.220" ];
+      url = "https://doh.opendns.com/dns-query";
+    };
+    custom = {
+      inherit (cfg.provider) ips url;
+    };
   };
 
   defaultProvider = "quad9";
 
   providerCfg =
-    let
-      isCustom = cfg.provider.kind == "custom";
-    in
-    lib.concatStringsSep " " [
+    concatStringsSep " " [
       "-b"
-      (concatStringsSep "," (if isCustom then cfg.provider.ips else providers."${cfg.provider.kind}".ips))
+      (concatStringsSep "," providers."${cfg.provider.kind}".ips)
       "-r"
-      (if isCustom then cfg.provider.url else providers."${cfg.provider.kind}".url)
+      providers."${cfg.provider.kind}".url
     ];
 
 in
@@ -42,7 +46,7 @@ in
   ###### interface
 
   options.services.https-dns-proxy = {
-    enable = mkEnableOption "https-dns-proxy daemon";
+    enable = mkEnableOption (lib.mdDoc "https-dns-proxy daemon");
 
     address = mkOption {
       description = lib.mdDoc "The address on which to listen";
@@ -62,14 +66,16 @@ in
           The upstream provider to use or custom in case you do not trust any of
           the predefined providers or just want to use your own.
 
-          The default is ${defaultProvider} and there are privacy and security trade-offs
-          when using any upstream provider. Please consider that before using any
-          of them.
+          The default is ${defaultProvider} and there are privacy and security
+          trade-offs when using any upstream provider. Please consider that
+          before using any of them.
+
+          Supported providers: ${concatStringsSep ", " (builtins.attrNames providers)}
 
-          If you pick a custom provider, you will need to provide the bootstrap
-          IP addresses as well as the resolver https URL.
+          If you pick the custom provider, you will need to provide the
+          bootstrap IP addresses as well as the resolver https URL.
         '';
-        type = types.enum ((builtins.attrNames providers) ++ [ "custom" ]);
+        type = types.enum (builtins.attrNames providers);
         default = defaultProvider;
       };
 
@@ -105,14 +111,18 @@ in
   config = lib.mkIf cfg.enable {
     systemd.services.https-dns-proxy = {
       description = "DNS to DNS over HTTPS (DoH) proxy";
+      requires = [ "network.target" ];
       after = [ "network.target" ];
+      wants = [ "nss-lookup.target" ];
+      before = [ "nss-lookup.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = rec {
         Type = "exec";
         DynamicUser = true;
+        ProtectHome = "tmpfs";
         ExecStart = lib.concatStringsSep " " (
           [
-            "${pkgs.https-dns-proxy}/bin/https_dns_proxy"
+            (lib.getExe pkgs.https-dns-proxy)
             "-a ${toString cfg.address}"
             "-p ${toString cfg.port}"
             "-l -"
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/options.nix b/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
index bc289132a760..82c144236f3b 100644
--- a/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
@@ -118,7 +118,7 @@ in
 
   options.services.hylafax = {
 
-    enable = mkEnableOption "HylaFAX server";
+    enable = mkEnableOption (lib.mdDoc "HylaFAX server");
 
     autostart = mkOption {
       type = bool;
@@ -172,23 +172,23 @@ in
     userAccessFile = mkOption {
       type = path;
       default = "/etc/hosts.hfaxd";
-      description = ''
-        The <filename>hosts.hfaxd</filename>
+      description = lib.mdDoc ''
+        The {file}`hosts.hfaxd`
         file entry in the spooling area
         will be symlinked to the location given here.
         This file must exist and be
-        readable only by the <literal>uucp</literal> user.
+        readable only by the `uucp` user.
         See hosts.hfaxd(5) for details.
         This configuration permits access for all users:
-        <literal>
+        ```
           environment.etc."hosts.hfaxd" = {
             mode = "0600";
             user = "uucp";
             text = ".*";
           };
-        </literal>
+        ```
         Note that host-based access can be controlled with
-        <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
+        {option}`config.systemd.sockets.hylafax-hfaxd.listenStreams`;
         by default, only 127.0.0.1 is permitted to connect.
       '';
     };
@@ -235,9 +235,9 @@ in
         InternationalPrefix = "00";
         LongDistancePrefix = "0";
       };
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of default values for
-        modem config files <filename>etc/config.*</filename>.
+        modem config files {file}`etc/config.*`.
         ${commonDescr}
         Think twice before changing
         paths of fax-processing scripts.
@@ -271,11 +271,11 @@ in
       '';
     };
 
-    faxcron.enable.spoolInit = mkEnableOption ''
+    faxcron.enable.spoolInit = mkEnableOption (lib.mdDoc ''
       Purge old files from the spooling area with
-      <filename>faxcron</filename>
+      {file}`faxcron`
       each time the spooling area is initialized.
-    '';
+    '');
     faxcron.enable.frequency = mkOption {
       type = nullOr nonEmptyStr;
       default = null;
@@ -311,11 +311,11 @@ in
       '';
     };
 
-    faxqclean.enable.spoolInit = mkEnableOption ''
+    faxqclean.enable.spoolInit = mkEnableOption (lib.mdDoc ''
       Purge old files from the spooling area with
-      <filename>faxqclean</filename>
+      {file}`faxqclean`
       each time the spooling area is initialized.
-    '';
+    '');
     faxqclean.enable.frequency = mkOption {
       type = nullOr nonEmptyStr;
       default = null;
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix b/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
index 4506bbbc5eb7..df6d0f49eec4 100644
--- a/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
@@ -96,7 +96,7 @@ let
   hardenService =
     # Add some common systemd service hardening settings,
     # but allow each service (here) to override
-    # settings by explicitely setting those to `null`.
+    # settings by explicitly setting those to `null`.
     # More hardening would be nice but makes
     # customizing hylafax setups very difficult.
     # If at all, it should only be added along
diff --git a/nixpkgs/nixos/modules/services/networking/i2p.nix b/nixpkgs/nixos/modules/services/networking/i2p.nix
index 3b6010531f13..c5c7a955cbd4 100644
--- a/nixpkgs/nixos/modules/services/networking/i2p.nix
+++ b/nixpkgs/nixos/modules/services/networking/i2p.nix
@@ -7,7 +7,7 @@ let
   homeDir = "/var/lib/i2p";
 in {
   ###### interface
-  options.services.i2p.enable = mkEnableOption "I2P router";
+  options.services.i2p.enable = mkEnableOption (lib.mdDoc "I2P router");
 
   ###### implementation
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/networking/i2pd.nix b/nixpkgs/nixos/modules/services/networking/i2pd.nix
index fb83778fcf7e..3f6cb97296b5 100644
--- a/nixpkgs/nixos/modules/services/networking/i2pd.nix
+++ b/nixpkgs/nixos/modules/services/networking/i2pd.nix
@@ -17,10 +17,10 @@ let
   optionalNullInt = o: i: optional (i != null) (intOpt o i);
   optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
 
-  mkEnableTrueOption = name: mkEnableOption name // { default = true; };
+  mkEnableTrueOption = name: mkEnableOption (lib.mdDoc name) // { default = true; };
 
   mkEndpointOpt = name: addr: port: {
-    enable = mkEnableOption name;
+    enable = mkEnableOption (lib.mdDoc name);
     name = mkOption {
       type = types.str;
       default = name;
@@ -247,8 +247,8 @@ in
 
     services.i2pd = {
 
-      enable = mkEnableOption "I2Pd daemon" // {
-        description = ''
+      enable = mkEnableOption (lib.mdDoc "I2Pd daemon") // {
+        description = lib.mdDoc ''
           Enables I2Pd as a running service upon activation.
           Please read http://i2pd.readthedocs.io/en/latest/ for further
           configuration help.
@@ -276,7 +276,7 @@ in
         '';
       };
 
-      logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
+      logCLFTime = mkEnableOption (lib.mdDoc "Full CLF-formatted date and time to log");
 
       address = mkOption {
         type = with types; nullOr str;
@@ -345,14 +345,14 @@ in
       ntcp = mkEnableTrueOption "ntcp";
       ssu = mkEnableTrueOption "ssu";
 
-      notransit = mkEnableOption "notransit" // {
-        description = ''
+      notransit = mkEnableOption (lib.mdDoc "notransit") // {
+        description = lib.mdDoc ''
           Tells the router to not accept transit tunnels during startup.
         '';
       };
 
-      floodfill = mkEnableOption "floodfill" // {
-        description = ''
+      floodfill = mkEnableOption (lib.mdDoc "floodfill") // {
+        description = lib.mdDoc ''
           If the router is declared to be unreachable and needs introduction nodes.
         '';
       };
@@ -383,10 +383,10 @@ in
       };
 
       enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
-      enableIPv6 = mkEnableOption "IPv6 connectivity";
+      enableIPv6 = mkEnableOption (lib.mdDoc "IPv6 connectivity");
       nat = mkEnableTrueOption "NAT bypass";
 
-      upnp.enable = mkEnableOption "UPnP service discovery";
+      upnp.enable = mkEnableOption (lib.mdDoc "UPnP service discovery");
       upnp.name = mkOption {
         type = types.str;
         default = "I2Pd";
@@ -396,17 +396,17 @@ in
       };
 
       precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
-        description = ''
+        description = lib.mdDoc ''
           Whenever to use precomputated tables for ElGamal.
-          <command>i2pd</command> defaults to <literal>false</literal>
+          {command}`i2pd` defaults to `false`
           to save 64M of memory (and looses some performance).
 
-          We default to <literal>true</literal> as that is what most
+          We default to `true` as that is what most
           users want anyway.
         '';
       };
 
-      reseed.verify = mkEnableOption "SU3 signature verification";
+      reseed.verify = mkEnableOption (lib.mdDoc "SU3 signature verification");
 
       reseed.file = mkOption {
         type = with types; nullOr str;
@@ -467,13 +467,13 @@ in
         '';
       };
 
-      trust.enable = mkEnableOption "Explicit trust options";
+      trust.enable = mkEnableOption (lib.mdDoc "Explicit trust options");
 
       trust.family = mkOption {
         type = with types; nullOr str;
         default = null;
         description = lib.mdDoc ''
-          Router Familiy to trust for first hops.
+          Router Family to trust for first hops.
         '';
       };
 
@@ -485,7 +485,7 @@ in
         '';
       };
 
-      trust.hidden = mkEnableOption "Router concealment";
+      trust.hidden = mkEnableOption (lib.mdDoc "Router concealment");
 
       websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
 
@@ -493,9 +493,9 @@ in
       exploratory.outbound = i2cpOpts "exploratory";
 
       ntcp2.enable = mkEnableTrueOption "NTCP2";
-      ntcp2.published = mkEnableOption "NTCP2 publication";
+      ntcp2.published = mkEnableOption (lib.mdDoc "NTCP2 publication");
       ntcp2.port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 0;
         description = lib.mdDoc ''
           Port to listen for incoming NTCP2 connections (0=auto).
@@ -550,7 +550,7 @@ in
         '';
       };
 
-      yggdrasil.enable = mkEnableOption "Yggdrasil";
+      yggdrasil.enable = mkEnableOption (lib.mdDoc "Yggdrasil");
 
       yggdrasil.address = mkOption {
         type = with types; nullOr str;
@@ -563,7 +563,7 @@ in
 
       proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
 
-        auth = mkEnableOption "Webconsole authentication";
+        auth = mkEnableOption (lib.mdDoc "Webconsole authentication");
 
         user = mkOption {
           type = types.str;
@@ -608,7 +608,7 @@ in
       };
       proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
       // {
-        outproxyEnable = mkEnableOption "SOCKS outproxy";
+        outproxyEnable = mkEnableOption (lib.mdDoc "SOCKS outproxy");
         outproxy = mkOption {
           type = types.str;
           default = "127.0.0.1";
diff --git a/nixpkgs/nixos/modules/services/networking/icecream/daemon.nix b/nixpkgs/nixos/modules/services/networking/icecream/daemon.nix
index f94832c4778b..fdd7a139c2fa 100644
--- a/nixpkgs/nixos/modules/services/networking/icecream/daemon.nix
+++ b/nixpkgs/nixos/modules/services/networking/icecream/daemon.nix
@@ -12,7 +12,7 @@ in {
 
     services.icecream.daemon = {
 
-     enable = mkEnableOption "Icecream Daemon";
+     enable = mkEnableOption (lib.mdDoc "Icecream Daemon");
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/networking/icecream/scheduler.nix b/nixpkgs/nixos/modules/services/networking/icecream/scheduler.nix
index 51f3988fe584..33aee1bb19cc 100644
--- a/nixpkgs/nixos/modules/services/networking/icecream/scheduler.nix
+++ b/nixpkgs/nixos/modules/services/networking/icecream/scheduler.nix
@@ -11,7 +11,7 @@ in {
   options = {
 
     services.icecream.scheduler = {
-      enable = mkEnableOption "Icecream Scheduler";
+      enable = mkEnableOption (lib.mdDoc "Icecream Scheduler");
 
       netName = mkOption {
         type = types.nullOr types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/imaginary.nix b/nixpkgs/nixos/modules/services/networking/imaginary.nix
new file mode 100644
index 000000000000..a655903d1031
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/imaginary.nix
@@ -0,0 +1,113 @@
+{ lib, config, pkgs, utils, ... }:
+
+let
+  inherit (lib) mdDoc mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.imaginary;
+in {
+  options.services.imaginary = {
+    enable = mkEnableOption (mdDoc "imaginary image processing microservice");
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = mdDoc ''
+        Bind address. Corresponds to the `-a` flag.
+        Set to `""` to bind to all addresses.
+      '';
+      example = "[::1]";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8088;
+      description = mdDoc "Bind port. Corresponds to the `-p` flag.";
+    };
+
+    settings = mkOption {
+      description = mdDoc ''
+        Command line arguments passed to the imaginary executable, stripped of
+        the prefix `-`. See upstream's
+        [README](https://github.com/h2non/imaginary#command-line-usage) for all
+        options.
+      '';
+      type = types.submodule {
+        freeformType = with types; attrsOf (oneOf [
+          bool
+          int
+          (nonEmptyListOf str)
+          str
+        ]);
+
+        options = {
+          return-size = mkOption {
+            type = types.bool;
+            default = false;
+            description = mdDoc "Return the image size in the HTTP headers.";
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [ {
+      assertion = ! lib.hasAttr "a" cfg.settings;
+      message = "Use services.imaginary.address to specify the -a flag.";
+    } {
+      assertion = ! lib.hasAttr "p" cfg.settings;
+      message = "Use services.imaginary.port to specify the -p flag.";
+    } ];
+
+    systemd.services.imaginary = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = rec {
+        ExecStart = let
+          args = lib.mapAttrsToList (key: val:
+            "-" + key + "=" + lib.concatStringsSep "," (map toString (lib.toList val))
+          ) (cfg.settings // { a = cfg.address; p = cfg.port; });
+        in "${pkgs.imaginary}/bin/imaginary ${utils.escapeSystemdExecArgs args}";
+        ProtectProc = "invisible";
+        BindReadOnlyPaths = lib.optional (cfg.settings ? mount) cfg.settings.mount;
+        CapabilityBoundingSet = if cfg.port < 1024 then
+          [ "CAP_NET_BIND_SERVICE" ]
+        else
+          [ "" ];
+        AmbientCapabilities = CapabilityBoundingSet;
+        NoNewPrivileges = true;
+        DynamicUser = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        TemporaryFileSystem = [ "/:ro" ];
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = cfg.port >= 1024;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+        ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        DevicePolicy = "closed";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ dotlambda ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/inspircd.nix b/nixpkgs/nixos/modules/services/networking/inspircd.nix
index f2464b9a11fa..da193df105b7 100644
--- a/nixpkgs/nixos/modules/services/networking/inspircd.nix
+++ b/nixpkgs/nixos/modules/services/networking/inspircd.nix
@@ -12,7 +12,7 @@ in {
 
   options = {
     services.inspircd = {
-      enable = lib.mkEnableOption "InspIRCd";
+      enable = lib.mkEnableOption (lib.mdDoc "InspIRCd");
 
       package = lib.mkOption {
         type = lib.types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/iperf3.nix b/nixpkgs/nixos/modules/services/networking/iperf3.nix
index 0c308f8e7c09..0a204524e00f 100644
--- a/nixpkgs/nixos/modules/services/networking/iperf3.nix
+++ b/nixpkgs/nixos/modules/services/networking/iperf3.nix
@@ -3,11 +3,11 @@ let
   cfg = config.services.iperf3;
 
   api = {
-    enable = mkEnableOption "iperf3 network throughput testing server";
+    enable = mkEnableOption (lib.mdDoc "iperf3 network throughput testing server");
     port = mkOption {
       type        = types.ints.u16;
       default     = 5201;
-      description = lib.mdDoc "Server port to listen on for iperf3 client requsts.";
+      description = lib.mdDoc "Server port to listen on for iperf3 client requests.";
     };
     affinity = mkOption {
       type        = types.nullOr types.ints.unsigned;
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
index 38312210df25..d9d2e4264dfd 100644
--- a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 doSub() {
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
index f659f3f3e8c1..554b0f7bb8b4 100644
--- a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
@@ -36,74 +36,74 @@ in
 
     services.ircdHybrid = {
 
-      enable = mkEnableOption "IRCD";
+      enable = mkEnableOption (lib.mdDoc "IRCD");
 
       serverName = mkOption {
         default = "hades.arpa";
         type = types.str;
-        description = "
+        description = lib.mdDoc ''
           IRCD server name.
-        ";
+        '';
       };
 
       sid = mkOption {
         default = "0NL";
         type = types.str;
-        description = "
+        description = lib.mdDoc ''
           IRCD server unique ID in a net of servers.
-        ";
+        '';
       };
 
       description = mkOption {
         default = "Hybrid-7 IRC server.";
         type = types.str;
-        description = "
+        description = lib.mdDoc ''
           IRCD server description.
-        ";
+        '';
       };
 
       rsaKey = mkOption {
         default = null;
         example = literalExpression "/root/certificates/irc.key";
         type = types.nullOr types.path;
-        description = "
+        description = lib.mdDoc ''
           IRCD server RSA key.
-        ";
+        '';
       };
 
       certificate = mkOption {
         default = null;
         example = literalExpression "/root/certificates/irc.pem";
         type = types.nullOr types.path;
-        description = "
+        description = lib.mdDoc ''
           IRCD server SSL certificate. There are some limitations - read manual.
-        ";
+        '';
       };
 
       adminEmail = mkOption {
         default = "<bit-bucket@example.com>";
         type = types.str;
         example = "<name@domain.tld>";
-        description = "
+        description = lib.mdDoc ''
           IRCD server administrator e-mail.
-        ";
+        '';
       };
 
       extraIPs = mkOption {
         default = [];
         example = ["127.0.0.1"];
         type = types.listOf types.str;
-        description = "
+        description = lib.mdDoc ''
           Extra IP's to bind.
-        ";
+        '';
       };
 
       extraPort = mkOption {
         default = "7117";
         type = types.str;
-        description = "
+        description = lib.mdDoc ''
           Extra port to avoid filtering.
-        ";
+        '';
       };
 
     };
diff --git a/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix b/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix
index 7414a705a257..d2865a660ead 100644
--- a/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix
+++ b/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix
@@ -4,11 +4,11 @@ let
 in
 {
   options.services.openiscsi = with types; {
-    enable = mkEnableOption "the openiscsi iscsi daemon";
-    enableAutoLoginOut = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc "the openiscsi iscsi daemon");
+    enableAutoLoginOut = mkEnableOption (lib.mdDoc ''
       automatic login and logout of all automatic targets.
       You probably do not want this.
-    '';
+    '');
     discoverPortal = mkOption {
       type = nullOr str;
       default = null;
diff --git a/nixpkgs/nixos/modules/services/networking/iscsi/root-initiator.nix b/nixpkgs/nixos/modules/services/networking/iscsi/root-initiator.nix
index b55fda672521..895467cc674a 100644
--- a/nixpkgs/nixos/modules/services/networking/iscsi/root-initiator.nix
+++ b/nixpkgs/nixos/modules/services/networking/iscsi/root-initiator.nix
@@ -77,7 +77,7 @@ in
     };
 
     extraConfigFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Append an additional file's contents to `/etc/iscsid.conf`. Use a non-store path
         and store passwords in this file. Note: the file specified here must be available
         in the initrd, see: `boot.initrd.secrets`.
@@ -185,6 +185,10 @@ in
         assertion = cfg.loginAll -> cfg.target == null;
         message = "iSCSI target name is set while login on all portals is enabled.";
       }
+      {
+        assertion = !config.boot.initrd.systemd.enable;
+        message = "systemd stage 1 does not support iscsi yet.";
+      }
     ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/iscsi/target.nix b/nixpkgs/nixos/modules/services/networking/iscsi/target.nix
index 5bdac4336ce6..88eaf4590030 100644
--- a/nixpkgs/nixos/modules/services/networking/iscsi/target.nix
+++ b/nixpkgs/nixos/modules/services/networking/iscsi/target.nix
@@ -9,7 +9,7 @@ in
   ###### interface
   options = {
     services.target = with types; {
-      enable = mkEnableOption "the kernel's LIO iscsi target";
+      enable = mkEnableOption (lib.mdDoc "the kernel's LIO iscsi target");
 
       config = mkOption {
         type = attrs;
diff --git a/nixpkgs/nixos/modules/services/networking/ivpn.nix b/nixpkgs/nixos/modules/services/networking/ivpn.nix
new file mode 100644
index 000000000000..6df630c1f194
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ivpn.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.ivpn;
+in
+with lib;
+{
+  options.services.ivpn = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        This option enables iVPN daemon.
+        This sets {option}`networking.firewall.checkReversePath` to "loose", which might be undesirable for security.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "tun" ];
+
+    environment.systemPackages = with pkgs; [ ivpn ivpn-service ];
+
+    # iVPN writes to /etc/iproute2/rt_tables
+    networking.iproute2.enable = true;
+    networking.firewall.checkReversePath = "loose";
+
+    systemd.services.ivpn-service = {
+      description = "iVPN daemon";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+      after = [
+        "network-online.target"
+        "NetworkManager.service"
+        "systemd-resolved.service"
+      ];
+      path = [
+        # Needed for mount
+        "/run/wrappers"
+      ];
+      startLimitBurst = 5;
+      startLimitIntervalSec = 20;
+      serviceConfig = {
+        ExecStart = "${pkgs.ivpn-service}/bin/ivpn-service --logging";
+        Restart = "always";
+        RestartSec = 1;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ ataraxiasjel ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/iwd.nix b/nixpkgs/nixos/modules/services/networking/iwd.nix
index 4921fe2c76cc..993a603c1ed5 100644
--- a/nixpkgs/nixos/modules/services/networking/iwd.nix
+++ b/nixpkgs/nixos/modules/services/networking/iwd.nix
@@ -17,7 +17,16 @@ let
 in
 {
   options.networking.wireless.iwd = {
-    enable = mkEnableOption "iwd";
+    enable = mkEnableOption (lib.mdDoc "iwd");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.iwd;
+      defaultText = lib.literalExpression "pkgs.iwd";
+      description = lib.mdDoc ''
+        The iwd package to use.
+      '';
+    };
 
     settings = mkOption {
       type = ini.type;
@@ -50,11 +59,11 @@ in
     environment.etc."iwd/${configFile.name}".source = configFile;
 
     # for iwctl
-    environment.systemPackages = [ pkgs.iwd ];
+    environment.systemPackages = [ cfg.package ];
 
-    services.dbus.packages = [ pkgs.iwd ];
+    services.dbus.packages = [ cfg.package ];
 
-    systemd.packages = [ pkgs.iwd ];
+    systemd.packages = [ cfg.package ];
 
     systemd.network.links."80-iwd" = {
       matchConfig.Type = "wlan";
@@ -67,5 +76,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ mic92 dtzWill ];
+  meta.maintainers = with lib.maintainers; [ dtzWill ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/jibri/default.nix b/nixpkgs/nixos/modules/services/networking/jibri/default.nix
index 4ac5bae22cc9..a931831fc281 100644
--- a/nixpkgs/nixos/modules/services/networking/jibri/default.nix
+++ b/nixpkgs/nixos/modules/services/networking/jibri/default.nix
@@ -89,7 +89,7 @@ let
 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>";
+    enable = mkEnableOption (lib.mdDoc "Jitsi BRoadcasting Infrastructure. Currently Jibri must be run on a host that is also running {option}`services.jitsi-meet.enable`, so for most use cases it will be simpler to run {option}`services.jitsi-meet.jibri.enable`");
     config = mkOption {
       type = attrs;
       default = { };
@@ -378,7 +378,7 @@ in
         '')
         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
+        ${pkgs.jdk11_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";
diff --git a/nixpkgs/nixos/modules/services/networking/jicofo.nix b/nixpkgs/nixos/modules/services/networking/jicofo.nix
index 3b9038f56741..0886bbe004c4 100644
--- a/nixpkgs/nixos/modules/services/networking/jicofo.nix
+++ b/nixpkgs/nixos/modules/services/networking/jicofo.nix
@@ -4,10 +4,19 @@ with lib;
 
 let
   cfg = config.services.jicofo;
+
+  # HOCON is a JSON superset that some jitsi-meet components use for configuration
+  toHOCON = x: if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}")
+    else if isAttrs x && x ? __hocon_unquoted_string then x.__hocon_unquoted_string
+    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;
+
+  configFile = pkgs.writeText "jicofo.conf" (toHOCON cfg.config);
 in
 {
   options.services.jicofo = with types; {
-    enable = mkEnableOption "Jitsi Conference Focus - component of Jitsi Meet";
+    enable = mkEnableOption (lib.mdDoc "Jitsi Conference Focus - component of Jitsi Meet");
 
     xmppHost = mkOption {
       type = str;
@@ -68,22 +77,34 @@ in
     };
 
     config = mkOption {
-      type = attrsOf str;
+      type = (pkgs.formats.json {}).type;
       default = { };
       example = literalExpression ''
         {
-          "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
+          jicofo.bridge.max-bridge-participants = 42;
         }
       '';
       description = lib.mdDoc ''
-        Contents of the {file}`sip-communicator.properties` configuration file for jicofo.
+        Contents of the {file}`jicofo.conf` configuration file.
       '';
     };
   };
 
   config = mkIf cfg.enable {
-    services.jicofo.config = mapAttrs (_: v: mkDefault v) {
-      "org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc;
+    services.jicofo.config = {
+      jicofo = {
+        bridge.brewery-jid = cfg.bridgeMuc;
+        xmpp = rec {
+          client = {
+            hostname = cfg.xmppHost;
+            username = cfg.userName;
+            domain = cfg.userDomain;
+            password = { __hocon_envvar = "JICOFO_AUTH_PASS"; };
+            xmpp-domain = if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain;
+          };
+          service = client;
+        };
+      };
     };
 
     users.groups.jitsi-meet = {};
@@ -93,6 +114,7 @@ in
         "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi";
         "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo";
         "-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties";
+        "-Dconfig.file" = configFile;
       };
     in
     {
@@ -101,18 +123,13 @@ in
       after = [ "network.target" ];
 
       restartTriggers = [
-        config.environment.etc."jitsi/jicofo/sip-communicator.properties".source
+        configFile
       ];
       environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps);
 
       script = ''
-        ${pkgs.jicofo}/bin/jicofo \
-          --host=${cfg.xmppHost} \
-          --domain=${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain} \
-          --secret=$(cat ${cfg.componentPasswordFile}) \
-          --user_name=${cfg.userName} \
-          --user_domain=${cfg.userDomain} \
-          --user_password=$(cat ${cfg.userPasswordFile})
+        export JICOFO_AUTH_PASS="$(<${cfg.userPasswordFile})"
+        exec "${pkgs.jicofo}/bin/jicofo"
       '';
 
       serviceConfig = {
@@ -140,10 +157,7 @@ in
       };
     };
 
-    environment.etc."jitsi/jicofo/sip-communicator.properties".source =
-      pkgs.writeText "sip-communicator.properties" (
-        generators.toKeyValue {} cfg.config
-      );
+    environment.etc."jitsi/jicofo/sip-communicator.properties".text = "";
     environment.etc."jitsi/jicofo/logging.properties".source =
       mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal";
   };
diff --git a/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix b/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix
index 36e7616d7550..37b0b1e5bf50 100644
--- a/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix
+++ b/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -43,6 +43,7 @@ let
         muc_nickname = xmppConfig.mucNickname;
         disable_certificate_verification = xmppConfig.disableCertificateVerification;
       });
+      apis.rest.enabled = cfg.colibriRestApi;
     };
   };
 
@@ -50,8 +51,13 @@ let
   jvbConfig = recursiveUpdate defaultJvbConfig cfg.config;
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "jitsi-videobridge" "apis" ]
+      "services.jitsi-videobridge.apis was broken and has been migrated into the boolean option services.jitsi-videobridge.colibriRestApi. It is set to false by default, setting it to true will correctly enable the private /colibri rest API."
+    )
+  ];
   options.services.jitsi-videobridge = with types; {
-    enable = mkEnableOption "Jitsi Videobridge, a WebRTC compatible video router";
+    enable = mkEnableOption (lib.mdDoc "Jitsi Videobridge, a WebRTC compatible video router");
 
     config = mkOption {
       type = attrs;
@@ -70,7 +76,7 @@ in
       description = lib.mdDoc ''
         Videobridge configuration.
 
-        See <https://github.com/jitsi/jitsi-videobridge/blob/master/src/main/resources/reference.conf>
+        See <https://github.com/jitsi/jitsi-videobridge/blob/master/jvb/src/main/resources/reference.conf>
         for default configuration with comments.
       '';
     };
@@ -150,7 +156,7 @@ in
         config = {
           hostName = mkDefault name;
           mucNickname = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
-            config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
+            config.networking.fqdnOrHostName
           ));
         };
       }));
@@ -192,14 +198,13 @@ in
       '';
     };
 
-    apis = mkOption {
-      type = with types; listOf str;
+    colibriRestApi = mkOption {
+      type = bool;
       description = lib.mdDoc ''
-        What is passed as --apis= parameter. If this is empty, "none" is passed.
-        Needed for monitoring jitsi.
+        Whether to enable the private rest API for the COLIBRI control interface.
+        Needed for monitoring jitsi, enabling scraping of the /colibri/stats endpoint.
       '';
-      default = [];
-      example = literalExpression "[ \"colibri\" \"rest\" ]";
+      default = false;
     };
   };
 
@@ -233,7 +238,7 @@ in
         "export ${toVarName name}=$(cat ${xmppConfig.passwordFile})\n"
       ) cfg.xmppConfigs))
       + ''
-        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge --apis=${if (cfg.apis == []) then "none" else concatStringsSep "," cfg.apis}
+        ${pkgs.jitsi-videobridge}/bin/jitsi-videobridge
       '';
 
       serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/networking/kea.nix b/nixpkgs/nixos/modules/services/networking/kea.nix
index d674a97391c9..945f4113bd47 100644
--- a/nixpkgs/nixos/modules/services/networking/kea.nix
+++ b/nixpkgs/nixos/modules/services/networking/kea.nix
@@ -41,13 +41,13 @@ in
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea Control Agent";
+          enable = mkEnableOption (lib.mdDoc "Kea Control Agent");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
             description = lib.mdDoc ''
-              List of additonal arguments to pass to the daemon.
+              List of additional arguments to pass to the daemon.
             '';
           };
 
@@ -80,13 +80,13 @@ in
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea DHCP4 server";
+          enable = mkEnableOption (lib.mdDoc "Kea DHCP4 server");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
             description = lib.mdDoc ''
-              List of additonal arguments to pass to the daemon.
+              List of additional arguments to pass to the daemon.
             '';
           };
 
@@ -140,13 +140,13 @@ in
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea DHCP6 server";
+          enable = mkEnableOption (lib.mdDoc "Kea DHCP6 server");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
             description = lib.mdDoc ''
-              List of additonal arguments to pass to the daemon.
+              List of additional arguments to pass to the daemon.
             '';
           };
 
@@ -201,13 +201,13 @@ in
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea DDNS server";
+          enable = mkEnableOption (lib.mdDoc "Kea DDNS server");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
             description = lib.mdDoc ''
-              List of additonal arguments to pass to the daemon.
+              List of additional arguments to pass to the daemon.
             '';
           };
 
@@ -298,7 +298,7 @@ in
       ];
 
       serviceConfig = {
-        ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.dhcp4.extraArgs}";
+        ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.ctrl-agent.extraArgs}";
         KillMode = "process";
         Restart = "on-failure";
       } // commonServiceConfig;
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/default.nix b/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
index 768c8e4b13c7..29fbea5545c3 100644
--- a/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
@@ -84,13 +84,11 @@ let
     ''
   ) vrrpInstances);
 
-  virtualIpLine = (ip:
-    ip.addr
+  virtualIpLine = ip: ip.addr
     + optionalString (notNullOrEmpty ip.brd) " brd ${ip.brd}"
     + optionalString (notNullOrEmpty ip.dev) " dev ${ip.dev}"
     + optionalString (notNullOrEmpty ip.scope) " scope ${ip.scope}"
-    + optionalString (notNullOrEmpty ip.label) " label ${ip.label}"
-  );
+    + optionalString (notNullOrEmpty ip.label) " label ${ip.label}";
 
   notNullOrEmpty = s: !(s == null || s == "");
 
@@ -264,6 +262,19 @@ in
         '';
       };
 
+      secretFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/keepalived.env";
+        description = lib.mdDoc ''
+          Environment variables from this file will be interpolated into the
+          final config file using envsubst with this syntax: `$ENVIRONMENT`
+          or `''${VARIABLE}`.
+          The file should contain lines formatted as `SECRET_VAR=SECRET_VALUE`.
+          This is useful to avoid putting secrets into the nix store.
+        '';
+      };
+
     };
   };
 
@@ -282,7 +293,9 @@ in
       };
     };
 
-    systemd.services.keepalived = {
+    systemd.services.keepalived = let
+      finalConfigFile = if cfg.secretFile == null then keepalivedConf else "/run/keepalived/keepalived.conf";
+    in {
       description = "Keepalive Daemon (LVS and VRRP)";
       after = [ "network.target" "network-online.target" "syslog.target" ];
       wants = [ "network-online.target" ];
@@ -290,8 +303,15 @@ in
         Type = "forking";
         PIDFile = pidFile;
         KillMode = "process";
+        RuntimeDirectory = "keepalived";
+        EnvironmentFile = lib.optional (cfg.secretFile != null) cfg.secretFile;
+        ExecStartPre = lib.optional (cfg.secretFile != null)
+        (pkgs.writeShellScript "keepalived-pre-start" ''
+          umask 077
+          ${pkgs.envsubst}/bin/envsubst -i "${keepalivedConf}" > ${finalConfigFile}
+        '');
         ExecStart = "${pkgs.keepalived}/sbin/keepalived"
-          + " -f ${keepalivedConf}"
+          + " -f ${finalConfigFile}"
           + " -p ${pidFile}"
           + optionalString cfg.snmp.enable " --snmp";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixpkgs/nixos/modules/services/networking/knot.nix b/nixpkgs/nixos/modules/services/networking/knot.nix
index 20f11f0cd592..e97195d82919 100644
--- a/nixpkgs/nixos/modules/services/networking/knot.nix
+++ b/nixpkgs/nixos/modules/services/networking/knot.nix
@@ -18,7 +18,7 @@ let
 
   knot-cli-wrappers = pkgs.stdenv.mkDerivation {
     name = "knot-cli-wrappers";
-    buildInputs = [ pkgs.makeWrapper ];
+    nativeBuildInputs = [ pkgs.makeWrapper ];
     buildCommand = ''
       mkdir -p $out/bin
       makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \
@@ -37,13 +37,13 @@ let
 in {
   options = {
     services.knot = {
-      enable = mkEnableOption "Knot authoritative-only DNS server";
+      enable = mkEnableOption (lib.mdDoc "Knot authoritative-only DNS server");
 
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [];
         description = lib.mdDoc ''
-          List of additional command line paramters for knotd
+          List of additional command line parameters for knotd
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/kresd.nix b/nixpkgs/nixos/modules/services/networking/kresd.nix
index 623e477ca7a8..3ad757133a60 100644
--- a/nixpkgs/nixos/modules/services/networking/kresd.nix
+++ b/nixpkgs/nixos/modules/services/networking/kresd.nix
@@ -59,9 +59,9 @@ in {
     };
     package = mkOption {
       type = types.package;
-      description = "
+      description = lib.mdDoc ''
         knot-resolver package to use.
-      ";
+      '';
       default = pkgs.knot-resolver;
       defaultText = literalExpression "pkgs.knot-resolver";
       example = literalExpression "pkgs.knot-resolver.override { extraFeatures = true; }";
@@ -79,7 +79,7 @@ in {
       example = [ "53" ];
       description = lib.mdDoc ''
         What addresses and ports the server should listen on.
-        For detailed syntax see ListenStream in man systemd.socket.
+        For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
       '';
     };
     listenTLS = mkOption {
@@ -88,7 +88,7 @@ in {
       example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ];
       description = lib.mdDoc ''
         Addresses and ports on which kresd should provide DNS over TLS (see RFC 7858).
-        For detailed syntax see ListenStream in man systemd.socket.
+        For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
       '';
     };
     listenDoH = mkOption {
@@ -97,7 +97,7 @@ in {
       example = [ "198.51.100.1:443" "[2001:db8::1]:443" "443" ];
       description = lib.mdDoc ''
         Addresses and ports on which kresd should provide DNS over HTTPS/2 (see RFC 8484).
-        For detailed syntax see ListenStream in man systemd.socket.
+        For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
       '';
     };
     instances = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/legit.nix b/nixpkgs/nixos/modules/services/networking/legit.nix
new file mode 100644
index 000000000000..90234f3955e8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/legit.nix
@@ -0,0 +1,182 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib)
+    literalExpression
+    mkEnableOption
+    mdDoc
+    mkIf
+    mkOption
+    mkPackageOptionMD
+    optionalAttrs
+    optional
+    types;
+
+  cfg = config.services.legit;
+
+  yaml = pkgs.formats.yaml { };
+  configFile = yaml.generate "legit.yaml" cfg.settings;
+
+  defaultStateDir = "/var/lib/legit";
+  defaultStaticDir = "${cfg.settings.repo.scanPath}/static";
+  defaultTemplatesDir = "${cfg.settings.repo.scanPath}/templates";
+in
+{
+  options.services.legit = {
+    enable = mkEnableOption (mdDoc "legit git web frontend");
+
+    package = mkPackageOptionMD pkgs "legit-web" { };
+
+    user = mkOption {
+      type = types.str;
+      default = "legit";
+      description = mdDoc "User account under which legit runs.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "legit";
+      description = mdDoc "Group account under which legit runs.";
+    };
+
+    settings = mkOption {
+      default = { };
+      description = mdDoc ''
+        The primary legit configuration. See the
+        [sample configuration](https://github.com/icyphox/legit/blob/master/config.yaml)
+        for possible values.
+      '';
+      type = types.submodule {
+        options.repo = {
+          scanPath = mkOption {
+            type = types.path;
+            default = defaultStateDir;
+            description = mdDoc "Directory where legit will scan for repositories.";
+          };
+          readme = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            description = mdDoc "Readme files to look for.";
+          };
+          mainBranch = mkOption {
+            type = types.listOf types.str;
+            default = [ "main" "master" ];
+            description = mdDoc "Main branch to look for.";
+          };
+          ignore = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            description = mdDoc "Repositories to ignore.";
+          };
+        };
+        options.dirs = {
+          templates = mkOption {
+            type = types.path;
+            default = "${pkgs.legit-web}/lib/legit/templates";
+            defaultText = literalExpression ''"''${pkgs.legit-web}/lib/legit/templates"'';
+            description = mdDoc "Directories where template files are located.";
+          };
+          static = mkOption {
+            type = types.path;
+            default = "${pkgs.legit-web}/lib/legit/static";
+            defaultText = literalExpression ''"''${pkgs.legit-web}/lib/legit/static"'';
+            description = mdDoc "Directories where static files are located.";
+          };
+        };
+        options.meta = {
+          title = mkOption {
+            type = types.str;
+            default = "legit";
+            description = mdDoc "Website title.";
+          };
+          description = mkOption {
+            type = types.str;
+            default = "git frontend";
+            description = mdDoc "Website description.";
+          };
+        };
+        options.server = {
+          name = mkOption {
+            type = types.str;
+            default = "localhost";
+            description = mdDoc "Server name.";
+          };
+          host = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            description = mdDoc "Host address.";
+          };
+          port = mkOption {
+            type = types.port;
+            default = 5555;
+            description = mdDoc "Legit port.";
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = optionalAttrs (cfg.group == "legit") {
+      "${cfg.group}" = { };
+    };
+
+    users.users = optionalAttrs (cfg.user == "legit") {
+      "${cfg.user}" = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    systemd.services.legit = {
+      description = "legit git frontend";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ configFile ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/legit -config ${configFile}";
+        Restart = "always";
+
+        WorkingDirectory = cfg.settings.repo.scanPath;
+        StateDirectory = [ ] ++
+          optional (cfg.settings.repo.scanPath == defaultStateDir) "legit" ++
+          optional (cfg.settings.dirs.static == defaultStaticDir) "legit/static" ++
+          optional (cfg.settings.dirs.templates == defaultTemplatesDir) "legit/templates";
+
+        # Hardening
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = 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";
+        ReadWritePaths = cfg.settings.repo.scanPath;
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/libreswan.nix b/nixpkgs/nixos/modules/services/networking/libreswan.nix
index 08ffcca8a5ac..785729d8f742 100644
--- a/nixpkgs/nixos/modules/services/networking/libreswan.nix
+++ b/nixpkgs/nixos/modules/services/networking/libreswan.nix
@@ -47,7 +47,7 @@ in
 
     services.libreswan = {
 
-      enable = mkEnableOption "Libreswan IPsec service";
+      enable = mkEnableOption (lib.mdDoc "Libreswan IPsec service");
 
       configSetup = mkOption {
         type = types.lines;
@@ -93,12 +93,12 @@ in
             ''';
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           A set of policies to apply to the IPsec connections.
 
-          <note><para>
-            The policy name must match the one of connection it needs to apply to.
-          </para></note>
+          ::: {.note}
+          The policy name must match the one of connection it needs to apply to.
+          :::
         '';
       };
 
@@ -106,7 +106,7 @@ in
         type = types.bool;
         default = true;
         description = lib.mdDoc ''
-          Whether to disable send and accept redirects for all nework interfaces.
+          Whether to disable send and accept redirects for all network interfaces.
           See the Libreswan [
           FAQ](https://libreswan.org/wiki/FAQ#Why_is_it_recommended_to_disable_send_redirects_in_.2Fproc.2Fsys.2Fnet_.3F) page for why this is recommended.
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/lldpd.nix b/nixpkgs/nixos/modules/services/networking/lldpd.nix
index 41a3713fcefe..b7ac99d75d75 100644
--- a/nixpkgs/nixos/modules/services/networking/lldpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/lldpd.nix
@@ -9,7 +9,7 @@ in
 
 {
   options.services.lldpd = {
-    enable = mkEnableOption "Link Layer Discovery Protocol Daemon";
+    enable = mkEnableOption (lib.mdDoc "Link Layer Discovery Protocol Daemon");
 
     extraArgs = mkOption {
       type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/lokinet.nix b/nixpkgs/nixos/modules/services/networking/lokinet.nix
index 6dc33faa82b4..f6bc314ed260 100644
--- a/nixpkgs/nixos/modules/services/networking/lokinet.nix
+++ b/nixpkgs/nixos/modules/services/networking/lokinet.nix
@@ -7,7 +7,7 @@ let
   configFile = settingsFormat.generate "lokinet.ini" (lib.filterAttrsRecursive (n: v: v != null) cfg.settings);
 in with lib; {
   options.services.lokinet = {
-    enable = mkEnableOption "Lokinet daemon";
+    enable = mkEnableOption (lib.mdDoc "Lokinet daemon");
 
     package = mkOption {
       type = types.package;
@@ -65,9 +65,9 @@ in with lib; {
                   exit-node = [ "example.loki" ];              # maps all exit traffic to example.loki
                   exit-node = [ "example.loki:100.0.0.0/24" ]; # maps 100.0.0.0/24 to example.loki
                 '';
-                description = ''
+                description = lib.mdDoc ''
                   Specify a `.loki` address and an optional ip range to use as an exit broker.
-                  See <link xlink:href="http://probably.loki/wiki/index.php?title=Exit_Nodes"/> for
+                  See <http://probably.loki/wiki/index.php?title=Exit_Nodes> for
                   a list of exit nodes.
                 '';
               };
@@ -95,7 +95,7 @@ in with lib; {
           network.exit-node = [ "example.loki" "example2.loki" ];
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configuration for Lokinet.
         Currently, the best way to view the available settings is by
         generating a config file using `lokinet -g`.
diff --git a/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix b/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix
index 44f93a5c56ec..d8e32eb997e8 100644
--- a/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix
+++ b/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.lxd-image-server = {
-      enable = mkEnableOption "lxd-image-server";
+      enable = mkEnableOption (lib.mdDoc "lxd-image-server");
 
       group = mkOption {
         type = types.str;
@@ -31,7 +31,7 @@ in
       };
 
       nginx = {
-        enable = mkEnableOption "nginx";
+        enable = mkEnableOption (lib.mdDoc "nginx");
         domain = mkOption {
           type = types.str;
           description = lib.mdDoc "Domain to use for nginx virtual host.";
@@ -87,7 +87,7 @@ in
         };
       };
     })
-    # this is seperate so it can be enabled on mirrored hosts
+    # this is separate 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 = {
diff --git a/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
index 09d357cd2b6e..9dd1f62350af 100644
--- a/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
+++ b/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
@@ -9,7 +9,7 @@ let
 in
 {
   options.services.magic-wormhole-mailbox-server = {
-    enable = mkEnableOption "Enable Magic Wormhole Mailbox Server";
+    enable = mkEnableOption (lib.mdDoc "Magic Wormhole Mailbox Server");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/networking/matterbridge.nix b/nixpkgs/nixos/modules/services/networking/matterbridge.nix
index f75be9b4e37b..2921074fcd2b 100644
--- a/nixpkgs/nixos/modules/services/networking/matterbridge.nix
+++ b/nixpkgs/nixos/modules/services/networking/matterbridge.nix
@@ -17,7 +17,7 @@ in
 {
   options = {
     services.matterbridge = {
-      enable = mkEnableOption "Matterbridge chat platform bridge";
+      enable = mkEnableOption (lib.mdDoc "Matterbridge chat platform bridge");
 
       configPath = mkOption {
         type = with types; nullOr str;
diff --git a/nixpkgs/nixos/modules/services/networking/minidlna.nix b/nixpkgs/nixos/modules/services/networking/minidlna.nix
index 0cac41f58da3..d0de6cd4fdc6 100644
--- a/nixpkgs/nixos/modules/services/networking/minidlna.nix
+++ b/nixpkgs/nixos/modules/services/networking/minidlna.nix
@@ -1,6 +1,5 @@
 # Module for MiniDLNA, a simple DLNA server.
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
@@ -17,7 +16,7 @@ in
     description = lib.mdDoc ''
       Whether to enable MiniDLNA, a simple DLNA server.
       It serves media files such as video and music to DLNA client devices
-      such as televisions and media players. If you use the firewall consider
+      such as televisions and media players. If you use the firewall, consider
       adding the following: `services.minidlna.openFirewall = true;`
     '';
   };
@@ -34,8 +33,7 @@ in
     default = {};
     description = lib.mdDoc ''
       The contents of MiniDLNA's configuration file.
-      When the service is activated, a basic template is generated
-      from the current options opened here.
+      When the service is activated, a basic template is generated from the current options opened here.
     '';
     type = types.submodule {
       freeformType = settingsFormat.type;
@@ -46,10 +44,8 @@ in
         example = [ "/data/media" "V,/home/alice/video" ];
         description = lib.mdDoc ''
           Directories to be scanned for media files.
-          The prefixes `A,`,`V,` and
-          `P,` restrict a directory to audio, video
-          or image files. The directories must be accessible to the
-          `minidlna` user account.
+          The `A,` `V,` `P,` prefixes restrict a directory to audio, video or image files.
+          The directories must be accessible to the `minidlna` user account.
         '';
       };
       options.notify_interval = mkOption {
@@ -57,18 +53,8 @@ in
         default = 90000;
         description = lib.mdDoc ''
           The interval between announces (in seconds).
-          Instead of waiting on announces, one can open port UDP 1900 or
-          set `openFirewall` option to use SSDP discovery.
-          Furthermore announce interval has now been set as 90000 in order
-          to prevent disconnects with certain clients and to rely solely
-          on the SSDP method.
-
-          Lower values (e.g. 60 seconds) should be used if one does not
-          want to utilize SSDP. By default miniDLNA will announce its
-          presence on the network approximately every 15 minutes. Many
-          people prefer shorter announce intervals on their home networks,
-          especially when DLNA clients are started on demand.
-
+          Instead of waiting for announces, you should set `openFirewall` option to use SSDP discovery.
+          Lower values (e.g. 30 seconds) should be used if your network blocks the discovery unicast.
           Some relevant information can be found here:
           https://sourceforge.net/p/minidlna/discussion/879957/thread/1389d197/
         '';
@@ -86,15 +72,15 @@ in
       };
       options.friendly_name = mkOption {
         type = types.str;
-        default = "${config.networking.hostName} MiniDLNA";
+        default = config.networking.hostName;
         defaultText = literalExpression "config.networking.hostName";
         example = "rpi3";
         description = lib.mdDoc "Name that the DLNA server presents to clients.";
       };
       options.root_container = mkOption {
         type = types.str;
-        default = ".";
-        example = "B";
+        default = "B";
+        example = ".";
         description = lib.mdDoc "Use a different container as the root of the directory tree presented to clients.";
       };
       options.log_level = mkOption {
@@ -116,7 +102,7 @@ in
       options.wide_links = mkOption {
         type = types.enum [ "yes" "no" ];
         default = "no";
-        description = lib.mdDoc "Set this to yes to allow symlinks that point outside user-defined media_dirs.";
+        description = lib.mdDoc "Set this to yes to allow symlinks that point outside user-defined `media_dir`.";
       };
     };
   };
@@ -144,22 +130,19 @@ in
 
     users.groups.minidlna.gid = config.ids.gids.minidlna;
 
-    systemd.services.minidlna =
-      { description = "MiniDLNA Server";
+    systemd.services.minidlna = {
+      description = "MiniDLNA Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
 
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-
-        serviceConfig =
-          { User = "minidlna";
-            Group = "minidlna";
-            CacheDirectory = "minidlna";
-            RuntimeDirectory = "minidlna";
-            PIDFile = "/run/minidlna/pid";
-            ExecStart =
-              "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid" +
-              " -f ${settingsFile}";
-          };
+      serviceConfig = {
+        User = "minidlna";
+        Group = "minidlna";
+        CacheDirectory = "minidlna";
+        RuntimeDirectory = "minidlna";
+        PIDFile = "/run/minidlna/pid";
+        ExecStart = "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid -f ${settingsFile}";
       };
+    };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/miniupnpd.nix b/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
index 524270edd1c6..64aacaf35040 100644
--- a/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
@@ -19,7 +19,7 @@ in
 {
   options = {
     services.miniupnpd = {
-      enable = mkEnableOption "MiniUPnP daemon";
+      enable = mkEnableOption (lib.mdDoc "MiniUPnP daemon");
 
       externalInterface = mkOption {
         type = types.str;
@@ -36,7 +36,7 @@ in
         '';
       };
 
-      natpmp = mkEnableOption "NAT-PMP support";
+      natpmp = mkEnableOption (lib.mdDoc "NAT-PMP support");
 
       upnp = mkOption {
         default = true;
diff --git a/nixpkgs/nixos/modules/services/networking/miredo.nix b/nixpkgs/nixos/modules/services/networking/miredo.nix
index 5e42678c32f3..d15a55b4d7d6 100644
--- a/nixpkgs/nixos/modules/services/networking/miredo.nix
+++ b/nixpkgs/nixos/modules/services/networking/miredo.nix
@@ -20,7 +20,7 @@ in
 
     services.miredo = {
 
-      enable = mkEnableOption "the Miredo IPv6 tunneling service";
+      enable = mkEnableOption (lib.mdDoc "the Miredo IPv6 tunneling service");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix b/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
index 8b490f0248bc..8f8d5f5c4d35 100644
--- a/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
+++ b/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
@@ -12,7 +12,7 @@ in {
 
     services.mjpg-streamer = {
 
-      enable = mkEnableOption "mjpg-streamer webcam streamer";
+      enable = mkEnableOption (lib.mdDoc "mjpg-streamer webcam streamer");
 
       inputPlugin = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/mmsd.nix b/nixpkgs/nixos/modules/services/networking/mmsd.nix
new file mode 100644
index 000000000000..7e262a9326c1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mmsd.nix
@@ -0,0 +1,38 @@
+{ pkgs, lib, config, ... }:
+with lib;
+let
+  cfg = config.services.mmsd;
+  dbusServiceFile = pkgs.writeTextDir "share/dbus-1/services/org.ofono.mms.service" ''
+    [D-BUS Service]
+    Name=org.ofono.mms
+    SystemdService=dbus-org.ofono.mms.service
+
+    # Exec= is still required despite SystemdService= being used:
+    # https://github.com/freedesktop/dbus/blob/ef55a3db0d8f17848f8a579092fb05900cc076f5/test/data/systemd-activation/com.example.SystemdActivatable1.service
+    Exec=${pkgs.coreutils}/bin/false mmsd
+  '';
+in
+{
+  options.services.mmsd = {
+    enable = mkEnableOption (mdDoc "Multimedia Messaging Service Daemon");
+    extraArgs = mkOption {
+      type = with types; listOf str;
+      description = mdDoc "Extra arguments passed to `mmsd-tng`";
+      default = [];
+      example = ["--debug"];
+    };
+  };
+  config = mkIf cfg.enable {
+    services.dbus.packages = [ dbusServiceFile ];
+    systemd.user.services.mmsd = {
+      after = [ "ModemManager.service" ];
+      aliases = [ "dbus-org.ofono.mms.service" ];
+      serviceConfig = {
+        Type = "dbus";
+        ExecStart = "${pkgs.mmsd-tng}/bin/mmsdtng " + escapeShellArgs cfg.extraArgs;
+        BusName = "org.ofono.mms";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/monero.nix b/nixpkgs/nixos/modules/services/networking/monero.nix
index 032f6df4e79e..0de02882acab 100644
--- a/nixpkgs/nixos/modules/services/networking/monero.nix
+++ b/nixpkgs/nixos/modules/services/networking/monero.nix
@@ -50,7 +50,7 @@ in
 
     services.monero = {
 
-      enable = mkEnableOption "Monero node daemon";
+      enable = mkEnableOption (lib.mdDoc "Monero node daemon");
 
       dataDir = mkOption {
         type = types.str;
@@ -181,7 +181,7 @@ in
       exclusiveNodes = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           List of peer IP addresses to connect to *only*.
           If given the other peer options will be ignored.
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/morty.nix b/nixpkgs/nixos/modules/services/networking/morty.nix
index cc5d7998f36b..72514764a7c6 100644
--- a/nixpkgs/nixos/modules/services/networking/morty.nix
+++ b/nixpkgs/nixos/modules/services/networking/morty.nix
@@ -17,7 +17,7 @@ in
     services.morty = {
 
       enable = mkEnableOption
-        "Morty proxy server. See https://github.com/asciimoo/morty";
+        (lib.mdDoc "Morty proxy server. See https://github.com/asciimoo/morty");
 
       ipv6 = mkOption {
         type = types.bool;
@@ -50,7 +50,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
         description = lib.mdDoc "Listing port";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/mosquitto.nix b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
index 49f0cc90122b..a4fd2fd7c89f 100644
--- a/nixpkgs/nixos/modules/services/networking/mosquitto.nix
+++ b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
@@ -56,8 +56,10 @@ let
         default = null;
         description = mdDoc ''
           Specifies the hashed password for the MQTT User.
-          To generate hashed password install `mosquitto`
-          package and use `mosquitto_passwd`.
+          To generate hashed password install the `mosquitto`
+          package and use `mosquitto_passwd`, then extract
+          the second field (after the `:`) from the generated
+          file.
         '';
       };
 
@@ -68,8 +70,9 @@ let
         description = mdDoc ''
           Specifies the path to a file containing the
           hashed password for the MQTT user.
-          To generate hashed password install `mosquitto`
-          package and use `mosquitto_passwd`.
+          To generate hashed password install the `mosquitto`
+          package and use `mosquitto_passwd`, then remove the
+          `username:` prefix from the generated file.
         '';
       };
 
@@ -443,7 +446,7 @@ let
   };
 
   globalOptions = with types; {
-    enable = mkEnableOption "the MQTT Mosquitto broker";
+    enable = mkEnableOption (lib.mdDoc "the MQTT Mosquitto broker");
 
     package = mkOption {
       type = package;
@@ -476,7 +479,7 @@ let
         Directories to be scanned for further config files to include.
         Directories will processed in the order given,
         `*.conf` files in the directory will be
-        read in case-sensistive alphabetical order.
+        read in case-sensitive alphabetical order.
       '';
       default = [];
     };
@@ -668,8 +671,6 @@ in
 
   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;
+    doc = ./mosquitto.md;
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/mosquitto.xml b/nixpkgs/nixos/modules/services/networking/mosquitto.xml
deleted file mode 100644
index d16ab28c0269..000000000000
--- a/nixpkgs/nixos/modules/services/networking/mosquitto.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-<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/mozillavpn.nix b/nixpkgs/nixos/modules/services/networking/mozillavpn.nix
index 71cbb0470412..cf962879b421 100644
--- a/nixpkgs/nixos/modules/services/networking/mozillavpn.nix
+++ b/nixpkgs/nixos/modules/services/networking/mozillavpn.nix
@@ -1,13 +1,8 @@
 { config, lib, pkgs, ... }:
 
 {
-  options.services.mozillavpn.enable = lib.mkOption {
-    type = lib.types.bool;
-    default = false;
-    description = lib.mdDoc ''
-      Enable the Mozilla VPN daemon.
-    '';
-  };
+  options.services.mozillavpn.enable =
+    lib.mkEnableOption (lib.mdDoc "Mozilla VPN daemon");
 
   config = lib.mkIf config.services.mozillavpn.enable {
     environment.systemPackages = [ pkgs.mozillavpn ];
diff --git a/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix b/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
index 7ff1cb0b2da3..3dd197697b23 100644
--- a/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
@@ -37,10 +37,10 @@ in
 
     services.mtprotoproxy = {
 
-      enable = mkEnableOption "mtprotoproxy";
+      enable = mkEnableOption (lib.mdDoc "mtprotoproxy");
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3256;
         description = lib.mdDoc ''
           TCP port to accept mtproto connections on.
diff --git a/nixpkgs/nixos/modules/services/networking/mtr-exporter.nix b/nixpkgs/nixos/modules/services/networking/mtr-exporter.nix
index b95af08d3634..43ebbbe96d05 100644
--- a/nixpkgs/nixos/modules/services/networking/mtr-exporter.nix
+++ b/nixpkgs/nixos/modules/services/networking/mtr-exporter.nix
@@ -9,7 +9,7 @@ in {
   options = {
     services = {
       mtr-exporter = {
-        enable = mkEnableOption "a Prometheus exporter for MTR";
+        enable = mkEnableOption (lib.mdDoc "a Prometheus exporter for MTR");
 
         target = mkOption {
           type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix
index ca60682b4b8b..82e68bf92af1 100644
--- a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix
+++ b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix
@@ -4,24 +4,54 @@ let
 in
 with lib;
 {
-  options.services.mullvad-vpn.enable = mkOption {
-    type = types.bool;
-    default = false;
-    description = lib.mdDoc ''
-      This option enables Mullvad VPN daemon.
-      This sets {option}`networking.firewall.checkReversePath` to "loose", which might be undesirable for security.
-    '';
+  options.services.mullvad-vpn = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        This option enables Mullvad VPN daemon.
+        This sets {option}`networking.firewall.checkReversePath` to "loose", which might be undesirable for security.
+      '';
+    };
+
+    enableExcludeWrapper = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        This option activates the wrapper that allows the use of mullvad-exclude.
+        Might have minor security impact, so consider disabling if you do not use the feature.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.mullvad;
+      defaultText = literalExpression "pkgs.mullvad";
+      description = lib.mdDoc ''
+        The Mullvad package to use. `pkgs.mullvad` only provides the CLI tool, `pkgs.mullvad-vpn` provides both the CLI and the GUI.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
     boot.kernelModules = [ "tun" ];
 
+    environment.systemPackages = [ cfg.package ];
+
     # mullvad-daemon writes to /etc/iproute2/rt_tables
     networking.iproute2.enable = true;
 
     # See https://github.com/NixOS/nixpkgs/issues/113589
     networking.firewall.checkReversePath = "loose";
 
+    # See https://github.com/NixOS/nixpkgs/issues/176603
+    security.wrappers.mullvad-exclude = mkIf cfg.enableExcludeWrapper {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${cfg.package}/bin/mullvad-exclude";
+    };
+
     systemd.services.mullvad-daemon = {
       description = "Mullvad VPN daemon";
       wantedBy = [ "multi-user.target" ];
@@ -39,12 +69,12 @@ with lib;
       startLimitBurst = 5;
       startLimitIntervalSec = 20;
       serviceConfig = {
-        ExecStart = "${pkgs.mullvad-vpn}/bin/mullvad-daemon -v --disable-stdout-timestamps";
+        ExecStart = "${cfg.package}/bin/mullvad-daemon -v --disable-stdout-timestamps";
         Restart = "always";
         RestartSec = 1;
       };
     };
   };
 
-  meta.maintainers = with maintainers; [ ymarkus ];
+  meta.maintainers = with maintainers; [ patricksjackson ymarkus ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/multipath.nix b/nixpkgs/nixos/modules/services/networking/multipath.nix
index 3dc6be96e7a7..bd403e109c2a 100644
--- a/nixpkgs/nixos/modules/services/networking/multipath.nix
+++ b/nixpkgs/nixos/modules/services/networking/multipath.nix
@@ -22,13 +22,13 @@ in {
 
   options.services.multipath = with types; {
 
-    enable = mkEnableOption "the device mapper multipath (DM-MP) daemon";
+    enable = mkEnableOption (lib.mdDoc "the device mapper multipath (DM-MP) daemon");
 
     package = mkOption {
       type = package;
       description = lib.mdDoc "multipath-tools package to use";
       default = pkgs.multipath-tools;
-      defaultText = "pkgs.multipath-tools";
+      defaultText = lib.literalExpression "pkgs.multipath-tools";
     };
 
     devices = mkOption {
@@ -513,23 +513,22 @@ in {
         ${indentLines 2 devices}
         }
 
-        ${optionalString (!isNull defaults) ''
+        ${optionalString (defaults != null) ''
           defaults {
           ${indentLines 2 defaults}
-            multipath_dir ${cfg.package}/lib/multipath
           }
         ''}
-        ${optionalString (!isNull blacklist) ''
+        ${optionalString (blacklist != null) ''
           blacklist {
           ${indentLines 2 blacklist}
           }
         ''}
-        ${optionalString (!isNull blacklist_exceptions) ''
+        ${optionalString (blacklist_exceptions != null) ''
           blacklist_exceptions {
           ${indentLines 2 blacklist_exceptions}
           }
         ''}
-        ${optionalString (!isNull overrides) ''
+        ${optionalString (overrides != null) ''
           overrides {
           ${indentLines 2 overrides}
           }
diff --git a/nixpkgs/nixos/modules/services/networking/murmur.nix b/nixpkgs/nixos/modules/services/networking/murmur.nix
index 807caceb5bfe..ebade7aa8e40 100644
--- a/nixpkgs/nixos/modules/services/networking/murmur.nix
+++ b/nixpkgs/nixos/modules/services/networking/murmur.nix
@@ -42,6 +42,8 @@ let
     ${if cfg.sslKey  == "" then "" else "sslKey="+cfg.sslKey}
     ${if cfg.sslCa   == "" then "" else "sslCA="+cfg.sslCa}
 
+    ${lib.optionalString (cfg.dbus != null) "dbus=${cfg.dbus}"}
+
     ${cfg.extraConfig}
   '';
 in
@@ -219,7 +221,7 @@ in
       registerHostname = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           DNS hostname where your server can be reached. This is only
           needed if you want your server to be accessed by its
           hostname and not IP - but the name *must* resolve on the
@@ -261,27 +263,33 @@ in
         type = types.nullOr types.path;
         default = null;
         example = "/var/lib/murmur/murmurd.env";
-        description = ''
-          Environment file as defined in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+        description = lib.mdDoc ''
+          Environment file as defined in {manpage}`systemd.exec(5)`.
 
           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.
 
-          <programlisting>
+          ```
             # snippet of murmur-related config
             services.murmur.password = "$MURMURD_PASSWORD";
-          </programlisting>
+          ```
 
-          <programlisting>
+          ```
             # content of the environment file
             MURMURD_PASSWORD=verysecretpassword
-          </programlisting>
+          ```
 
           Note that this file needs to be available on the host on which
-          <literal>murmur</literal> is running.
+          `murmur` is running.
         '';
       };
+
+      dbus = mkOption {
+        type = types.enum [ null "session" "system" ];
+        default = null;
+        description = lib.mdDoc "Enable D-Bus remote control. Set to the bus you want Murmur to connect to.";
+      };
     };
   };
 
@@ -305,7 +313,7 @@ in
     systemd.services.murmur = {
       description = "Murmur Chat Service";
       wantedBy    = [ "multi-user.target" ];
-      after       = [ "network-online.target" ];
+      after       = [ "network.target" ];
       preStart    = ''
         ${pkgs.envsubst}/bin/envsubst \
           -o /run/murmur/murmurd.ini \
@@ -325,5 +333,27 @@ in
         Group = "murmur";
       };
     };
+
+    # currently not included in upstream package, addition requested at
+    # https://github.com/mumble-voip/mumble/issues/6078
+    services.dbus.packages = mkIf (cfg.dbus == "system") [(pkgs.writeTextFile {
+      name = "murmur-dbus-policy";
+      text = ''
+        <!DOCTYPE busconfig PUBLIC
+          "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+          "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+        <busconfig>
+          <policy user="murmur">
+            <allow own="net.sourceforge.mumble.murmur"/>
+          </policy>
+
+          <policy context="default">
+            <allow send_destination="net.sourceforge.mumble.murmur"/>
+            <allow receive_sender="net.sourceforge.mumble.murmur"/>
+          </policy>
+        </busconfig>
+      '';
+      destination = "/share/dbus-1/system.d/murmur.conf";
+    })];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/mxisd.nix b/nixpkgs/nixos/modules/services/networking/mxisd.nix
index 9ddc2094b6df..528a51c1f3af 100644
--- a/nixpkgs/nixos/modules/services/networking/mxisd.nix
+++ b/nixpkgs/nixos/modules/services/networking/mxisd.nix
@@ -37,7 +37,7 @@ let
 in {
   options = {
     services.mxisd = {
-      enable = mkEnableOption "matrix federated identity server";
+      enable = mkEnableOption (lib.mdDoc "matrix federated identity server");
 
       package = mkOption {
         type = types.package;
@@ -49,9 +49,9 @@ in {
       environmentFile = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to an environment-file which may contain secrets to be
-          substituted via <package>envsubst</package>.
+          substituted via `envsubst`.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/namecoind.nix b/nixpkgs/nixos/modules/services/networking/namecoind.nix
index 45a90741465a..085d6c5fe282 100644
--- a/nixpkgs/nixos/modules/services/networking/namecoind.nix
+++ b/nixpkgs/nixos/modules/services/networking/namecoind.nix
@@ -44,7 +44,7 @@ in
 
     services.namecoind = {
 
-      enable = mkEnableOption "namecoind, Namecoin client";
+      enable = mkEnableOption (lib.mdDoc "namecoind, Namecoin client");
 
       wallet = mkOption {
         type = types.path;
diff --git a/nixpkgs/nixos/modules/services/networking/nar-serve.nix b/nixpkgs/nixos/modules/services/networking/nar-serve.nix
index 09f019d41bb7..beee53c8a242 100644
--- a/nixpkgs/nixos/modules/services/networking/nar-serve.nix
+++ b/nixpkgs/nixos/modules/services/networking/nar-serve.nix
@@ -10,7 +10,7 @@ in
   };
   options = {
     services.nar-serve = {
-      enable = mkEnableOption "Serve NAR file contents via HTTP";
+      enable = mkEnableOption (lib.mdDoc "Serve NAR file contents via HTTP");
 
       port = mkOption {
         type = types.port;
@@ -23,7 +23,7 @@ in
       cacheURL = mkOption {
         type = types.str;
         default = "https://cache.nixos.org/";
-        description = ''
+        description = lib.mdDoc ''
           Binary cache URL to connect to.
 
           The URL format is compatible with the nix remote url style, such as:
diff --git a/nixpkgs/nixos/modules/services/networking/nat-iptables.nix b/nixpkgs/nixos/modules/services/networking/nat-iptables.nix
new file mode 100644
index 000000000000..d1bed401feeb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nat-iptables.nix
@@ -0,0 +1,191 @@
+# This module enables Network Address Translation (NAT).
+# XXX: todo: support multiple upstream links
+# see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.nat;
+
+  mkDest = externalIP:
+    if externalIP == null
+    then "-j MASQUERADE"
+    else "-j SNAT --to-source ${externalIP}";
+  dest = mkDest cfg.externalIP;
+  destIPv6 = mkDest cfg.externalIPv6;
+
+  # Whether given IP (plus optional port) is an IPv6.
+  isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
+
+  helpers = import ./helpers.nix { inherit config lib; };
+
+  flushNat = ''
+    ${helpers}
+    ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
+    ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
+    ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
+    ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
+    ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
+    ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
+    ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
+    ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
+    ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
+
+    ${cfg.extraStopCommands}
+  '';
+
+  mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: ''
+    # We can't match on incoming interface in POSTROUTING, so
+    # mark packets coming from the internal interfaces.
+    ${concatMapStrings (iface: ''
+      ${iptables} -w -t nat -A nixos-nat-pre \
+        -i '${iface}' -j MARK --set-mark 1
+    '') cfg.internalInterfaces}
+
+    # NAT the marked packets.
+    ${optionalString (cfg.internalInterfaces != []) ''
+      ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
+        ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
+    ''}
+
+    # NAT packets coming from the internal IPs.
+    ${concatMapStrings (range: ''
+      ${iptables} -w -t nat -A nixos-nat-post \
+        -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
+    '') internalIPs}
+
+    # NAT from external ports to internal ports.
+    ${concatMapStrings (fwd: ''
+      ${iptables} -w -t nat -A nixos-nat-pre \
+        -i ${toString cfg.externalInterface} -p ${fwd.proto} \
+        --dport ${builtins.toString fwd.sourcePort} \
+        -j DNAT --to-destination ${fwd.destination}
+
+      ${concatMapStrings (loopbackip:
+        let
+          matchIP          = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
+          m                = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
+          destinationIP    = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
+          destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
+        in ''
+          # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
+          ${iptables} -w -t nat -A nixos-nat-out \
+            -d ${loopbackip} -p ${fwd.proto} \
+            --dport ${builtins.toString fwd.sourcePort} \
+            -j DNAT --to-destination ${fwd.destination}
+
+          # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
+          ${iptables} -w -t nat -A nixos-nat-pre \
+            -d ${loopbackip} -p ${fwd.proto} \
+            --dport ${builtins.toString fwd.sourcePort} \
+            -j DNAT --to-destination ${fwd.destination}
+
+          ${iptables} -w -t nat -A nixos-nat-post \
+            -d ${destinationIP} -p ${fwd.proto} \
+            --dport ${destinationPorts} \
+            -j SNAT --to-source ${loopbackip}
+        '') fwd.loopbackIPs}
+    '') forwardPorts}
+  '';
+
+  setupNat = ''
+    ${helpers}
+    # Create subchains where we store rules
+    ip46tables -w -t nat -N nixos-nat-pre
+    ip46tables -w -t nat -N nixos-nat-post
+    ip46tables -w -t nat -N nixos-nat-out
+
+    ${mkSetupNat {
+      iptables = "iptables";
+      inherit dest;
+      inherit (cfg) internalIPs;
+      forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
+    }}
+
+    ${optionalString cfg.enableIPv6 (mkSetupNat {
+      iptables = "ip6tables";
+      dest = destIPv6;
+      internalIPs = cfg.internalIPv6s;
+      forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
+    })}
+
+    ${optionalString (cfg.dmzHost != null) ''
+      iptables -w -t nat -A nixos-nat-pre \
+        -i ${toString cfg.externalInterface} -j DNAT \
+        --to-destination ${cfg.dmzHost}
+    ''}
+
+    ${cfg.extraCommands}
+
+    # Append our chains to the nat tables
+    ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
+    ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
+    ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
+  '';
+
+in
+
+{
+
+  options = {
+
+    networking.nat.extraCommands = mkOption {
+      type = types.lines;
+      default = "";
+      example = "iptables -A INPUT -p icmp -j ACCEPT";
+      description = lib.mdDoc ''
+        Additional shell commands executed as part of the nat
+        initialisation script.
+
+        This option is incompatible with the nftables based nat module.
+      '';
+    };
+
+    networking.nat.extraStopCommands = mkOption {
+      type = types.lines;
+      default = "";
+      example = "iptables -D INPUT -p icmp -j ACCEPT || true";
+      description = lib.mdDoc ''
+        Additional shell commands executed as part of the nat
+        teardown script.
+
+        This option is incompatible with the nftables based nat module.
+      '';
+    };
+
+  };
+
+
+  config = mkIf (!config.networking.nftables.enable)
+    (mkMerge [
+      ({ networking.firewall.extraCommands = mkBefore flushNat; })
+      (mkIf config.networking.nat.enable {
+
+        networking.firewall = mkIf config.networking.firewall.enable {
+          extraCommands = setupNat;
+          extraStopCommands = flushNat;
+        };
+
+        systemd.services = mkIf (!config.networking.firewall.enable) {
+          nat = {
+            description = "Network Address Translation";
+            wantedBy = [ "network.target" ];
+            after = [ "network-pre.target" "systemd-modules-load.service" ];
+            path = [ config.networking.firewall.package ];
+            unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+            };
+
+            script = flushNat + setupNat;
+
+            postStop = flushNat;
+          };
+        };
+      })
+    ]);
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nat-nftables.nix b/nixpkgs/nixos/modules/services/networking/nat-nftables.nix
new file mode 100644
index 000000000000..483910a16658
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nat-nftables.nix
@@ -0,0 +1,184 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.nat;
+
+  mkDest = externalIP:
+    if externalIP == null
+    then "masquerade"
+    else "snat ${externalIP}";
+  dest = mkDest cfg.externalIP;
+  destIPv6 = mkDest cfg.externalIPv6;
+
+  toNftSet = list: concatStringsSep ", " list;
+  toNftRange = ports: replaceStrings [ ":" ] [ "-" ] (toString ports);
+
+  ifaceSet = toNftSet (map (x: ''"${x}"'') cfg.internalInterfaces);
+  ipSet = toNftSet cfg.internalIPs;
+  ipv6Set = toNftSet cfg.internalIPv6s;
+  oifExpr = optionalString (cfg.externalInterface != null) ''oifname "${cfg.externalInterface}"'';
+
+  # Whether given IP (plus optional port) is an IPv6.
+  isIPv6 = ip: length (lib.splitString ":" ip) > 2;
+
+  splitIPPorts = IPPorts:
+    let
+      matchIP = if isIPv6 IPPorts then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
+      m = builtins.match "${matchIP}:([0-9-]+)" IPPorts;
+    in
+    {
+      IP = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 0;
+      ports = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 1;
+    };
+
+  mkTable = { ipVer, dest, ipSet, forwardPorts, dmzHost }:
+    let
+      # nftables does not support both port and port range as values in a dnat map.
+      # e.g. "dnat th dport map { 80 : 10.0.0.1 . 80, 443 : 10.0.0.2 . 900-1000 }"
+      # So we split them.
+      fwdPorts = filter (x: length (splitString "-" x.destination) == 1) forwardPorts;
+      fwdPortsRange = filter (x: length (splitString "-" x.destination) > 1) forwardPorts;
+
+      # nftables maps for port forward
+      # l4proto . dport : addr . port
+      toFwdMap = forwardPorts: toNftSet (map
+        (fwd:
+          with (splitIPPorts fwd.destination);
+          "${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
+        )
+        forwardPorts);
+      fwdMap = toFwdMap fwdPorts;
+      fwdRangeMap = toFwdMap fwdPortsRange;
+
+      # nftables maps for port forward loopback dnat
+      # daddr . l4proto . dport : addr . port
+      toFwdLoopDnatMap = forwardPorts: toNftSet (concatMap
+        (fwd: map
+          (loopbackip:
+            with (splitIPPorts fwd.destination);
+            "${loopbackip} . ${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
+          )
+          fwd.loopbackIPs)
+        forwardPorts);
+      fwdLoopDnatMap = toFwdLoopDnatMap fwdPorts;
+      fwdLoopDnatRangeMap = toFwdLoopDnatMap fwdPortsRange;
+
+      # nftables set for port forward loopback snat
+      # daddr . l4proto . dport
+      fwdLoopSnatSet = toNftSet (map
+        (fwd:
+          with (splitIPPorts fwd.destination);
+          "${IP} . ${fwd.proto} . ${ports}"
+        )
+        forwardPorts);
+    in
+    ''
+      chain pre {
+        type nat hook prerouting priority dstnat;
+
+        ${optionalString (fwdMap != "") ''
+          iifname "${cfg.externalInterface}" dnat meta l4proto . th dport map { ${fwdMap} } comment "port forward"
+        ''}
+        ${optionalString (fwdRangeMap != "") ''
+          iifname "${cfg.externalInterface}" dnat meta l4proto . th dport map { ${fwdRangeMap} } comment "port forward"
+        ''}
+
+        ${optionalString (fwdLoopDnatMap != "") ''
+          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from other hosts behind NAT"
+        ''}
+        ${optionalString (fwdLoopDnatRangeMap != "") ''
+          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatRangeMap} } comment "port forward loopback from other hosts behind NAT"
+        ''}
+
+        ${optionalString (dmzHost != null) ''
+          iifname "${cfg.externalInterface}" dnat ${dmzHost} comment "dmz"
+        ''}
+      }
+
+      chain post {
+        type nat hook postrouting priority srcnat;
+
+        ${optionalString (ifaceSet != "") ''
+          iifname { ${ifaceSet} } ${oifExpr} ${dest} comment "from internal interfaces"
+        ''}
+        ${optionalString (ipSet != "") ''
+          ${ipVer} saddr { ${ipSet} } ${oifExpr} ${dest} comment "from internal IPs"
+        ''}
+
+        ${optionalString (fwdLoopSnatSet != "") ''
+          iifname != "${cfg.externalInterface}" ${ipVer} daddr . meta l4proto . th dport { ${fwdLoopSnatSet} } masquerade comment "port forward loopback snat"
+        ''}
+      }
+
+      chain out {
+        type nat hook output priority mangle;
+
+        ${optionalString (fwdLoopDnatMap != "") ''
+          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from the host itself"
+        ''}
+        ${optionalString (fwdLoopDnatRangeMap != "") ''
+          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatRangeMap} } comment "port forward loopback from the host itself"
+        ''}
+      }
+    '';
+
+in
+
+{
+
+  config = mkIf (config.networking.nftables.enable && cfg.enable) {
+
+    assertions = [
+      {
+        assertion = cfg.extraCommands == "";
+        message = "extraCommands is incompatible with the nftables based nat module: ${cfg.extraCommands}";
+      }
+      {
+        assertion = cfg.extraStopCommands == "";
+        message = "extraStopCommands is incompatible with the nftables based nat module: ${cfg.extraStopCommands}";
+      }
+      {
+        assertion = config.networking.nftables.rulesetFile == null;
+        message = "networking.nftables.rulesetFile conflicts with the nat module";
+      }
+    ];
+
+    networking.nftables.ruleset = ''
+      table ip nixos-nat {
+        ${mkTable {
+          ipVer = "ip";
+          inherit dest ipSet;
+          forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
+          inherit (cfg) dmzHost;
+        }}
+      }
+
+      ${optionalString cfg.enableIPv6 ''
+        table ip6 nixos-nat {
+          ${mkTable {
+            ipVer = "ip6";
+            dest = destIPv6;
+            ipSet = ipv6Set;
+            forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
+            dmzHost = null;
+          }}
+        }
+      ''}
+    '';
+
+    networking.firewall.extraForwardRules = optionalString config.networking.firewall.filterForward ''
+      ${optionalString (ifaceSet != "") ''
+        iifname { ${ifaceSet} } ${oifExpr} accept comment "from internal interfaces"
+      ''}
+      ${optionalString (ipSet != "") ''
+        ip saddr { ${ipSet} } ${oifExpr} accept comment "from internal IPs"
+      ''}
+      ${optionalString (ipv6Set != "") ''
+        ip6 saddr { ${ipv6Set} } ${oifExpr} accept comment "from internal IPv6s"
+      ''}
+    '';
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nat.nix b/nixpkgs/nixos/modules/services/networking/nat.nix
index 0eb9b158e686..3afe6fe0a971 100644
--- a/nixpkgs/nixos/modules/services/networking/nat.nix
+++ b/nixpkgs/nixos/modules/services/networking/nat.nix
@@ -7,219 +7,95 @@
 with lib;
 
 let
-  cfg = config.networking.nat;
-
-  mkDest = externalIP: if externalIP == null
-                       then "-j MASQUERADE"
-                       else "-j SNAT --to-source ${externalIP}";
-  dest = mkDest cfg.externalIP;
-  destIPv6 = mkDest cfg.externalIPv6;
-
-  # Whether given IP (plus optional port) is an IPv6.
-  isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
-
-  helpers = import ./helpers.nix { inherit config lib; };
-
-  flushNat = ''
-    ${helpers}
-    ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
-    ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
-    ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
-    ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
-    ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
-    ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
-    ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
-    ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
-    ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
-
-    ${cfg.extraStopCommands}
-  '';
-
-  mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: ''
-    # We can't match on incoming interface in POSTROUTING, so
-    # mark packets coming from the internal interfaces.
-    ${concatMapStrings (iface: ''
-      ${iptables} -w -t nat -A nixos-nat-pre \
-        -i '${iface}' -j MARK --set-mark 1
-    '') cfg.internalInterfaces}
-
-    # NAT the marked packets.
-    ${optionalString (cfg.internalInterfaces != []) ''
-      ${iptables} -w -t nat -A nixos-nat-post -m mark --mark 1 \
-        ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
-    ''}
-
-    # NAT packets coming from the internal IPs.
-    ${concatMapStrings (range: ''
-      ${iptables} -w -t nat -A nixos-nat-post \
-        -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
-    '') internalIPs}
-
-    # NAT from external ports to internal ports.
-    ${concatMapStrings (fwd: ''
-      ${iptables} -w -t nat -A nixos-nat-pre \
-        -i ${toString cfg.externalInterface} -p ${fwd.proto} \
-        --dport ${builtins.toString fwd.sourcePort} \
-        -j DNAT --to-destination ${fwd.destination}
 
-      ${concatMapStrings (loopbackip:
-        let
-          matchIP          = if isIPv6 fwd.destination then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
-          m                = builtins.match "${matchIP}:([0-9-]+)" fwd.destination;
-          destinationIP    = if m == null then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
-          destinationPorts = if m == null then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
-        in ''
-          # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
-          ${iptables} -w -t nat -A nixos-nat-out \
-            -d ${loopbackip} -p ${fwd.proto} \
-            --dport ${builtins.toString fwd.sourcePort} \
-            -j DNAT --to-destination ${fwd.destination}
-
-          # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
-          ${iptables} -w -t nat -A nixos-nat-pre \
-            -d ${loopbackip} -p ${fwd.proto} \
-            --dport ${builtins.toString fwd.sourcePort} \
-            -j DNAT --to-destination ${fwd.destination}
-
-          ${iptables} -w -t nat -A nixos-nat-post \
-            -d ${destinationIP} -p ${fwd.proto} \
-            --dport ${destinationPorts} \
-            -j SNAT --to-source ${loopbackip}
-        '') fwd.loopbackIPs}
-    '') forwardPorts}
-  '';
-
-  setupNat = ''
-    ${helpers}
-    # Create subchains where we store rules
-    ip46tables -w -t nat -N nixos-nat-pre
-    ip46tables -w -t nat -N nixos-nat-post
-    ip46tables -w -t nat -N nixos-nat-out
-
-    ${mkSetupNat {
-      iptables = "iptables";
-      inherit dest;
-      inherit (cfg) internalIPs;
-      forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
-    }}
-
-    ${optionalString cfg.enableIPv6 (mkSetupNat {
-      iptables = "ip6tables";
-      dest = destIPv6;
-      internalIPs = cfg.internalIPv6s;
-      forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
-    })}
-
-    ${optionalString (cfg.dmzHost != null) ''
-      iptables -w -t nat -A nixos-nat-pre \
-        -i ${toString cfg.externalInterface} -j DNAT \
-        --to-destination ${cfg.dmzHost}
-    ''}
-
-    ${cfg.extraCommands}
-
-    # Append our chains to the nat tables
-    ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
-    ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
-    ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
-  '';
+  cfg = config.networking.nat;
 
 in
 
 {
 
-  ###### interface
-
   options = {
 
     networking.nat.enable = mkOption {
       type = types.bool;
       default = false;
-      description =
-        lib.mdDoc ''
-          Whether to enable Network Address Translation (NAT).
-        '';
+      description = lib.mdDoc ''
+        Whether to enable Network Address Translation (NAT).
+      '';
     };
 
     networking.nat.enableIPv6 = mkOption {
       type = types.bool;
       default = false;
-      description =
-        lib.mdDoc ''
-          Whether to enable IPv6 NAT.
-        '';
+      description = lib.mdDoc ''
+        Whether to enable IPv6 NAT.
+      '';
     };
 
     networking.nat.internalInterfaces = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       example = [ "eth0" ];
-      description =
-        lib.mdDoc ''
-          The interfaces for which to perform NAT. Packets coming from
-          these interface and destined for the external interface will
-          be rewritten.
-        '';
+      description = lib.mdDoc ''
+        The interfaces for which to perform NAT. Packets coming from
+        these interface and destined for the external interface will
+        be rewritten.
+      '';
     };
 
     networking.nat.internalIPs = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       example = [ "192.168.1.0/24" ];
-      description =
-        lib.mdDoc ''
-          The IP address ranges for which to perform NAT.  Packets
-          coming from these addresses (on any interface) and destined
-          for the external interface will be rewritten.
-        '';
+      description = lib.mdDoc ''
+        The IP address ranges for which to perform NAT.  Packets
+        coming from these addresses (on any interface) and destined
+        for the external interface will be rewritten.
+      '';
     };
 
     networking.nat.internalIPv6s = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       example = [ "fc00::/64" ];
-      description =
-        lib.mdDoc ''
-          The IPv6 address ranges for which to perform NAT.  Packets
-          coming from these addresses (on any interface) and destined
-          for the external interface will be rewritten.
-        '';
+      description = lib.mdDoc ''
+        The IPv6 address ranges for which to perform NAT.  Packets
+        coming from these addresses (on any interface) and destined
+        for the external interface will be rewritten.
+      '';
     };
 
     networking.nat.externalInterface = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "eth1";
-      description =
-        lib.mdDoc ''
-          The name of the external network interface.
-        '';
+      description = lib.mdDoc ''
+        The name of the external network interface.
+      '';
     };
 
     networking.nat.externalIP = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "203.0.113.123";
-      description =
-        lib.mdDoc ''
-          The public IP address to which packets from the local
-          network are to be rewritten.  If this is left empty, the
-          IP address associated with the external interface will be
-          used.
-        '';
+      description = lib.mdDoc ''
+        The public IP address to which packets from the local
+        network are to be rewritten.  If this is left empty, the
+        IP address associated with the external interface will be
+        used.
+      '';
     };
 
     networking.nat.externalIPv6 = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "2001:dc0:2001:11::175";
-      description =
-        lib.mdDoc ''
-          The public IPv6 address to which packets from the local
-          network are to be rewritten.  If this is left empty, the
-          IP address associated with the external interface will be
-          used.
-        '';
+      description = lib.mdDoc ''
+        The public IPv6 address to which packets from the local
+        network are to be rewritten.  If this is left empty, the
+        IP address associated with the external interface will be
+        used.
+      '';
     };
 
     networking.nat.forwardPorts = mkOption {
@@ -246,119 +122,75 @@ in
 
           loopbackIPs = mkOption {
             type = types.listOf types.str;
-            default = [];
+            default = [ ];
             example = literalExpression ''[ "55.1.2.3" ]'';
-            description = lib.mdDoc "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
+            description = lib.mdDoc "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort` from the host itself and from other hosts behind NAT";
           };
         };
       });
-      default = [];
+      default = [ ];
       example = [
         { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; }
         { sourcePort = 8080; destination = "[fc00::2]:80"; proto = "tcp"; }
       ];
-      description =
-        lib.mdDoc ''
-          List of forwarded ports from the external interface to
-          internal destinations by using DNAT. Destination can be
-          IPv6 if IPv6 NAT is enabled.
-        '';
+      description = lib.mdDoc ''
+        List of forwarded ports from the external interface to
+        internal destinations by using DNAT. Destination can be
+        IPv6 if IPv6 NAT is enabled.
+      '';
     };
 
     networking.nat.dmzHost = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "10.0.0.1";
-      description =
-        lib.mdDoc ''
-          The local IP address to which all traffic that does not match any
-          forwarding rule is forwarded.
-        '';
-    };
-
-    networking.nat.extraCommands = mkOption {
-      type = types.lines;
-      default = "";
-      example = "iptables -A INPUT -p icmp -j ACCEPT";
-      description =
-        lib.mdDoc ''
-          Additional shell commands executed as part of the nat
-          initialisation script.
-        '';
-    };
-
-    networking.nat.extraStopCommands = mkOption {
-      type = types.lines;
-      default = "";
-      example = "iptables -D INPUT -p icmp -j ACCEPT || true";
-      description =
-        lib.mdDoc ''
-          Additional shell commands executed as part of the nat
-          teardown script.
-        '';
+      description = lib.mdDoc ''
+        The local IP address to which all traffic that does not match any
+        forwarding rule is forwarded.
+      '';
     };
 
   };
 
 
-  ###### implementation
-
-  config = mkMerge [
-    { networking.firewall.extraCommands = mkBefore flushNat; }
-    (mkIf config.networking.nat.enable {
-
-      assertions = [
-        { assertion = cfg.enableIPv6           -> config.networking.enableIPv6;
-          message = "networking.nat.enableIPv6 requires networking.enableIPv6";
-        }
-        { assertion = (cfg.dmzHost != null)    -> (cfg.externalInterface != null);
-          message = "networking.nat.dmzHost requires networking.nat.externalInterface";
-        }
-        { assertion = (cfg.forwardPorts != []) -> (cfg.externalInterface != null);
-          message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
-        }
-      ];
-
-      environment.systemPackages = [ pkgs.iptables ];
-
-      boot = {
-        kernelModules = [ "nf_nat_ftp" ];
-        kernel.sysctl = {
-          "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
-          "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
-        } // optionalAttrs cfg.enableIPv6 {
-          # Do not prevent IPv6 autoconfiguration.
-          # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
-          "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
-          "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
-
-          # Forward IPv6 packets.
-          "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
-          "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
-        };
-      };
-
-      networking.firewall = mkIf config.networking.firewall.enable {
-        extraCommands = setupNat;
-        extraStopCommands = flushNat;
+  config = mkIf config.networking.nat.enable {
+
+    assertions = [
+      {
+        assertion = cfg.enableIPv6 -> config.networking.enableIPv6;
+        message = "networking.nat.enableIPv6 requires networking.enableIPv6";
+      }
+      {
+        assertion = (cfg.dmzHost != null) -> (cfg.externalInterface != null);
+        message = "networking.nat.dmzHost requires networking.nat.externalInterface";
+      }
+      {
+        assertion = (cfg.forwardPorts != [ ]) -> (cfg.externalInterface != null);
+        message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
+      }
+    ];
+
+    # Use the same iptables package as in config.networking.firewall.
+    # When the firewall is enabled, this should be deduplicated without any
+    # error.
+    environment.systemPackages = [ config.networking.firewall.package ];
+
+    boot = {
+      kernelModules = [ "nf_nat_ftp" ];
+      kernel.sysctl = {
+        "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
+        "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
+      } // optionalAttrs cfg.enableIPv6 {
+        # Do not prevent IPv6 autoconfiguration.
+        # See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
+        "net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
+        "net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
+
+        # Forward IPv6 packets.
+        "net.ipv6.conf.all.forwarding" = mkOverride 99 true;
+        "net.ipv6.conf.default.forwarding" = mkOverride 99 true;
       };
+    };
 
-      systemd.services = mkIf (!config.networking.firewall.enable) { nat = {
-        description = "Network Address Translation";
-        wantedBy = [ "network.target" ];
-        after = [ "network-pre.target" "systemd-modules-load.service" ];
-        path = [ pkgs.iptables ];
-        unitConfig.ConditionCapability = "CAP_NET_ADMIN";
-
-        serviceConfig = {
-          Type = "oneshot";
-          RemainAfterExit = true;
-        };
-
-        script = flushNat + setupNat;
-
-        postStop = flushNat;
-      }; };
-    })
-  ];
+  };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/nats.nix b/nixpkgs/nixos/modules/services/networking/nats.nix
index 41e38add69fe..6c21e21b5cb8 100644
--- a/nixpkgs/nixos/modules/services/networking/nats.nix
+++ b/nixpkgs/nixos/modules/services/networking/nats.nix
@@ -16,7 +16,7 @@ in {
 
   options = {
     services.nats = {
-      enable = mkEnableOption "NATS messaging system";
+      enable = mkEnableOption (lib.mdDoc "NATS messaging system");
 
       user = mkOption {
         type = types.str;
@@ -39,7 +39,7 @@ in {
         '';
       };
 
-      jetstream = mkEnableOption "JetStream";
+      jetstream = mkEnableOption (lib.mdDoc "JetStream");
 
       port = mkOption {
         default = 4222;
@@ -137,7 +137,7 @@ in {
           RestrictNamespaces = true;
           RestrictRealtime = true;
           RestrictSUIDSGID = true;
-          SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
           UMask = "0077";
         }
       ];
diff --git a/nixpkgs/nixos/modules/services/networking/nbd.nix b/nixpkgs/nixos/modules/services/networking/nbd.nix
index 76ca11dfea76..454380aa3154 100644
--- a/nixpkgs/nixos/modules/services/networking/nbd.nix
+++ b/nixpkgs/nixos/modules/services/networking/nbd.nix
@@ -43,7 +43,7 @@ in
   options = {
     services.nbd = {
       server = {
-        enable = mkEnableOption "the Network Block Device (nbd) server";
+        enable = mkEnableOption (lib.mdDoc "the Network Block Device (nbd) server");
 
         listenPort = mkOption {
           type = types.port;
diff --git a/nixpkgs/nixos/modules/services/networking/ncdns.nix b/nixpkgs/nixos/modules/services/networking/ncdns.nix
index 958231963c68..cc97beb14e01 100644
--- a/nixpkgs/nixos/modules/services/networking/ncdns.nix
+++ b/nixpkgs/nixos/modules/services/networking/ncdns.nix
@@ -50,11 +50,11 @@ in
 
     services.ncdns = {
 
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         ncdns, a Go daemon to bridge Namecoin to DNS.
-        To resolve .bit domains set <literal>services.namecoind.enable = true;</literal>
+        To resolve .bit domains set `services.namecoind.enable = true;`
         and an RPC username/password
-      '';
+      '');
 
       address = mkOption {
         type = types.str;
@@ -78,16 +78,16 @@ in
         default = config.networking.hostName;
         defaultText = literalExpression "config.networking.hostName";
         example = "example.com";
-        description = ''
+        description = lib.mdDoc ''
           The hostname of this ncdns instance, which defaults to the machine
           hostname. If specified, ncdns lists the hostname as an NS record at
           the zone apex:
-          <programlisting>
+          ```
           bit. IN NS ns1.example.com.
-          </programlisting>
-          If unset ncdns will generate an internal psuedo-hostname under the
+          ```
+          If unset ncdns will generate an internal pseudo-hostname under the
           zone, which will resolve to the value of
-          <option>services.ncdns.identity.address</option>.
+          {option}`services.ncdns.identity.address`.
           If you are only using ncdns locally you can ignore this.
         '';
       };
@@ -112,24 +112,24 @@ in
         '';
       };
 
-      dnssec.enable = mkEnableOption ''
+      dnssec.enable = mkEnableOption (lib.mdDoc ''
         DNSSEC support in ncdns. This will generate KSK and ZSK keypairs
         (unless provided via the options
-        <option>services.ncdns.dnssec.publicKey</option>,
-        <option>services.ncdns.dnssec.privateKey</option> etc.) and add a trust
+        {option}`services.ncdns.dnssec.publicKey`,
+        {option}`services.ncdns.dnssec.privateKey` etc.) and add a trust
         anchor to recursive resolvers
-      '';
+      '');
 
       dnssec.keys.public = mkOption {
         type = types.path;
         default = defaultFiles.public;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing the KSK public key.
-          The key can be generated using the <literal>dnssec-keygen</literal>
-          command, provided by the package <package>bind</package> as follows:
-          <programlisting>
+          The key can be generated using the `dnssec-keygen`
+          command, provided by the package `bind` as follows:
+          ```
           $ dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
-          </programlisting>
+          ```
         '';
       };
 
@@ -144,13 +144,13 @@ in
       dnssec.keys.zonePublic = mkOption {
         type = types.path;
         default = defaultFiles.zonePublic;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing the ZSK public key.
-          The key can be generated using the <literal>dnssec-keygen</literal>
-          command, provided by the package <package>bind</package> as follows:
-          <programlisting>
+          The key can be generated using the `dnssec-keygen`
+          command, provided by the package `bind` as follows:
+          ```
           $ dnssec-keygen -a RSASHA256 -3 -b 2048 bit
-          </programlisting>
+          ```
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/ndppd.nix b/nixpkgs/nixos/modules/services/networking/ndppd.nix
index ed97fe233b8a..d221c95ae620 100644
--- a/nixpkgs/nixos/modules/services/networking/ndppd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ndppd.nix
@@ -17,7 +17,7 @@ let
       ttl ${toString proxy.ttl}
       ${render proxy.rules (ruleNetworkName: rule: ''
       rule ${prefer rule.network ruleNetworkName} {
-        ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+        ${rule.method}${optionalString (rule.method == "iface") " ${rule.interface}"}
       }'')}
     }'')}
   '');
@@ -43,7 +43,7 @@ let
       timeout = mkOption {
         type = types.int;
         description = lib.mdDoc ''
-          Controls how long to wait for a Neighbor Advertisment Message before
+          Controls how long to wait for a Neighbor Advertisement Message before
           invalidating the entry, in milliseconds.
         '';
         default = 500;
@@ -74,7 +74,7 @@ let
         type = types.nullOr types.str;
         description = lib.mdDoc ''
           This is the target address is to match against. If no netmask
-          is provided, /128 is assumed. The addresses of serveral rules
+          is provided, /128 is assumed. The addresses of several rules
           may or may not overlap.
           Defaults to the name of the attrset.
         '';
@@ -103,21 +103,21 @@ let
 
 in {
   options.services.ndppd = {
-    enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+    enable = mkEnableOption (lib.mdDoc "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces");
     interface = mkOption {
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         Interface which is on link-level with router.
-        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+        (Legacy option, use services.ndppd.proxies.\<interface\>.rules.\<network\> instead)
       '';
       default = null;
       example = "eth0";
     };
     network = mkOption {
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         Network that we proxy.
-        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+        (Legacy option, use services.ndppd.proxies.\<interface\>.rules.\<network\> instead)
       '';
       default = null;
       example = "1111::/64";
diff --git a/nixpkgs/nixos/modules/services/networking/nebula.nix b/nixpkgs/nixos/modules/services/networking/nebula.nix
index 2bedafc5d9fe..e1a8c6740f57 100644
--- a/nixpkgs/nixos/modules/services/networking/nebula.nix
+++ b/nixpkgs/nixos/modules/services/networking/nebula.nix
@@ -68,6 +68,12 @@ in
               description = lib.mdDoc "Whether this node is a lighthouse.";
             };
 
+            isRelay = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc "Whether this node is a relay.";
+            };
+
             lighthouses = mkOption {
               type = types.listOf types.str;
               default = [];
@@ -78,6 +84,15 @@ in
               example = [ "192.168.100.1" ];
             };
 
+            relays = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = lib.mdDoc ''
+                List of IPs of relays that this node should allow traffic from.
+              '';
+              example = [ "192.168.100.1" ];
+            };
+
             listen.host = mkOption {
               type = types.str;
               default = "0.0.0.0";
@@ -157,6 +172,11 @@ in
             am_lighthouse = netCfg.isLighthouse;
             hosts = netCfg.lighthouses;
           };
+          relay = {
+            am_relay = netCfg.isRelay;
+            relays = netCfg.relays;
+            use_relays = true;
+          };
           listen = {
             host = netCfg.listen.host;
             port = netCfg.listen.port;
@@ -173,25 +193,41 @@ in
         configFile = format.generate "nebula-config-${netName}.yml" settings;
         in
         {
-          # Create systemd service for Nebula.
+          # Create the systemd service for Nebula.
           "nebula@${netName}" = {
             description = "Nebula VPN service for ${netName}";
             wants = [ "basic.target" ];
             after = [ "basic.target" "network.target" ];
             before = [ "sshd.service" ];
             wantedBy = [ "multi-user.target" ];
-            serviceConfig = mkMerge [
-              {
-                Type = "simple";
-                Restart = "always";
-                ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
-              }
-              # The service needs to launch as root to access the tun device, if it's enabled.
-              (mkIf netCfg.tun.disable {
-                User = networkId;
-                Group = networkId;
-              })
-            ];
+            serviceConfig = {
+              Type = "simple";
+              Restart = "always";
+              ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
+              UMask = "0027";
+              CapabilityBoundingSet = "CAP_NET_ADMIN";
+              AmbientCapabilities = "CAP_NET_ADMIN";
+              LockPersonality = true;
+              NoNewPrivileges = true;
+              PrivateDevices = false; # needs access to /dev/net/tun (below)
+              DeviceAllow = "/dev/net/tun rw";
+              DevicePolicy = "closed";
+              PrivateTmp = true;
+              PrivateUsers = false; # CapabilityBoundingSet needs to apply to the host namespace
+              ProtectClock = true;
+              ProtectControlGroups = true;
+              ProtectHome = true;
+              ProtectHostname = true;
+              ProtectKernelLogs = true;
+              ProtectKernelModules = true;
+              ProtectKernelTunables = true;
+              ProtectProc = "invisible";
+              ProtectSystem = "strict";
+              RestrictNamespaces = true;
+              RestrictSUIDSGID = true;
+              User = networkId;
+              Group = networkId;
+            };
             unitConfig.StartLimitIntervalSec = 0; # ensure Restart=always is always honoured (networks can go down for arbitrarily long)
           };
         }) enabledNetworks);
@@ -202,7 +238,7 @@ in
 
     # Create the service users and groups.
     users.users = mkMerge (mapAttrsToList (netName: netCfg:
-      mkIf netCfg.tun.disable {
+      {
         ${nameToId netName} = {
           group = nameToId netName;
           description = "Nebula service user for network ${netName}";
@@ -210,9 +246,8 @@ in
         };
       }) enabledNetworks);
 
-    users.groups = mkMerge (mapAttrsToList (netName: netCfg:
-      mkIf netCfg.tun.disable {
-        ${nameToId netName} = {};
-      }) enabledNetworks);
+    users.groups = mkMerge (mapAttrsToList (netName: netCfg: {
+      ${nameToId netName} = {};
+    }) enabledNetworks);
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/netbird.nix b/nixpkgs/nixos/modules/services/networking/netbird.nix
new file mode 100644
index 000000000000..647c0ce3e6d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/netbird.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.netbird;
+  kernel = config.boot.kernelPackages;
+  interfaceName = "wt0";
+in {
+  meta.maintainers = with maintainers; [ misuzu ];
+
+  options.services.netbird = {
+    enable = mkEnableOption (lib.mdDoc "Netbird daemon");
+    package = mkOption {
+      type = types.package;
+      default = pkgs.netbird;
+      defaultText = literalExpression "pkgs.netbird";
+      description = lib.mdDoc "The package to use for netbird";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
+
+    environment.systemPackages = [ cfg.package ];
+
+    networking.dhcpcd.denyInterfaces = [ interfaceName ];
+
+    systemd.network.networks."50-netbird" = mkIf config.networking.useNetworkd {
+      matchConfig = {
+        Name = interfaceName;
+      };
+      linkConfig = {
+        Unmanaged = true;
+        ActivationPolicy = "manual";
+      };
+    };
+
+    systemd.services.netbird = {
+      description = "A WireGuard-based mesh network that connects your devices into a single private network";
+      documentation = [ "https://netbird.io/docs/" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        openresolv
+      ];
+      serviceConfig = {
+        Environment = [
+          "NB_CONFIG=/var/lib/netbird/config.json"
+          "NB_LOG_FILE=console"
+        ];
+        ExecStart = "${cfg.package}/bin/netbird service run";
+        Restart = "always";
+        RuntimeDirectory = "netbird";
+        StateDirectory = "netbird";
+        WorkingDirectory = "/var/lib/netbird";
+      };
+      unitConfig = {
+        StartLimitInterval = 5;
+        StartLimitBurst = 10;
+      };
+      stopIfChanged = false;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/networkd-dispatcher.nix b/nixpkgs/nixos/modules/services/networking/networkd-dispatcher.nix
new file mode 100644
index 000000000000..c5319ca7b88a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/networkd-dispatcher.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.networkd-dispatcher;
+
+in {
+
+  options = {
+    services.networkd-dispatcher = {
+
+      enable = mkEnableOption (mdDoc ''
+        Networkd-dispatcher service for systemd-networkd connection status
+        change. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+        for usage.
+      '');
+
+      rules = mkOption {
+        default = {};
+        example = lib.literalExpression ''
+          { "restart-tor" = {
+              onState = ["routable" "off"];
+              script = '''
+                #!''${pkgs.runtimeShell}
+                if [[ $IFACE == "wlan0" && $AdministrativeState == "configured" ]]; then
+                  echo "Restarting Tor ..."
+                  systemctl restart tor
+                fi
+                exit 0
+              ''';
+            };
+          };
+        '';
+        description = lib.mdDoc ''
+          Declarative configuration of networkd-dispatcher rules. See
+          [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+          for an introduction and example scripts.
+        '';
+        type = types.attrsOf (types.submodule {
+          options = {
+            onState = mkOption {
+              type = types.listOf (types.enum [
+                "routable" "dormant" "no-carrier" "off" "carrier" "degraded"
+                "configuring" "configured"
+              ]);
+              default = null;
+              description = lib.mdDoc ''
+                List of names of the systemd-networkd operational states which
+                should trigger the script. See <https://www.freedesktop.org/software/systemd/man/networkctl.html>
+                for a description of the specific state type.
+              '';
+            };
+            script = mkOption {
+              type = types.lines;
+              description = lib.mdDoc ''
+                Shell commands executed on specified operational states.
+              '';
+            };
+          };
+        });
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      packages = [ pkgs.networkd-dispatcher ];
+      services.networkd-dispatcher = {
+        wantedBy = [ "multi-user.target" ];
+        # Override existing ExecStart definition
+        serviceConfig.ExecStart = let
+          scriptDir = pkgs.symlinkJoin {
+            name = "networkd-dispatcher-script-dir";
+            paths = lib.mapAttrsToList (name: cfg:
+              (map(state:
+                pkgs.writeTextFile {
+                  inherit name;
+                  text = cfg.script;
+                  destination = "/${state}.d/${name}";
+                  executable = true;
+                }
+              ) cfg.onState)
+            ) cfg.rules;
+          };
+        in [
+          ""
+          "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${scriptDir} $networkd_dispatcher_args"
+        ];
+      };
+    };
+
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/networkmanager.nix b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
index d5d562e7ba5f..3b28cec83cb7 100644
--- a/nixpkgs/nixos/modules/services/networking/networkmanager.nix
+++ b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
@@ -106,30 +106,14 @@ let
     type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
     default = "preserve";
     example = "00:11:22:33:44:55";
-    description = ''
+    description = lib.mdDoc ''
       Set the MAC address of the interface.
-      <variablelist>
-        <varlistentry>
-          <term>"XX:XX:XX:XX:XX:XX"</term>
-          <listitem><para>MAC address of the interface</para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"permanent"</literal></term>
-          <listitem><para>Use the permanent MAC address of the device</para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"preserve"</literal></term>
-          <listitem><para>Don’t change the MAC address of the device upon activation</para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"random"</literal></term>
-          <listitem><para>Generate a randomized value upon each connect</para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"stable"</literal></term>
-          <listitem><para>Generate a stable, hashed MAC address</para></listitem>
-        </varlistentry>
-      </variablelist>
+
+      - `"XX:XX:XX:XX:XX:XX"`: MAC address of the interface
+      - `"permanent"`: Use the permanent MAC address of the device
+      - `"preserve"`: Don’t change the MAC address of the device upon activation
+      - `"random"`: Generate a randomized value upon each connect
+      - `"stable"`: Generate a stable, hashed MAC address
     '';
   };
 
@@ -343,9 +327,9 @@ in {
             type = mkOption {
               type = types.enum (attrNames dispatcherTypesSubdirMap);
               default = "basic";
-              description = ''
+              description = lib.mdDoc ''
                 Dispatcher hook type. Look up the hooks described at
-                <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.html">https://developer.gnome.org/NetworkManager/stable/NetworkManager.html</link>
+                [https://developer.gnome.org/NetworkManager/stable/NetworkManager.html](https://developer.gnome.org/NetworkManager/stable/NetworkManager.html)
                 and choose the type depending on the output folder.
                 You should then filter the event type (e.g., "up"/"down") from within your script.
               '';
@@ -381,7 +365,7 @@ in {
           If you enable this option the
           `networkmanager_strongswan` plugin will be added to
           the {option}`networking.networkmanager.plugins` option
-          so you don't need to to that yourself.
+          so you don't need to do that yourself.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/nftables.nix b/nixpkgs/nixos/modules/services/networking/nftables.nix
index 4e7d5ce59ce8..faff1dca89ba 100644
--- a/nixpkgs/nixos/modules/services/networking/nftables.nix
+++ b/nixpkgs/nixos/modules/services/networking/nftables.nix
@@ -11,26 +11,49 @@ in
       type = types.bool;
       default = false;
       description =
-        ''
-          Whether to enable nftables.  nftables is a Linux-based packet
-          filtering framework intended to replace frameworks like iptables.
-
-          This conflicts with the standard networking firewall, so make sure to
-          disable it before using nftables.
+        lib.mdDoc ''
+          Whether to enable nftables and use nftables based firewall if enabled.
+          nftables is a Linux-based packet filtering framework intended to
+          replace frameworks like iptables.
 
           Note that if you have Docker enabled you will not be able to use
           nftables without intervention. Docker uses iptables internally to
           setup NAT for containers. This module disables the ip_tables kernel
-          module, however Docker automatically loads the module. Please see [1]
+          module, however Docker automatically loads the module. Please see
+          <https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273>
           for more information.
 
           There are other programs that use iptables internally too, such as
-          libvirt. For information on how the two firewalls interact, see [2].
-
-          [1]: https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273
-          [2]: https://wiki.nftables.org/wiki-nftables/index.php/Troubleshooting#Question_4._How_do_nftables_and_iptables_interact_when_used_on_the_same_system.3F
+          libvirt. For information on how the two firewalls interact, see
+          <https://wiki.nftables.org/wiki-nftables/index.php/Troubleshooting#Question_4._How_do_nftables_and_iptables_interact_when_used_on_the_same_system.3F>.
         '';
     };
+
+    networking.nftables.checkRuleset = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Run `nft check` on the ruleset to spot syntax errors during build.
+        Because this is executed in a sandbox, the check might fail if it requires
+        access to any environmental factors or paths outside the Nix store.
+        To circumvent this, the ruleset file can be edited using the preCheckRuleset
+        option to work in the sandbox environment.
+      '';
+    };
+
+    networking.nftables.preCheckRuleset = mkOption {
+      type = types.lines;
+      default = "";
+      example = lib.literalExpression ''
+        sed 's/skgid meadow/skgid nogroup/g' -i ruleset.conf
+      '';
+      description = lib.mdDoc ''
+        This script gets run before the ruleset is checked. It can be used to
+        create additional files needed for the ruleset check to work, or modify
+        the ruleset for cases the build environment cannot cover.
+      '';
+    };
+
     networking.nftables.ruleset = mkOption {
       type = types.lines;
       default = "";
@@ -38,7 +61,7 @@ in
         # Check out https://wiki.nftables.org/ for better documentation.
         # Table for both IPv4 and IPv6.
         table inet filter {
-          # Block all incomming connections traffic except SSH and "ping".
+          # Block all incoming connections traffic except SSH and "ping".
           chain input {
             type filter hook input priority 0;
 
@@ -80,19 +103,17 @@ in
         lib.mdDoc ''
           The ruleset to be used with nftables.  Should be in a format that
           can be loaded using "/bin/nft -f".  The ruleset is updated atomically.
+          This option conflicts with rulesetFile.
         '';
     };
     networking.nftables.rulesetFile = mkOption {
-      type = types.path;
-      default = pkgs.writeTextFile {
-        name = "nftables-rules";
-        text = cfg.ruleset;
-      };
-      defaultText = literalDocBook ''a file with the contents of <option>networking.nftables.ruleset</option>'';
+      type = types.nullOr types.path;
+      default = null;
       description =
         lib.mdDoc ''
           The ruleset file to be used with nftables.  Should be in a format that
           can be loaded using "nft -f".  The ruleset is updated atomically.
+          This option conflicts with ruleset and nftables based firewall.
         '';
     };
   };
@@ -100,10 +121,6 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    assertions = [{
-      assertion = config.networking.firewall.enable == false;
-      message = "You can not use nftables and iptables at the same time. networking.firewall.enable must be set to false.";
-    }];
     boot.blacklistedKernelModules = [ "ip_tables" ];
     environment.systemPackages = [ pkgs.nftables ];
     networking.networkmanager.firewallBackend = mkDefault "nftables";
@@ -114,11 +131,24 @@ in
       wantedBy = [ "multi-user.target" ];
       reloadIfChanged = true;
       serviceConfig = let
-        rulesScript = pkgs.writeScript "nftables-rules" ''
-          #! ${pkgs.nftables}/bin/nft -f
-          flush ruleset
-          include "${cfg.rulesetFile}"
-        '';
+        rulesScript = pkgs.writeTextFile {
+          name =  "nftables-rules";
+          executable = true;
+          text = ''
+            #! ${pkgs.nftables}/bin/nft -f
+            flush ruleset
+            ${if cfg.rulesetFile != null then ''
+              include "${cfg.rulesetFile}"
+            '' else cfg.ruleset}
+          '';
+          checkPhase = lib.optionalString cfg.checkRuleset ''
+            cp $out ruleset.conf
+            ${cfg.preCheckRuleset}
+            export NIX_REDIRECTS=/etc/protocols=${pkgs.buildPackages.iana-etc}/etc/protocols:/etc/services=${pkgs.buildPackages.iana-etc}/etc/services
+            LD_PRELOAD="${pkgs.buildPackages.libredirect}/lib/libredirect.so ${pkgs.buildPackages.lklWithFirewall.lib}/lib/liblkl-hijack.so" \
+              ${pkgs.buildPackages.nftables}/bin/nft --check --file ruleset.conf
+          '';
+        };
       in {
         Type = "oneshot";
         RemainAfterExit = true;
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
index 13c328b4180a..82ab8c4223e6 100644
--- a/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
@@ -1,6 +1,6 @@
 { lib, ... }:
 { options.services.nghttpx = {
-    enable = lib.mkEnableOption "nghttpx";
+    enable = lib.mkEnableOption (lib.mdDoc "nghttpx");
 
     frontends = lib.mkOption {
       type        = lib.types.listOf (lib.types.submodule (import ./frontend-submodule.nix));
@@ -131,8 +131,8 @@
     rlimit-nofile = lib.mkOption {
       type        = lib.types.int;
       default     = 0;
-      description = ''
-        Set maximum number of open files (RLIMIT_NOFILE) to &lt;N&gt;. If 0
+      description = lib.mdDoc ''
+        Set maximum number of open files (RLIMIT_NOFILE) to \<N\>. If 0
         is given, nghttpx does not set the limit.
 
         Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--rlimit-nofile
diff --git a/nixpkgs/nixos/modules/services/networking/ngircd.nix b/nixpkgs/nixos/modules/services/networking/ngircd.nix
index f6c7415c1d3b..5e721f5aa625 100644
--- a/nixpkgs/nixos/modules/services/networking/ngircd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ngircd.nix
@@ -20,7 +20,7 @@ let
 in {
   options = {
     services.ngircd = {
-      enable = mkEnableOption "the ngircd IRC server";
+      enable = mkEnableOption (lib.mdDoc "the ngircd IRC server");
 
       config = mkOption {
         description = lib.mdDoc "The ngircd configuration (see ngircd.conf(5)).";
diff --git a/nixpkgs/nixos/modules/services/networking/nix-serve.nix b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
index 04cbc0c0d8f8..f37be31270b7 100644
--- a/nixpkgs/nixos/modules/services/networking/nix-serve.nix
+++ b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.nix-serve = {
-      enable = mkEnableOption "nix-serve, the standalone Nix binary cache server";
+      enable = mkEnableOption (lib.mdDoc "nix-serve, the standalone Nix binary cache server");
 
       port = mkOption {
         type = types.port;
@@ -26,6 +26,15 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.nix-serve;
+        defaultText = literalExpression "pkgs.nix-serve";
+        description = lib.mdDoc ''
+          nix-serve package to use.
+        '';
+      };
+
       openFirewall = mkOption {
         type = types.bool;
         default = false;
@@ -35,7 +44,7 @@ in
       secretKeyFile = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the file used for signing derivation data.
           Generate with:
 
@@ -43,7 +52,7 @@ in
           nix-store --generate-binary-cache-key key-name secret-key-file public-key-file
           ```
 
-          For more details see <citerefentry><refentrytitle>nix-store</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+          For more details see {manpage}`nix-store(1)`.
         '';
       };
 
@@ -70,7 +79,7 @@ in
         ${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}
+        exec ${cfg.package}/bin/nix-serve --listen ${cfg.bindAddress}:${toString cfg.port} ${cfg.extraParams}
       '';
 
       serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/networking/nixops-dns.nix b/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
index 4abdb50d6942..378c2ee6d05f 100644
--- a/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
+++ b/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
@@ -40,7 +40,7 @@ in
       dnsmasq = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable dnsmasq forwarding to nixops-dns. This allows to use
           nixops-dns for `services.nixops-dns.domain` resolution
           while forwarding the rest of the queries to original resolvers.
diff --git a/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix b/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
index 4dd2922e83f1..b887c0e16ef4 100644
--- a/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
@@ -59,7 +59,7 @@ in
   options = {
 
     services.nntp-proxy = {
-      enable = mkEnableOption "NNTP-Proxy";
+      enable = mkEnableOption (lib.mdDoc "NNTP-Proxy");
 
       upstreamServer = mkOption {
         type = types.str;
@@ -71,7 +71,7 @@ in
       };
 
       upstreamPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 563;
         description = lib.mdDoc ''
           Upstream server port
@@ -112,7 +112,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 5555;
         description = lib.mdDoc ''
           Proxy listen port
diff --git a/nixpkgs/nixos/modules/services/networking/nomad.nix b/nixpkgs/nixos/modules/services/networking/nomad.nix
index 73b6f13327fa..b1e51195247a 100644
--- a/nixpkgs/nixos/modules/services/networking/nomad.nix
+++ b/nixpkgs/nixos/modules/services/networking/nomad.nix
@@ -8,7 +8,7 @@ in
   ##### interface
   options = {
     services.nomad = {
-      enable = mkEnableOption "Nomad, a distributed, highly available, datacenter-aware scheduler";
+      enable = mkEnableOption (lib.mdDoc "Nomad, a distributed, highly available, datacenter-aware scheduler");
 
       package = mkOption {
         type = types.package;
@@ -22,8 +22,8 @@ in
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = [ ];
-        description = ''
-          Extra packages to add to <envar>PATH</envar> for the Nomad agent process.
+        description = lib.mdDoc ''
+          Extra packages to add to {env}`PATH` for the Nomad agent process.
         '';
         example = literalExpression ''
           with pkgs; [ cni-plugins ]
@@ -67,10 +67,21 @@ in
           Additional plugins dir used to configure nomad.
         '';
         example = literalExpression ''
-          [ "<pluginDir>" "pkgs.<plugins-name>"]
+          [ "<pluginDir>" pkgs.nomad-driver-nix pkgs.nomad-driver-podman  ]
         '';
       };
 
+      credentials = mkOption {
+        description = lib.mdDoc ''
+          Credentials envs used to configure nomad secrets.
+        '';
+        type = types.attrsOf types.str;
+        default = { };
+
+        example = {
+          logs_remote_write_password = "/run/keys/nomad_write_password";
+        };
+      };
 
       settings = mkOption {
         type = format.type;
@@ -139,9 +150,17 @@ in
         {
           DynamicUser = cfg.dropPrivileges;
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-          ExecStart = "${cfg.package}/bin/nomad agent -config=/etc/nomad.json" +
+          ExecStart =
+            let
+              pluginsDir = pkgs.symlinkJoin
+                {
+                  name = "nomad-plugins";
+                  paths = cfg.extraSettingsPlugins;
+                };
+            in
+            "${cfg.package}/bin/nomad agent -config=/etc/nomad.json -plugin-dir=${pluginsDir}/bin" +
             concatMapStrings (path: " -config=${path}") cfg.extraSettingsPaths +
-            concatMapStrings (path: " -plugin-dir=${path}/bin") cfg.extraSettingsPlugins;
+            concatMapStrings (key: " -config=\${CREDENTIALS_DIRECTORY}/${key}") (lib.attrNames cfg.credentials);
           KillMode = "process";
           KillSignal = "SIGINT";
           LimitNOFILE = 65536;
@@ -150,6 +169,7 @@ in
           Restart = "on-failure";
           RestartSec = 2;
           TasksMax = "infinity";
+          LoadCredential = lib.mapAttrsToList (key: value: "${key}:${value}") cfg.credentials;
         }
         (mkIf cfg.enableDocker {
           SupplementaryGroups = "docker"; # space-separated string
diff --git a/nixpkgs/nixos/modules/services/networking/nsd.nix b/nixpkgs/nixos/modules/services/networking/nsd.nix
index cf2afcacc528..09f3bdc7ae07 100644
--- a/nixpkgs/nixos/modules/services/networking/nsd.nix
+++ b/nixpkgs/nixos/modules/services/networking/nsd.nix
@@ -213,24 +213,24 @@ let
         example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
                     "10.0.3.4&255.255.0.0 BLOCKED"
                   ];
-        description = ''
+        description = lib.mdDoc ''
           Listed primary servers are allowed to notify this secondary server.
-          <screen><![CDATA[
-          Format: <ip> <key-name | NOKEY | BLOCKED>
 
-          <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
-          * 10.0.0.0/24            # via subnet size
-          * 10.0.0.0&255.255.255.0 # via subnet mask
-          * 10.0.0.1-10.0.0.254    # via range
+          Format: `<ip> <key-name | NOKEY | BLOCKED>`
+
+          `<ip>` either a plain IPv4/IPv6 address or range.
+          Valid patters for ranges:
+          * `10.0.0.0/24`: via subnet size
+          * `10.0.0.0&255.255.255.0`: via subnet mask
+          * `10.0.0.1-10.0.0.254`: via range
 
           A optional port number could be added with a '@':
-          * 2001:1234::1@1234
+          * `2001:1234::1@1234`
 
-          <key-name | NOKEY | BLOCKED>
-          * <key-name> will use the specified TSIG key
-          * NOKEY      no TSIG signature is required
-          * BLOCKED    notifies from non-listed or blocked IPs will be ignored
-          * ]]></screen>
+          `<key-name | NOKEY | BLOCKED>`
+          * `<key-name>` will use the specified TSIG key
+          * `NOKEY` no TSIG signature is required
+          * `BLOCKED`notifies from non-listed or blocked IPs will be ignored
         '';
       };
 
@@ -262,7 +262,7 @@ let
         '';
       };
 
-      dnssec = mkEnableOption "DNSSEC";
+      dnssec = mkEnableOption (lib.mdDoc "DNSSEC");
 
       dnssecPolicy = {
         algorithm = mkOption {
@@ -344,18 +344,17 @@ let
         type = types.listOf types.str;
         default = [];
         example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
-        description = ''
+        description = lib.mdDoc ''
           This primary server will notify all given secondary servers about
           zone changes.
-          <screen><![CDATA[
-          Format: <ip> <key-name | NOKEY>
 
-          <ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
+          Format: `<ip> <key-name | NOKEY>`
+
+          `<ip>` a plain IPv4/IPv6 address with on optional port number (ip@port)
 
-          <key-name | NOKEY>
-          * <key-name> sign notifies with the specified key
-          * NOKEY      don't sign notifies
-          ]]></screen>
+          `<key-name | NOKEY>`
+          - `<key-name>` sign notifies with the specified key
+          - `NOKEY` don't sign notifies
         '';
       };
 
@@ -372,7 +371,7 @@ let
         default = null;
         example = "2000::1@1234";
         description = lib.mdDoc ''
-          This address will be used for zone-transfere requests if configured
+          This address will be used for zone-transfer requests if configured
           as a secondary server or notifications in case of a primary server.
           Supply either a plain IPv4 or IPv6 address with an optional port
           number (ip@port).
@@ -383,9 +382,9 @@ let
         type = types.listOf types.str;
         default = [];
         example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
-        description = ''
+        description = lib.mdDoc ''
           Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
-          address range 192.0.2.0/24, 1.2.3.4&amp;255.255.0.0, 3.0.2.20-3.0.2.40
+          address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40
         '';
       };
 
@@ -479,9 +478,9 @@ in
   # options are ordered alphanumerically
   options.services.nsd = {
 
-    enable = mkEnableOption "NSD authoritative DNS server";
+    enable = mkEnableOption (lib.mdDoc "NSD authoritative DNS server");
 
-    bind8Stats = mkEnableOption "BIND8 like statistics";
+    bind8Stats = mkEnableOption (lib.mdDoc "BIND8 like statistics");
 
     dnssecInterval = mkOption {
       type = types.str;
@@ -589,7 +588,7 @@ in
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 53;
       description = lib.mdDoc ''
         Port the service should bind do.
@@ -617,7 +616,7 @@ in
       '';
     };
 
-    roundRobin = mkEnableOption "round robin rotation of records";
+    roundRobin = mkEnableOption (lib.mdDoc "round robin rotation of records");
 
     serverCount = mkOption {
       type = types.int;
@@ -736,7 +735,7 @@ in
 
     ratelimit = {
 
-      enable = mkEnableOption "ratelimit capabilities";
+      enable = mkEnableOption (lib.mdDoc "ratelimit capabilities");
 
       ipv4PrefixLength = mkOption {
         type = types.nullOr types.int;
@@ -797,7 +796,7 @@ in
 
     remoteControl = {
 
-      enable = mkEnableOption "remote control via nsd-control";
+      enable = mkEnableOption (lib.mdDoc "remote control via nsd-control");
 
       controlCertFile = mkOption {
         type = types.path;
@@ -826,7 +825,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8952;
         description = lib.mdDoc ''
           Port number for remote control operations (uses TLS over TCP).
diff --git a/nixpkgs/nixos/modules/services/networking/ntopng.nix b/nixpkgs/nixos/modules/services/networking/ntopng.nix
index e6344d7ff3b3..bf7ec19f02a6 100644
--- a/nixpkgs/nixos/modules/services/networking/ntopng.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntopng.nix
@@ -86,7 +86,7 @@ in
 
       redis.createInstance = mkOption {
         type = types.nullOr types.str;
-        default = if versionAtLeast config.system.stateVersion "22.05" then "ntopng" else "";
+        default = optionalString (versionAtLeast config.system.stateVersion "22.05") "ntopng";
         description = lib.mdDoc ''
           Local Redis instance name. Set to `null` to disable
           local Redis instance. Defaults to `""` for
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
index a89c7769152e..2d421abc8be7 100644
--- a/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
@@ -27,7 +27,7 @@ let
     ${cfg.extraConfig}
   '';
 
-  chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
+  chronyFlags = [ "-n" "-m" "-u" "chrony" "-f" "${configFile}" ] ++ cfg.extraFlags;
 in
 {
   options = {
@@ -147,9 +147,9 @@ in
     systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
 
     systemd.tmpfiles.rules = [
-      "d ${stateDir} 0755 chrony chrony - -"
-      "f ${driftFile} 0640 chrony chrony -"
-      "f ${keyFile} 0640 chrony chrony -"
+      "d ${stateDir} 0750 chrony chrony - -"
+      "f ${driftFile} 0640 chrony chrony - -"
+      "f ${keyFile} 0640 chrony chrony - -"
     ];
 
     systemd.services.chronyd =
@@ -164,15 +164,47 @@ in
         path = [ chronyPkg ];
 
         unitConfig.ConditionCapability = "CAP_SYS_TIME";
-        serviceConfig =
-          { Type = "simple";
-            ExecStart = "${chronyPkg}/bin/chronyd ${chronyFlags}";
-
-            ProtectHome = "yes";
-            ProtectSystem = "full";
-            PrivateTmp = "yes";
-          };
-
+        serviceConfig = {
+          Type = "simple";
+          ExecStart = "${chronyPkg}/bin/chronyd ${builtins.toString chronyFlags}";
+
+          # Proc filesystem
+          ProcSubset = "pid";
+          ProtectProc = "invisible";
+          # Access write directories
+          ReadWritePaths = [ "${stateDir}" ];
+          UMask = "0027";
+          # Capabilities
+          CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_DAC_OVERRIDE" "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" "CAP_SYS_RESOURCE" "CAP_SYS_TIME" ];
+          # Device Access
+          DeviceAllow = [ "char-pps rw" "char-ptp rw" "char-rtc rw" ];
+          DevicePolicy = "closed";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "full";
+          ProtectHome = true;
+          PrivateTmp = true;
+          PrivateDevices = false;
+          PrivateUsers = false;
+          ProtectHostname = true;
+          ProtectClock = false;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+          RestrictNamespaces = true;
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RemoveIPC = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "@chown" ];
+        };
       };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
index a9dae2c8667a..036a8df635db 100644
--- a/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
@@ -25,7 +25,7 @@ let
     ${cfg.extraConfig}
   '';
 
-  ntpFlags = "-c ${configFile} -u ntp:ntp ${toString cfg.extraFlags}";
+  ntpFlags = [ "-c" "${configFile}" "-u" "ntp:ntp" ] ++ cfg.extraFlags;
 
 in
 
@@ -137,7 +137,7 @@ in
           '';
 
         serviceConfig = {
-          ExecStart = "@${ntp}/bin/ntpd ntpd -g ${ntpFlags}";
+          ExecStart = "@${ntp}/bin/ntpd ntpd -g ${builtins.toString ntpFlags}";
           Type = "forking";
         };
       };
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
index 2a766a134f77..05df1f6e6266 100644
--- a/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
@@ -19,7 +19,7 @@ in
   ###### interface
 
   options.services.openntpd = {
-    enable = mkEnableOption "OpenNTP time synchronization server";
+    enable = mkEnableOption (lib.mdDoc "OpenNTP time synchronization server");
 
     servers = mkOption {
       default = config.services.ntp.servers;
diff --git a/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix b/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
index 85f5c799a310..e74e1dd6b795 100644
--- a/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
+++ b/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
@@ -3,7 +3,7 @@
 
 in {
   options.services.nullidentdmod = with types; {
-    enable = mkEnableOption "the nullidentdmod identd daemon";
+    enable = mkEnableOption (lib.mdDoc "the nullidentdmod identd daemon");
 
     userid = mkOption {
       type = nullOr str;
diff --git a/nixpkgs/nixos/modules/services/networking/nylon.nix b/nixpkgs/nixos/modules/services/networking/nylon.nix
index 3eb15c23bef5..401dbe97c52d 100644
--- a/nixpkgs/nixos/modules/services/networking/nylon.nix
+++ b/nixpkgs/nixos/modules/services/networking/nylon.nix
@@ -81,7 +81,7 @@ let
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 1080;
         description = lib.mdDoc ''
           What port to listen for client requests, default is 1080.
@@ -139,7 +139,7 @@ in
 
     services.nylon = mkOption {
       default = {};
-      description = "Collection of named nylon instances";
+      description = lib.mdDoc "Collection of named nylon instances";
       type = with types; attrsOf (submodule nylonOpts);
       internal = true;
     };
diff --git a/nixpkgs/nixos/modules/services/networking/ocserv.nix b/nixpkgs/nixos/modules/services/networking/ocserv.nix
index dc26ffeafeef..9548fd92dbda 100644
--- a/nixpkgs/nixos/modules/services/networking/ocserv.nix
+++ b/nixpkgs/nixos/modules/services/networking/ocserv.nix
@@ -10,12 +10,12 @@ in
 
 {
   options.services.ocserv = {
-    enable = mkEnableOption "ocserv";
+    enable = mkEnableOption (lib.mdDoc "ocserv");
 
     config = mkOption {
       type = types.lines;
 
-      description = ''
+      description = lib.mdDoc ''
         Configuration content to start an OCServ server.
 
         For a full configuration reference,please refer to the online documentation
diff --git a/nixpkgs/nixos/modules/services/networking/ofono.nix b/nixpkgs/nixos/modules/services/networking/ofono.nix
index 6192857cd3ed..960fc35a70ac 100644
--- a/nixpkgs/nixos/modules/services/networking/ofono.nix
+++ b/nixpkgs/nixos/modules/services/networking/ofono.nix
@@ -19,7 +19,7 @@ in
   ###### interface
   options = {
     services.ofono = {
-      enable = mkEnableOption "Ofono";
+      enable = mkEnableOption (lib.mdDoc "Ofono");
 
       plugins = mkOption {
         type = types.listOf types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/onedrive.nix b/nixpkgs/nixos/modules/services/networking/onedrive.nix
index 5a531d7a47fd..d782ec05352b 100644
--- a/nixpkgs/nixos/modules/services/networking/onedrive.nix
+++ b/nixpkgs/nixos/modules/services/networking/onedrive.nix
@@ -26,11 +26,7 @@ in {
   ### Interface
 
   options.services.onedrive = {
-    enable = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = lib.mdDoc "Enable OneDrive service";
-    };
+     enable = lib.mkEnableOption (lib.mdDoc "OneDrive service");
 
      package = lib.mkOption {
        type = lib.types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/openconnect.nix b/nixpkgs/nixos/modules/services/networking/openconnect.nix
index 469f0a3bc3bb..7f9006053b89 100644
--- a/nixpkgs/nixos/modules/services/networking/openconnect.nix
+++ b/nixpkgs/nixos/modules/services/networking/openconnect.nix
@@ -32,6 +32,7 @@ let
         description = lib.mdDoc "Username to authenticate with.";
         example = "example-user";
         type = types.nullOr types.str;
+        default = null;
       };
 
       # Note: It does not make sense to provide a way to declaratively
@@ -89,6 +90,7 @@ let
   generateConfig = name: icfg:
     pkgs.writeText "config" ''
       interface=${name}
+      ${optionalString (icfg.protocol != null) "protocol=${icfg.protocol}"}
       ${optionalString (icfg.user != null) "user=${icfg.user}"}
       ${optionalString (icfg.passwordFile != null) "passwd-on-stdin"}
       ${optionalString (icfg.certificate != null)
@@ -108,14 +110,14 @@ let
       ExecStart = "${openconnect}/bin/openconnect --config=${
           generateConfig name icfg
         } ${icfg.gateway}";
-      StandardInput = "file:${icfg.passwordFile}";
+      StandardInput = lib.mkIf (icfg.passwordFile != null) "file:${icfg.passwordFile}";
 
       ProtectHome = true;
     };
   };
 in {
   options.networking.openconnect = {
-    package = mkPackageOption pkgs "openconnect" { };
+    package = mkPackageOptionMD pkgs "openconnect" { };
 
     interfaces = mkOption {
       description = lib.mdDoc "OpenConnect interfaces.";
diff --git a/nixpkgs/nixos/modules/services/networking/openvpn.nix b/nixpkgs/nixos/modules/services/networking/openvpn.nix
index 492a0936fdbb..9a5866f2afd4 100644
--- a/nixpkgs/nixos/modules/services/networking/openvpn.nix
+++ b/nixpkgs/nixos/modules/services/networking/openvpn.nix
@@ -14,7 +14,6 @@ let
       path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
 
       upScript = ''
-        #! /bin/sh
         export PATH=${path}
 
         # For convenience in client scripts, extract the remote domain
@@ -34,7 +33,6 @@ let
       '';
 
       downScript = ''
-        #! /bin/sh
         export PATH=${path}
         ${optionalString cfg.updateResolvConf
            "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
@@ -47,9 +45,9 @@ let
           ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
           ${cfg.config}
           ${optionalString (cfg.up != "" || cfg.updateResolvConf)
-              "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"}
+              "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
           ${optionalString (cfg.down != "" || cfg.updateResolvConf)
-              "down ${pkgs.writeScript "openvpn-${name}-down" downScript}"}
+              "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
           ${optionalString (cfg.authUserPass != null)
               "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
                 ${cfg.authUserPass.username}
@@ -57,7 +55,8 @@ let
               ''}"}
         '';
 
-    in {
+    in
+    {
       description = "OpenVPN instance ‘${name}’";
 
       wantedBy = optional cfg.autoStart "multi-user.target";
@@ -70,6 +69,16 @@ let
       serviceConfig.Type = "notify";
     };
 
+  restartService = optionalAttrs cfg.restartAfterSleep {
+    openvpn-restart = {
+      wantedBy = [ "sleep.target" ];
+      path = [ pkgs.procps ];
+      script = "pkill --signal SIGHUP --exact openvpn";
+      #SIGHUP makes openvpn process to self-exit and then it got restarted by systemd because of Restart=always
+      description = "Sends a signal to OpenVPN process to trigger a restart after return from sleep";
+    };
+  };
+
 in
 
 {
@@ -82,7 +91,7 @@ in
   options = {
 
     services.openvpn.servers = mkOption {
-      default = {};
+      default = { };
 
       example = literalExpression ''
         {
@@ -201,14 +210,21 @@ in
 
     };
 
+    services.openvpn.restartAfterSleep = mkOption {
+      default = true;
+      type = types.bool;
+      description = lib.mdDoc "Whether OpenVPN client should be restarted after sleep.";
+    };
+
   };
 
 
   ###### implementation
 
-  config = mkIf (cfg.servers != {}) {
+  config = mkIf (cfg.servers != { }) {
 
-    systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
+    systemd.services = (listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
+      // restartService;
 
     environment.systemPackages = [ openvpn ];
 
diff --git a/nixpkgs/nixos/modules/services/networking/ostinato.nix b/nixpkgs/nixos/modules/services/networking/ostinato.nix
index 808ccdd4e0cf..dc07313ea901 100644
--- a/nixpkgs/nixos/modules/services/networking/ostinato.nix
+++ b/nixpkgs/nixos/modules/services/networking/ostinato.nix
@@ -26,10 +26,10 @@ in
 
     services.ostinato = {
 
-      enable = mkEnableOption "Ostinato agent-controller (Drone)";
+      enable = mkEnableOption (lib.mdDoc "Ostinato agent-controller (Drone)");
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 7878;
         description = lib.mdDoc ''
           Port to listen on.
@@ -54,7 +54,7 @@ in
           default = "0.0.0.0";
           description = lib.mdDoc ''
             By default, the Drone RPC server will listen on all interfaces and
-            local IPv4 adresses for incoming connections from clients.  Specify
+            local IPv4 addresses for incoming connections from clients.  Specify
             a single IPv4 or IPv6 address if you want to restrict that.
             To listen on any IPv6 address, use ::
           '';
diff --git a/nixpkgs/nixos/modules/services/networking/owamp.nix b/nixpkgs/nixos/modules/services/networking/owamp.nix
index baf64347b099..32b2dab9e3c7 100644
--- a/nixpkgs/nixos/modules/services/networking/owamp.nix
+++ b/nixpkgs/nixos/modules/services/networking/owamp.nix
@@ -10,7 +10,7 @@ in
   ###### interface
 
   options = {
-    services.owamp.enable = mkEnableOption "Enable OWAMP server";
+    services.owamp.enable = mkEnableOption (lib.mdDoc "OWAMP server");
   };
 
 
diff --git a/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix b/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
index 7319793101c5..2f07cefc736e 100644
--- a/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
+++ b/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
@@ -27,7 +27,7 @@ let
 
 in {
   options.services.pdns-recursor = {
-    enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
+    enable = mkEnableOption (lib.mdDoc "PowerDNS Recursor, a recursive DNS server");
 
     dns.address = mkOption {
       type = oneOrMore types.str;
@@ -38,7 +38,7 @@ in {
     };
 
     dns.port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 53;
       description = lib.mdDoc ''
         Port number Recursor DNS server will bind to.
@@ -67,7 +67,7 @@ in {
     };
 
     api.port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8082;
       description = lib.mdDoc ''
         Port number Recursor REST API server will bind to.
diff --git a/nixpkgs/nixos/modules/services/networking/pdnsd.nix b/nixpkgs/nixos/modules/services/networking/pdnsd.nix
index 03c9005413b5..8fe27a44eee6 100644
--- a/nixpkgs/nixos/modules/services/networking/pdnsd.nix
+++ b/nixpkgs/nixos/modules/services/networking/pdnsd.nix
@@ -24,7 +24,7 @@ in
 
 { options =
     { services.pdnsd =
-        { enable = mkEnableOption "pdnsd";
+        { enable = mkEnableOption (lib.mdDoc "pdnsd");
 
           cacheDir = mkOption {
             type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/peroxide.nix b/nixpkgs/nixos/modules/services/networking/peroxide.nix
new file mode 100644
index 000000000000..885ee1d96cd0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/peroxide.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.peroxide;
+  settingsFormat = pkgs.formats.yaml { };
+  stateDir = "peroxide";
+in
+{
+  options.services.peroxide = {
+    enable = mkEnableOption (lib.mdDoc "peroxide");
+
+    package = mkPackageOptionMD pkgs "peroxide" {
+      default = [ "peroxide" ];
+    };
+
+    logLevel = mkOption {
+      # https://github.com/sirupsen/logrus#level-logging
+      type = types.enum [ "Panic" "Fatal" "Error" "Warning" "Info" "Debug" "Trace" ];
+      default = "Warning";
+      example = "Info";
+      description = lib.mdDoc "Only log messages of this priority or higher.";
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          UserPortImap = mkOption {
+            type = types.port;
+            default = 1143;
+            description = lib.mdDoc "The port on which to listen for IMAP connections.";
+          };
+
+          UserPortSmtp = mkOption {
+            type = types.port;
+            default = 1025;
+            description = lib.mdDoc "The port on which to listen for SMTP connections.";
+          };
+
+          ServerAddress = mkOption {
+            type = types.str;
+            default = "[::0]";
+            example = "localhost";
+            description = lib.mdDoc "The address on which to listen for connections.";
+          };
+        };
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Configuration for peroxide.  See
+        [config.example.yaml](https://github.com/ljanyst/peroxide/blob/master/config.example.yaml)
+        for an example configuration.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.peroxide.settings = {
+      # peroxide deletes the cache directory on startup, which requires write
+      # permission on the parent directory, so we can't use
+      # /var/cache/peroxide
+      CacheDir = "/var/cache/peroxide/cache";
+      X509Key = mkDefault "/var/lib/${stateDir}/key.pem";
+      X509Cert = mkDefault "/var/lib/${stateDir}/cert.pem";
+      CookieJar = "/var/lib/${stateDir}/cookies.json";
+      CredentialsStore = "/var/lib/${stateDir}/credentials.json";
+    };
+
+    users.users.peroxide = {
+      isSystemUser = true;
+      group = "peroxide";
+    };
+    users.groups.peroxide = { };
+
+    systemd.services.peroxide = {
+      description = "Peroxide ProtonMail bridge";
+      requires = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      restartTriggers = [ config.environment.etc."peroxide.conf".source ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = "peroxide";
+        LogsDirectory = "peroxide";
+        LogsDirectoryMode = "0750";
+        # Specify just "peroxide" so that the user has write permission, because
+        # peroxide deletes and recreates the cache directory on startup.
+        CacheDirectory = [ "peroxide" "peroxide/cache" ];
+        CacheDirectoryMode = "0700";
+        StateDirectory = stateDir;
+        StateDirectoryMode = "0700";
+        ExecStart = "${cfg.package}/bin/peroxide -log-file=/var/log/peroxide/peroxide.log -log-level ${cfg.logLevel}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+
+      preStart = ''
+        # Create a self-signed certificate if no certificate exists.
+        if [[ ! -e "${cfg.settings.X509Key}" && ! -e "${cfg.settings.X509Cert}" ]]; then
+            ${cfg.package}/bin/peroxide-cfg -action gen-x509 \
+              -x509-org 'N/A' \
+              -x509-cn 'nixos' \
+              -x509-cert "${cfg.settings.X509Cert}" \
+              -x509-key "${cfg.settings.X509Key}"
+        fi
+      '';
+    };
+
+    # https://github.com/ljanyst/peroxide/blob/master/peroxide.logrotate
+    services.logrotate.settings.peroxide = {
+      files = "/var/log/peroxide/peroxide.log";
+      rotate = 31;
+      frequency = "daily";
+      compress = true;
+      delaycompress = true;
+      missingok = true;
+      notifempty = true;
+      su = "peroxide peroxide";
+      postrotate = "systemctl reload peroxide";
+    };
+
+    environment.etc."peroxide.conf".source = settingsFormat.generate "peroxide.conf" cfg.settings;
+    environment.systemPackages = [ cfg.package ];
+  };
+
+  meta.maintainers = with maintainers; [ aanderse aidalgol ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/picosnitch.nix b/nixpkgs/nixos/modules/services/networking/picosnitch.nix
new file mode 100644
index 000000000000..c9b38c1929ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/picosnitch.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.picosnitch;
+in
+{
+  options.services.picosnitch = {
+    enable = mkEnableOption (lib.mdDoc "picosnitch daemon");
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.picosnitch ];
+    systemd.services.picosnitch = {
+      description = "picosnitch";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+        RestartSec = 5;
+        ExecStart = "${pkgs.picosnitch}/bin/picosnitch start-no-daemon";
+        PIDFile = "/run/picosnitch/picosnitch.pid";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pixiecore.nix b/nixpkgs/nixos/modules/services/networking/pixiecore.nix
index c88081af6203..f410be471646 100644
--- a/nixpkgs/nixos/modules/services/networking/pixiecore.nix
+++ b/nixpkgs/nixos/modules/services/networking/pixiecore.nix
@@ -10,7 +10,7 @@ in
 
   options = {
     services.pixiecore = {
-      enable = mkEnableOption "Pixiecore";
+      enable = mkEnableOption (lib.mdDoc "Pixiecore");
 
       openFirewall = mkOption {
         type = types.bool;
@@ -23,7 +23,7 @@ in
       mode = mkOption {
         description = lib.mdDoc "Which mode to use";
         default = "boot";
-        type = types.enum [ "api" "boot" ];
+        type = types.enum [ "api" "boot" "quick" ];
       };
 
       debug = mkOption {
@@ -38,6 +38,12 @@ in
         description = lib.mdDoc "Handle DHCP traffic without binding to the DHCP server port";
       };
 
+      quick = mkOption {
+        description = lib.mdDoc "Which quick option to use";
+        default = "xyz";
+        type = types.enum [ "arch" "centos" "coreos" "debian" "fedora" "ubuntu" "xyz" ];
+      };
+
       kernel = mkOption {
         type = types.str or types.path;
         default = "";
@@ -117,6 +123,8 @@ in
               then [ "boot" cfg.kernel ]
                    ++ optional (cfg.initrd != "") cfg.initrd
                    ++ optionals (cfg.cmdLine != "") [ "--cmdline" cfg.cmdLine ]
+              else if cfg.mode == "quick"
+              then [ "quick" cfg.quick ]
               else [ "api" cfg.apiServer ];
           in
             ''
diff --git a/nixpkgs/nixos/modules/services/networking/pleroma.md b/nixpkgs/nixos/modules/services/networking/pleroma.md
new file mode 100644
index 000000000000..7c499e1c616c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pleroma.md
@@ -0,0 +1,180 @@
+# Pleroma {#module-services-pleroma}
+
+[Pleroma](https://pleroma.social/) is a lightweight activity pub server.
+
+## Generating the Pleroma config {#module-services-pleroma-generate-config}
+
+The `pleroma_ctl` CLI utility will prompt you some questions and it will generate an initial config file. This is an example of usage
+```ShellSession
+$ mkdir tmp-pleroma
+$ cd tmp-pleroma
+$ nix-shell -p pleroma-otp
+$ pleroma_ctl instance gen --output config.exs --output-psql setup.psql
+```
+
+The `config.exs` file can be further customized following the instructions on the [upstream documentation](https://docs-develop.pleroma.social/backend/configuration/cheatsheet/). Many refinements can be applied also after the service is running.
+
+## Initializing the database {#module-services-pleroma-initialize-db}
+
+First, the Postgresql service must be enabled in the NixOS configuration
+```
+services.postgresql = {
+  enable = true;
+  package = pkgs.postgresql_13;
+};
+```
+and activated with the usual
+```ShellSession
+$ nixos-rebuild switch
+```
+
+Then you can create and seed the database, using the `setup.psql` file that you generated in the previous section, by running
+```ShellSession
+$ sudo -u postgres psql -f setup.psql
+```
+
+## Enabling the Pleroma service locally {#module-services-pleroma-enable}
+
+In this section we will enable the Pleroma service only locally, so its configurations can be improved incrementally.
+
+This is an example of configuration, where [](#opt-services.pleroma.configs) option contains the content of the file `config.exs`, generated [in the first section](#module-services-pleroma-generate-config), but with the secrets (database password, endpoint secret key, salts, etc.) removed. Removing secrets is important, because otherwise they will be stored publicly in the Nix store.
+```
+services.pleroma = {
+  enable = true;
+  secretConfigFile = "/var/lib/pleroma/secrets.exs";
+  configs = [
+    ''
+    import Config
+
+    config :pleroma, Pleroma.Web.Endpoint,
+      url: [host: "pleroma.example.net", scheme: "https", port: 443],
+      http: [ip: {127, 0, 0, 1}, port: 4000]
+
+    config :pleroma, :instance,
+      name: "Test",
+      email: "admin@example.net",
+      notify_email: "admin@example.net",
+      limit: 5000,
+      registrations_open: true
+
+    config :pleroma, :media_proxy,
+      enabled: false,
+      redirect_on_failure: true
+
+    config :pleroma, Pleroma.Repo,
+      adapter: Ecto.Adapters.Postgres,
+      username: "pleroma",
+      database: "pleroma",
+      hostname: "localhost"
+
+    # Configure web push notifications
+    config :web_push_encryption, :vapid_details,
+      subject: "mailto:admin@example.net"
+
+    # ... TO CONTINUE ...
+    ''
+  ];
+};
+```
+
+Secrets must be moved into a file pointed by [](#opt-services.pleroma.secretConfigFile), in our case `/var/lib/pleroma/secrets.exs`. This file can be created copying the previously generated `config.exs` file and then removing all the settings, except the secrets. This is an example
+```
+# Pleroma instance passwords
+
+import Config
+
+config :pleroma, Pleroma.Web.Endpoint,
+   secret_key_base: "<the secret generated by pleroma_ctl>",
+   signing_salt: "<the secret generated by pleroma_ctl>"
+
+config :pleroma, Pleroma.Repo,
+  password: "<the secret generated by pleroma_ctl>"
+
+# Configure web push notifications
+config :web_push_encryption, :vapid_details,
+  public_key: "<the secret generated by pleroma_ctl>",
+  private_key: "<the secret generated by pleroma_ctl>"
+
+# ... TO CONTINUE ...
+```
+Note that the lines of the same configuration group are comma separated (i.e. all the lines end with a comma, except the last one), so when the lines with passwords are added or removed, commas must be adjusted accordingly.
+
+The service can be enabled with the usual
+```ShellSession
+$ nixos-rebuild switch
+```
+
+The service is accessible only from the local `127.0.0.1:4000` port. It can be tested using a port forwarding like this
+```ShellSession
+$ ssh -L 4000:localhost:4000 myuser@example.net
+```
+and then accessing <http://localhost:4000> from a web browser.
+
+## Creating the admin user {#module-services-pleroma-admin-user}
+
+After Pleroma service is running, all [Pleroma administration utilities](https://docs-develop.pleroma.social/) can be used. In particular an admin user can be created with
+```ShellSession
+$ pleroma_ctl user new <nickname> <email>  --admin --moderator --password <password>
+```
+
+## Configuring Nginx {#module-services-pleroma-nginx}
+
+In this configuration, Pleroma is listening only on the local port 4000. Nginx can be configured as a Reverse Proxy, for forwarding requests from public ports to the Pleroma service. This is an example of configuration, using
+[Let's Encrypt](https://letsencrypt.org/) for the TLS certificates
+```
+security.acme = {
+  email = "root@example.net";
+  acceptTerms = true;
+};
+
+services.nginx = {
+  enable = true;
+  addSSL = true;
+
+  recommendedTlsSettings = true;
+  recommendedOptimisation = true;
+  recommendedGzipSettings = true;
+
+  recommendedProxySettings = false;
+  # NOTE: if enabled, the NixOS proxy optimizations will override the Pleroma
+  # specific settings, and they will enter in conflict.
+
+  virtualHosts = {
+    "pleroma.example.net" = {
+      http2 = true;
+      enableACME = true;
+      forceSSL = true;
+
+      locations."/" = {
+        proxyPass = "http://127.0.0.1:4000";
+
+        extraConfig = ''
+          etag on;
+          gzip on;
+
+          add_header 'Access-Control-Allow-Origin' '*' always;
+          add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
+          add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
+          add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
+          if ($request_method = OPTIONS) {
+            return 204;
+          }
+          add_header X-XSS-Protection "1; mode=block";
+          add_header X-Permitted-Cross-Domain-Policies none;
+          add_header X-Frame-Options DENY;
+          add_header X-Content-Type-Options nosniff;
+          add_header Referrer-Policy same-origin;
+          add_header X-Download-Options noopen;
+          proxy_http_version 1.1;
+          proxy_set_header Upgrade $http_upgrade;
+          proxy_set_header Connection "upgrade";
+          proxy_set_header Host $host;
+
+          client_max_body_size 16m;
+          # NOTE: increase if users need to upload very big files
+        '';
+      };
+    };
+  };
+};
+```
diff --git a/nixpkgs/nixos/modules/services/networking/pleroma.nix b/nixpkgs/nixos/modules/services/networking/pleroma.nix
index de9d0821c63a..e9db7f3eab8e 100644
--- a/nixpkgs/nixos/modules/services/networking/pleroma.nix
+++ b/nixpkgs/nixos/modules/services/networking/pleroma.nix
@@ -4,7 +4,7 @@ let
 in {
   options = {
     services.pleroma = with lib; {
-      enable = mkEnableOption "pleroma";
+      enable = mkEnableOption (lib.mdDoc "pleroma");
 
       package = mkOption {
         type = types.package;
@@ -52,7 +52,7 @@ in {
           the right place to store any secret
 
           Have a look to Pleroma section in the NixOS manual for more
-          informations.
+          information.
           '';
       };
 
@@ -141,9 +141,11 @@ in {
         NoNewPrivileges = true;
         CapabilityBoundingSet = "~CAP_SYS_ADMIN";
       };
+      # disksup requires bash
+      path = [ pkgs.bash ];
     };
 
   };
   meta.maintainers = with lib.maintainers; [ ninjatrappeur ];
-  meta.doc = ./pleroma.xml;
+  meta.doc = ./pleroma.md;
 }
diff --git a/nixpkgs/nixos/modules/services/networking/pleroma.xml b/nixpkgs/nixos/modules/services/networking/pleroma.xml
deleted file mode 100644
index ad0a481af28b..000000000000
--- a/nixpkgs/nixos/modules/services/networking/pleroma.xml
+++ /dev/null
@@ -1,188 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-pleroma">
- <title>Pleroma</title>
- <para>
-  <link xlink:href="https://pleroma.social/">Pleroma</link> is a lightweight activity pub server.</para>
- <section xml:id="module-services-pleroma-generate-config">
-  <title>Generating the Pleroma config</title>
-  <para>The <literal>pleroma_ctl</literal> CLI utility will prompt you some questions and it will generate an initial config file. This is an example of usage
-<programlisting>
-<prompt>$ </prompt>mkdir tmp-pleroma
-<prompt>$ </prompt>cd tmp-pleroma
-<prompt>$ </prompt>nix-shell -p pleroma-otp
-<prompt>$ </prompt>pleroma_ctl instance gen --output config.exs --output-psql setup.psql
-</programlisting>
-  </para>
-  <para>The <literal>config.exs</literal> file can be further customized following the instructions on the <link xlink:href="https://docs-develop.pleroma.social/backend/configuration/cheatsheet/">upstream documentation</link>. Many refinements can be applied also after the service is running.</para>
- </section>
- <section xml:id="module-services-pleroma-initialize-db">
-  <title>Initializing the database</title>
-  <para>First, the Postgresql service must be enabled in the NixOS configuration
-<programlisting>
-services.postgresql = {
-  enable = true;
-  package = pkgs.postgresql_13;
-};
-</programlisting>
-and activated with the usual
-<programlisting>
-<prompt>$ </prompt>nixos-rebuild switch
-</programlisting>
-  </para>
-  <para>Then you can create and seed the database, using the <literal>setup.psql</literal> file that you generated in the previous section, by running
-<programlisting>
-<prompt>$ </prompt>sudo -u postgres psql -f setup.psql
-</programlisting>
-  </para>
- </section>
- <section xml:id="module-services-pleroma-enable">
-  <title>Enabling the Pleroma service locally</title>
-  <para>In this section we will enable the Pleroma service only locally, so its configurations can be improved incrementally.</para>
-  <para>This is an example of configuration, where <link linkend="opt-services.pleroma.configs">services.pleroma.configs</link> option contains the content of the file <literal>config.exs</literal>, generated <link linkend="module-services-pleroma-generate-config">in the first section</link>, but with the secrets (database password, endpoint secret key, salts, etc.) removed. Removing secrets is important, because otherwise they will be stored publicly in the Nix store.
-<programlisting>
-services.pleroma = {
-  enable = true;
-  secretConfigFile = "/var/lib/pleroma/secrets.exs";
-  configs = [
-    ''
-    import Config
-
-    config :pleroma, Pleroma.Web.Endpoint,
-      url: [host: "pleroma.example.net", scheme: "https", port: 443],
-      http: [ip: {127, 0, 0, 1}, port: 4000]
-
-    config :pleroma, :instance,
-      name: "Test",
-      email: "admin@example.net",
-      notify_email: "admin@example.net",
-      limit: 5000,
-      registrations_open: true
-
-    config :pleroma, :media_proxy,
-      enabled: false,
-      redirect_on_failure: true
-
-    config :pleroma, Pleroma.Repo,
-      adapter: Ecto.Adapters.Postgres,
-      username: "pleroma",
-      database: "pleroma",
-      hostname: "localhost"
-
-    # Configure web push notifications
-    config :web_push_encryption, :vapid_details,
-      subject: "mailto:admin@example.net"
-
-    # ... TO CONTINUE ...
-    ''
-  ];
-};
-</programlisting>
-  </para>
-  <para>Secrets must be moved into a file pointed by <link linkend="opt-services.pleroma.secretConfigFile">services.pleroma.secretConfigFile</link>, in our case <literal>/var/lib/pleroma/secrets.exs</literal>. This file can be created copying the previously generated <literal>config.exs</literal> file and then removing all the settings, except the secrets. This is an example
-<programlisting>
-# Pleroma instance passwords
-
-import Config
-
-config :pleroma, Pleroma.Web.Endpoint,
-   secret_key_base: "&lt;the secret generated by pleroma_ctl&gt;",
-   signing_salt: "&lt;the secret generated by pleroma_ctl&gt;"
-
-config :pleroma, Pleroma.Repo,
-  password: "&lt;the secret generated by pleroma_ctl&gt;"
-
-# Configure web push notifications
-config :web_push_encryption, :vapid_details,
-  public_key: "&lt;the secret generated by pleroma_ctl&gt;",
-  private_key: "&lt;the secret generated by pleroma_ctl&gt;"
-
-# ... TO CONTINUE ...
-</programlisting>
-  Note that the lines of the same configuration group are comma separated (i.e. all the lines end with a comma, except the last one), so when the lines with passwords are added or removed, commas must be adjusted accordingly.</para>
-
-  <para>The service can be enabled with the usual
-<programlisting>
-<prompt>$ </prompt>nixos-rebuild switch
-</programlisting>
-  </para>
-  <para>The service is accessible only from the local <literal>127.0.0.1:4000</literal> port. It can be tested using a port forwarding like this
-<programlisting>
-<prompt>$ </prompt>ssh -L 4000:localhost:4000 myuser@example.net
-</programlisting>
-and then accessing <link xlink:href="http://localhost:4000">http://localhost:4000</link> from a web browser.</para>
- </section>
- <section xml:id="module-services-pleroma-admin-user">
-  <title>Creating the admin user</title>
-  <para>After Pleroma service is running, all <link xlink:href="https://docs-develop.pleroma.social/">Pleroma administration utilities</link> can be used. In particular an admin user can be created with
-<programlisting>
-<prompt>$ </prompt>pleroma_ctl user new &lt;nickname&gt; &lt;email&gt;  --admin --moderator --password &lt;password&gt;
-</programlisting>
-  </para>
- </section>
- <section xml:id="module-services-pleroma-nginx">
-  <title>Configuring Nginx</title>
-  <para>In this configuration, Pleroma is listening only on the local port 4000. Nginx can be configured as a Reverse Proxy, for forwarding requests from public ports to the Pleroma service. This is an example of configuration, using
-<link xlink:href="https://letsencrypt.org/">Let's Encrypt</link> for the TLS certificates
-<programlisting>
-security.acme = {
-  email = "root@example.net";
-  acceptTerms = true;
-};
-
-services.nginx = {
-  enable = true;
-  addSSL = true;
-
-  recommendedTlsSettings = true;
-  recommendedOptimisation = true;
-  recommendedGzipSettings = true;
-
-  recommendedProxySettings = false;
-  # NOTE: if enabled, the NixOS proxy optimizations will override the Pleroma
-  # specific settings, and they will enter in conflict.
-
-  virtualHosts = {
-    "pleroma.example.net" = {
-      http2 = true;
-      enableACME = true;
-      forceSSL = true;
-
-      locations."/" = {
-        proxyPass = "http://127.0.0.1:4000";
-
-        extraConfig = ''
-          etag on;
-          gzip on;
-
-          add_header 'Access-Control-Allow-Origin' '*' always;
-          add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
-          add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
-          add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
-          if ($request_method = OPTIONS) {
-            return 204;
-          }
-          add_header X-XSS-Protection "1; mode=block";
-          add_header X-Permitted-Cross-Domain-Policies none;
-          add_header X-Frame-Options DENY;
-          add_header X-Content-Type-Options nosniff;
-          add_header Referrer-Policy same-origin;
-          add_header X-Download-Options noopen;
-          proxy_http_version 1.1;
-          proxy_set_header Upgrade $http_upgrade;
-          proxy_set_header Connection "upgrade";
-          proxy_set_header Host $host;
-
-          client_max_body_size 16m;
-          # NOTE: increase if users need to upload very big files
-        '';
-      };
-    };
-  };
-};
-</programlisting>
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/networking/polipo.nix b/nixpkgs/nixos/modules/services/networking/polipo.nix
index d820e1b397b8..8581553829bf 100644
--- a/nixpkgs/nixos/modules/services/networking/polipo.nix
+++ b/nixpkgs/nixos/modules/services/networking/polipo.nix
@@ -23,11 +23,7 @@ in
 
     services.polipo = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Whether to run the polipo caching web proxy.";
-      };
+      enable = mkEnableOption (lib.mdDoc "polipo caching web proxy");
 
       proxyAddress = mkOption {
         type = types.str;
@@ -36,7 +32,7 @@ in
       };
 
       proxyPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8123;
         description = lib.mdDoc "TCP port on which Polipo will listen.";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/powerdns.nix b/nixpkgs/nixos/modules/services/networking/powerdns.nix
index f7c72361dfa1..850a128cf1a4 100644
--- a/nixpkgs/nixos/modules/services/networking/powerdns.nix
+++ b/nixpkgs/nixos/modules/services/networking/powerdns.nix
@@ -5,10 +5,11 @@ with lib;
 let
   cfg = config.services.powerdns;
   configDir = pkgs.writeTextDir "pdns.conf" "${cfg.extraConfig}";
+  finalConfigDir = if cfg.secretFile == null then configDir else "/run/pdns";
 in {
   options = {
     services.powerdns = {
-      enable = mkEnableOption "PowerDNS domain name server";
+      enable = mkEnableOption (lib.mdDoc "PowerDNS domain name server");
 
       extraConfig = mkOption {
         type = types.lines;
@@ -19,6 +20,19 @@ in {
           for details on supported values.
         '';
       };
+
+      secretFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/powerdns.env";
+        description = lib.mdDoc ''
+          Environment variables from this file will be interpolated into the
+          final config file using envsubst with this syntax: `$ENVIRONMENT`
+          or `''${VARIABLE}`.
+          The file should contain lines formatted as `SECRET_VAR=SECRET_VALUE`.
+          This is useful to avoid putting secrets into the nix store.
+        '';
+      };
     };
   };
 
@@ -31,7 +45,13 @@ in {
       after = [ "network.target" "mysql.service" "postgresql.service" "openldap.service" ];
 
       serviceConfig = {
-        ExecStart = [ "" "${pkgs.pdns}/bin/pdns_server --config-dir=${configDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ];
+        EnvironmentFile = lib.optional (cfg.secretFile != null) cfg.secretFile;
+        ExecStartPre = lib.optional (cfg.secretFile != null)
+          (pkgs.writeShellScript "pdns-pre-start" ''
+            umask 077
+            ${pkgs.envsubst}/bin/envsubst -i "${configDir}/pdns.conf" > ${finalConfigDir}/pdns.conf
+          '');
+        ExecStart = [ "" "${pkgs.pdns}/bin/pdns_server --config-dir=${finalConfigDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ];
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/pppd.nix b/nixpkgs/nixos/modules/services/networking/pppd.nix
index d923b49dda29..75fc04c67571 100644
--- a/nixpkgs/nixos/modules/services/networking/pppd.nix
+++ b/nixpkgs/nixos/modules/services/networking/pppd.nix
@@ -12,7 +12,7 @@ in
 
   options = {
     services.pppd = {
-      enable = mkEnableOption "pppd";
+      enable = mkEnableOption (lib.mdDoc "pppd");
 
       package = mkOption {
         default = pkgs.ppp;
diff --git a/nixpkgs/nixos/modules/services/networking/pptpd.nix b/nixpkgs/nixos/modules/services/networking/pptpd.nix
index d16496a2cb52..703dda99803e 100644
--- a/nixpkgs/nixos/modules/services/networking/pptpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/pptpd.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.pptpd = {
-      enable = mkEnableOption "pptpd, the Point-to-Point Tunneling Protocol daemon";
+      enable = mkEnableOption (lib.mdDoc "pptpd, the Point-to-Point Tunneling Protocol daemon");
 
       serverIp = mkOption {
         type        = types.str;
@@ -82,7 +82,7 @@ with lib;
       ppp-pptpd-wrapped = pkgs.stdenv.mkDerivation {
         name         = "ppp-pptpd-wrapped";
         phases       = [ "installPhase" ];
-        buildInputs  = with pkgs; [ makeWrapper ];
+        nativeBuildInputs  = with pkgs; [ makeWrapper ];
         installPhase = ''
           mkdir -p $out/bin
           makeWrapper ${pkgs.ppp}/bin/pppd $out/bin/pppd \
diff --git a/nixpkgs/nixos/modules/services/networking/prayer.nix b/nixpkgs/nixos/modules/services/networking/prayer.nix
index 01e961997a77..197aa8a6f448 100644
--- a/nixpkgs/nixos/modules/services/networking/prayer.nix
+++ b/nixpkgs/nixos/modules/services/networking/prayer.nix
@@ -41,7 +41,7 @@ in
 
     services.prayer = {
 
-      enable = mkEnableOption "the prayer webmail http server";
+      enable = mkEnableOption (lib.mdDoc "the prayer webmail http server");
 
       port = mkOption {
         default = 2080;
diff --git a/nixpkgs/nixos/modules/services/networking/privoxy.nix b/nixpkgs/nixos/modules/services/networking/privoxy.nix
index 1ad5b155feb2..78d02aaa1125 100644
--- a/nixpkgs/nixos/modules/services/networking/privoxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/privoxy.nix
@@ -53,7 +53,7 @@ in
 
   options.services.privoxy = {
 
-    enable = mkEnableOption "Privoxy, non-caching filtering proxy";
+    enable = mkEnableOption (lib.mdDoc "Privoxy, non-caching filtering proxy");
 
     enableTor = mkOption {
       type = types.bool;
@@ -67,21 +67,21 @@ in
     inspectHttps = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to configure Privoxy to inspect HTTPS requests, meaning all
         encrypted traffic will be filtered as well. This works by decrypting
         and re-encrypting the requests using a per-domain generated certificate.
 
         To issue per-domain certificates, Privoxy must be provided with a CA
-        certificate, using the <literal>ca-cert-file</literal>,
-        <literal>ca-key-file</literal> settings.
-
-        <warning><para>
-          The CA certificate must also be added to the system trust roots,
-          otherwise browsers will reject all Privoxy certificates as invalid.
-          You can do so by using the option
-          <option>security.pki.certificateFiles</option>.
-        </para></warning>
+        certificate, using the `ca-cert-file`,
+        `ca-key-file` settings.
+
+        ::: {.warning}
+        The CA certificate must also be added to the system trust roots,
+        otherwise browsers will reject all Privoxy certificates as invalid.
+        You can do so by using the option
+        {option}`security.pki.certificateFiles`.
+        :::
       '';
     };
 
@@ -89,8 +89,8 @@ in
       type = ageType;
       default = "10d";
       example = "12h";
-      description = ''
-        If <literal>inspectHttps</literal> is enabled, the time generated HTTPS
+      description = lib.mdDoc ''
+        If `inspectHttps` is enabled, the time generated HTTPS
         certificates will be stored in a temporary directory for reuse. Once
         the lifetime has expired the directory will cleared and the certificate
         will have to be generated again, on-demand.
@@ -98,8 +98,10 @@ in
         Depending on the traffic, you may want to reduce the lifetime to limit
         the disk usage, since Privoxy itself never deletes the certificates.
 
-        <note><para>The format is that of the <literal>tmpfiles.d(5)</literal>
-        Age parameter.</para></note>
+        ::: {.note}
+        The format is that of the `tmpfiles.d(5)`
+        Age parameter.
+        :::
       '';
     };
 
@@ -179,15 +181,15 @@ in
           # debug 64
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option is mapped to the main Privoxy configuration file.
         Check out the Privoxy user manual at
-        <link xlink:href="https://www.privoxy.org/user-manual/config.html"/>
+        <https://www.privoxy.org/user-manual/config.html>
         for available settings and documentation.
 
-        <note><para>
-          Repeated settings can be represented by using a list.
-        </para></note>
+        ::: {.note}
+        Repeated settings can be represented by using a list.
+        :::
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.md b/nixpkgs/nixos/modules/services/networking/prosody.md
new file mode 100644
index 000000000000..2da2c242a98b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/prosody.md
@@ -0,0 +1,72 @@
+# Prosody {#module-services-prosody}
+
+[Prosody](https://prosody.im/) is an open-source, modern XMPP server.
+
+## Basic usage {#module-services-prosody-basic-usage}
+
+A common struggle for most XMPP newcomers is to find the right set
+of XMPP Extensions (XEPs) to setup. Forget to activate a few of
+those and your XMPP experience might turn into a nightmare!
+
+The XMPP community tackles this problem by creating a meta-XEP
+listing a decent set of XEPs you should implement. This meta-XEP
+is issued every year, the 2020 edition being
+[XEP-0423](https://xmpp.org/extensions/xep-0423.html).
+
+The NixOS Prosody module will implement most of these recommendend XEPs out of
+the box. That being said, two components still require some
+manual configuration: the
+[Multi User Chat (MUC)](https://xmpp.org/extensions/xep-0045.html)
+and the [HTTP File Upload](https://xmpp.org/extensions/xep-0363.html) ones.
+You'll need to create a DNS subdomain for each of those. The current convention is to name your
+MUC endpoint `conference.example.org` and your HTTP upload domain `upload.example.org`.
+
+A good configuration to start with, including a
+[Multi User Chat (MUC)](https://xmpp.org/extensions/xep-0045.html)
+endpoint as well as a [HTTP File Upload](https://xmpp.org/extensions/xep-0363.html)
+endpoint will look like this:
+```
+services.prosody = {
+  enable = true;
+  admins = [ "root@example.org" ];
+  ssl.cert = "/var/lib/acme/example.org/fullchain.pem";
+  ssl.key = "/var/lib/acme/example.org/key.pem";
+  virtualHosts."example.org" = {
+      enabled = true;
+      domain = "example.org";
+      ssl.cert = "/var/lib/acme/example.org/fullchain.pem";
+      ssl.key = "/var/lib/acme/example.org/key.pem";
+  };
+  muc = [ {
+      domain = "conference.example.org";
+  } ];
+  uploadHttp = {
+      domain = "upload.example.org";
+  };
+};
+```
+
+## Let's Encrypt Configuration {#module-services-prosody-letsencrypt}
+
+As you can see in the code snippet from the
+[previous section](#module-services-prosody-basic-usage),
+you'll need a single TLS certificate covering your main endpoint,
+the MUC one as well as the HTTP Upload one. We can generate such a
+certificate by leveraging the ACME
+[extraDomainNames](#opt-security.acme.certs._name_.extraDomainNames) module option.
+
+Provided the setup detailed in the previous section, you'll need the following acme configuration to generate
+a TLS certificate for the three endponits:
+```
+security.acme = {
+  email = "root@example.org";
+  acceptTerms = true;
+  certs = {
+    "example.org" = {
+      webroot = "/var/www/example.org";
+      email = "root@example.org";
+      extraDomainNames = [ "conference.example.org" "upload.example.org" ];
+    };
+  };
+};
+```
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.nix b/nixpkgs/nixos/modules/services/networking/prosody.nix
index f32c7adbd2c3..9f68853f9fa8 100644
--- a/nixpkgs/nixos/modules/services/networking/prosody.nix
+++ b/nixpkgs/nixos/modules/services/networking/prosody.nix
@@ -263,7 +263,7 @@ let
     if builtins.isString x then ''"${x}"''
     else if builtins.isBool x then boolToString x
     else if builtins.isInt x then toString x
-    else if builtins.isList x then ''{ ${lib.concatStringsSep ", " (map (n: toLua n) x) } }''
+    else if builtins.isList x then "{ ${lib.concatMapStringsSep ", " toLua x} }"
     else throw "Invalid Lua value";
 
   createSSLOptsStr = o: ''
@@ -309,7 +309,7 @@ let
         type = types.int;
         default = 300;
         description = lib.mdDoc ''
-          Timout after which the room is destroyed or unlocked if not
+          Timeout after which the room is destroyed or unlocked if not
           configured, in seconds
        '';
       };
@@ -489,7 +489,7 @@ in
 
           Setting this option to true will prevent you from building a
           NixOS configuration which won't comply with this standard.
-          You can explicitely decide to ignore this standard if you
+          You can explicitly decide to ignore this standard if you
           know what you are doing by setting this option to false.
 
           [1] https://xmpp.org/extensions/xep-0423.html
@@ -529,28 +529,28 @@ in
       user = mkOption {
         type = types.str;
         default = "prosody";
-        description = ''
+        description = lib.mdDoc ''
           User account under which prosody runs.
 
-          <note><para>
+          ::: {.note}
           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 prosody service starts.
-          </para></note>
+          :::
         '';
       };
 
       group = mkOption {
         type = types.str;
         default = "prosody";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which prosody runs.
 
-          <note><para>
+          ::: {.note}
           If left as the default value this group will automatically be created
           on system activation, otherwise you are responsible for
           ensuring the group exists before the prosody service starts.
-          </para></note>
+          :::
         '';
       };
 
@@ -649,7 +649,7 @@ in
       extraPluginPaths = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = lib.mdDoc "Addtional path in which to look find plugins/modules";
+        description = lib.mdDoc "Additional path in which to look find plugins/modules";
       };
 
       uploadHttp = mkOption {
@@ -733,7 +733,7 @@ in
 
           Having a server not XEP-0423-compliant might make your XMPP
           experience terrible. See the NixOS manual for further
-          informations.
+          information.
 
           If you know what you're doing, you can disable this warning by
           setting config.services.prosody.xmppComplianceSuite to false.
@@ -904,5 +904,6 @@ in
     };
 
   };
-  meta.doc = ./prosody.xml;
+
+  meta.doc = ./prosody.md;
 }
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.xml b/nixpkgs/nixos/modules/services/networking/prosody.xml
deleted file mode 100644
index 6358d744ff78..000000000000
--- a/nixpkgs/nixos/modules/services/networking/prosody.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-prosody">
- <title>Prosody</title>
- <para>
-  <link xlink:href="https://prosody.im/">Prosody</link> is an open-source, modern XMPP server.
- </para>
- <section xml:id="module-services-prosody-basic-usage">
-  <title>Basic usage</title>
-
-  <para>
-    A common struggle for most XMPP newcomers is to find the right set
-    of XMPP Extensions (XEPs) to setup. Forget to activate a few of
-    those and your XMPP experience might turn into a nightmare!
-  </para>
-
-  <para>
-    The XMPP community tackles this problem by creating a meta-XEP
-    listing a decent set of XEPs you should implement. This meta-XEP
-    is issued every year, the 2020 edition being
-    <link xlink:href="https://xmpp.org/extensions/xep-0423.html">XEP-0423</link>.
-  </para>
-  <para>
-    The NixOS Prosody module will implement most of these recommendend XEPs out of
-    the box. That being said, two components still require some
-    manual configuration: the
-    <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
-    and the <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link> ones.
-    You'll need to create a DNS subdomain for each of those. The current convention is to name your
-    MUC endpoint <literal>conference.example.org</literal> and your HTTP upload domain <literal>upload.example.org</literal>.
-  </para>
-  <para>
-    A good configuration to start with, including a
-    <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
-    endpoint as well as a <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link>
-    endpoint will look like this:
-    <programlisting>
-services.prosody = {
-  <link linkend="opt-services.prosody.enable">enable</link> = true;
-  <link linkend="opt-services.prosody.admins">admins</link> = [ "root@example.org" ];
-  <link linkend="opt-services.prosody.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
-  <link linkend="opt-services.prosody.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
-  <link linkend="opt-services.prosody.virtualHosts">virtualHosts</link>."example.org" = {
-      <link linkend="opt-services.prosody.virtualHosts._name_.enabled">enabled</link> = true;
-      <link linkend="opt-services.prosody.virtualHosts._name_.domain">domain</link> = "example.org";
-      <link linkend="opt-services.prosody.virtualHosts._name_.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
-      <link linkend="opt-services.prosody.virtualHosts._name_.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
-  };
-  <link linkend="opt-services.prosody.muc">muc</link> = [ {
-      <link linkend="opt-services.prosody.muc">domain</link> = "conference.example.org";
-  } ];
-  <link linkend="opt-services.prosody.uploadHttp">uploadHttp</link> = {
-      <link linkend="opt-services.prosody.uploadHttp.domain">domain</link> = "upload.example.org";
-  };
-};</programlisting>
-  </para>
- </section>
- <section xml:id="module-services-prosody-letsencrypt">
-  <title>Let's Encrypt Configuration</title>
- <para>
-   As you can see in the code snippet from the
-   <link linkend="module-services-prosody-basic-usage">previous section</link>,
-   you'll need a single TLS certificate covering your main endpoint,
-   the MUC one as well as the HTTP Upload one. We can generate such a
-   certificate by leveraging the ACME
-   <link linkend="opt-security.acme.certs._name_.extraDomainNames">extraDomainNames</link> module option.
- </para>
- <para>
-   Provided the setup detailed in the previous section, you'll need the following acme configuration to generate
-   a TLS certificate for the three endponits:
-    <programlisting>
-security.acme = {
-  <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" = {
-      <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/example.org";
-      <link linkend="opt-security.acme.certs._name_.email">email</link> = "root@example.org";
-      <link linkend="opt-security.acme.certs._name_.extraDomainNames">extraDomainNames</link> = [ "conference.example.org" "upload.example.org" ];
-    };
-  };
-};</programlisting>
- </para>
-</section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/networking/quassel.nix b/nixpkgs/nixos/modules/services/networking/quassel.nix
index a4b203ea0018..a074023b5ee4 100644
--- a/nixpkgs/nixos/modules/services/networking/quassel.nix
+++ b/nixpkgs/nixos/modules/services/networking/quassel.nix
@@ -17,7 +17,7 @@ in
 
     services.quassel = {
 
-      enable = mkEnableOption "the Quassel IRC client daemon";
+      enable = mkEnableOption (lib.mdDoc "the Quassel IRC client daemon");
 
       certificateFile = mkOption {
         type = types.nullOr types.str;
@@ -47,9 +47,9 @@ in
       interfaces = mkOption {
         type = types.listOf types.str;
         default = [ "127.0.0.1" ];
-        description = ''
-          The interfaces the Quassel daemon will be listening to.  If `[ 127.0.0.1 ]',
-          only clients on the local host can connect to it; if `[ 0.0.0.0 ]', clients
+        description = lib.mdDoc ''
+          The interfaces the Quassel daemon will be listening to.  If `[ 127.0.0.1 ]`,
+          only clients on the local host can connect to it; if `[ 0.0.0.0 ]`, clients
           can access it from any network interface.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/networking/quicktun.nix b/nixpkgs/nixos/modules/services/networking/quicktun.nix
index e2282b9aaf7c..7aed972adc88 100644
--- a/nixpkgs/nixos/modules/services/networking/quicktun.nix
+++ b/nixpkgs/nixos/modules/services/networking/quicktun.nix
@@ -20,65 +20,65 @@ with lib;
             type = types.int;
             default = 0;
             example = 1;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           remoteAddress = mkOption {
             type = types.str;
             example = "tunnel.example.com";
-            description = "";
+            description = lib.mdDoc "";
           };
 
           localAddress = mkOption {
             type = types.str;
             example = "0.0.0.0";
-            description = "";
+            description = lib.mdDoc "";
           };
 
           localPort = mkOption {
             type = types.int;
             default = 2998;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           remotePort = mkOption {
             type = types.int;
             default = 2998;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           remoteFloat = mkOption {
             type = types.int;
             default = 0;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           protocol = mkOption {
             type = types.str;
             default = "nacltai";
-            description = "";
+            description = lib.mdDoc "";
           };
 
           privateKey = mkOption {
             type = types.str;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           publicKey = mkOption {
             type = types.str;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           timeWindow = mkOption {
             type = types.int;
             default = 5;
-            description = "";
+            description = lib.mdDoc "";
           };
 
           upScript = mkOption {
             type = types.lines;
             default = "";
-            description = "";
+            description = lib.mdDoc "";
           };
         };
       });
diff --git a/nixpkgs/nixos/modules/services/networking/quorum.nix b/nixpkgs/nixos/modules/services/networking/quorum.nix
index 67027ae3f857..4b90b12f86fc 100644
--- a/nixpkgs/nixos/modules/services/networking/quorum.nix
+++ b/nixpkgs/nixos/modules/services/networking/quorum.nix
@@ -13,7 +13,7 @@ in {
   options = {
 
     services.quorum = {
-      enable = mkEnableOption "Quorum blockchain daemon";
+      enable = mkEnableOption (lib.mdDoc "Quorum blockchain daemon");
 
       user = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/r53-ddns.nix b/nixpkgs/nixos/modules/services/networking/r53-ddns.nix
index 77738c755313..277b65dcecd4 100644
--- a/nixpkgs/nixos/modules/services/networking/r53-ddns.nix
+++ b/nixpkgs/nixos/modules/services/networking/r53-ddns.nix
@@ -10,7 +10,7 @@ in
   options = {
     services.r53-ddns = {
 
-      enable = mkEnableOption "r53-ddyns";
+      enable = mkEnableOption (lib.mdDoc "r53-ddyns");
 
       interval = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/radicale.nix b/nixpkgs/nixos/modules/services/networking/radicale.nix
index 687cf206e145..00dbd6bbe386 100644
--- a/nixpkgs/nixos/modules/services/networking/radicale.nix
+++ b/nixpkgs/nixos/modules/services/networking/radicale.nix
@@ -9,7 +9,7 @@ let
     listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { });
   };
 
-  pkg = if isNull cfg.package then
+  pkg = if cfg.package == null then
     pkgs.radicale
   else
     cfg.package;
@@ -25,7 +25,7 @@ let
 
 in {
   options.services.radicale = {
-    enable = mkEnableOption "Radicale CalDAV and CardDAV server";
+    enable = mkEnableOption (lib.mdDoc "Radicale CalDAV and CardDAV server");
 
     package = mkOption {
       description = lib.mdDoc "Radicale package to use.";
@@ -77,7 +77,7 @@ in {
         <https://radicale.org/3.0.html#documentation/authentication-and-rights>.
         This option only works in conjunction with {option}`settings`.
         Setting this will also set {option}`settings.rights.type` and
-        {option}`settings.rights.file` to approriate values.
+        {option}`settings.rights.file` to appropriate values.
       '';
       default = { };
       example = literalExpression ''
@@ -117,13 +117,13 @@ in {
       }
     ];
 
-    warnings = optional (isNull cfg.package && versionOlder config.system.stateVersion "17.09") ''
+    warnings = optional (cfg.package == null && versionOlder config.system.stateVersion "17.09") ''
       The configuration and storage formats of your existing Radicale
       installation might be incompatible with the newest version.
       For upgrade instructions see
       https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx.
       Set services.radicale.package to suppress this warning.
-    '' ++ optional (isNull cfg.package && versionOlder config.system.stateVersion "20.09") ''
+    '' ++ optional (cfg.package == null && versionOlder config.system.stateVersion "20.09") ''
       The configuration format of your existing Radicale installation might be
       incompatible with the newest version.  For upgrade instructions see
       https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist.
@@ -200,5 +200,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ aneeshusa infinisil dotlambda ];
+  meta.maintainers = with lib.maintainers; [ infinisil dotlambda ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/redsocks.nix b/nixpkgs/nixos/modules/services/networking/redsocks.nix
index 5aa9f003bad5..30d6a0a6336d 100644
--- a/nixpkgs/nixos/modules/services/networking/redsocks.nix
+++ b/nixpkgs/nixos/modules/services/networking/redsocks.nix
@@ -30,14 +30,14 @@ in
         type = types.str;
         default = "stderr";
         description =
-          ''
+          lib.mdDoc ''
             Where to send logs.
 
             Possible values are:
               - stderr
               - file:/path/to/file
               - syslog:FACILITY where FACILITY is any of "daemon", "local0",
-              etc.
+                etc.
           '';
       };
 
@@ -81,7 +81,7 @@ in
           };
 
           port = mkOption {
-            type = types.int;
+            type = types.port;
             default = 12345;
             description = lib.mdDoc "Port on which redsocks should listen.";
           };
@@ -122,9 +122,10 @@ in
                                 "Forwarded_ipport" ];
             default = "false";
             description =
-              ''
+              lib.mdDoc ''
                 Way to disclose client IP to the proxy.
                   - "false": do not disclose
+
                 http-connect supports the following ways:
                   - "X-Forwarded-For": add header "X-Forwarded-For: IP"
                   - "Forwarded_ip": add header "Forwarded: for=IP" (see RFC7239)
diff --git a/nixpkgs/nixos/modules/services/networking/resilio.nix b/nixpkgs/nixos/modules/services/networking/resilio.nix
index 05798a2c83ee..7f6358d00d0b 100644
--- a/nixpkgs/nixos/modules/services/networking/resilio.nix
+++ b/nixpkgs/nixos/modules/services/networking/resilio.nix
@@ -8,7 +8,6 @@ let
   resilioSync = pkgs.resilio-sync;
 
   sharedFoldersRecord = map (entry: {
-    secret = entry.secret;
     dir = entry.directory;
 
     use_relay_server = entry.useRelayServer;
@@ -40,6 +39,36 @@ let
     shared_folders = sharedFoldersRecord;
   }));
 
+  sharedFoldersSecretFiles = map (entry: {
+    dir = entry.directory;
+    secretFile = if builtins.hasAttr "secret" entry then
+      toString (pkgs.writeTextFile {
+        name = "secret-file";
+        text = entry.secret;
+      })
+    else
+      entry.secretFile;
+  }) cfg.sharedFolders;
+
+  runConfigPath = "/run/rslsync/config.json";
+
+  createConfig = pkgs.writeShellScriptBin "create-resilio-config" (
+    if cfg.sharedFolders != [ ] then ''
+      ${pkgs.jq}/bin/jq \
+        '.shared_folders |= map(.secret = $ARGS.named[.dir])' \
+        ${
+          lib.concatMapStringsSep " \\\n  "
+          (entry: ''--arg '${entry.dir}' "$(cat '${entry.secretFile}')"'')
+          sharedFoldersSecretFiles
+        } \
+        <${configFile} \
+        >${runConfigPath}
+    '' else ''
+      # no secrets, passing through config
+      cp ${configFile} ${runConfigPath};
+    ''
+  );
+
 in
 {
   options = {
@@ -186,7 +215,7 @@ in
         default = [];
         type = types.listOf (types.attrsOf types.anything);
         example =
-          [ { secret         = "AHMYFPCQAHBM7LQPFXQ7WV6Y42IGUXJ5Y";
+          [ { secretFile     = "/run/resilio-secret";
               directory      = "/home/user/sync_test";
               useRelayServer = true;
               useTracker     = true;
@@ -199,25 +228,22 @@ in
               ];
             }
           ];
-        description = ''
+        description = lib.mdDoc ''
           Shared folder list. If enabled, web UI must be
-          disabled. Secrets can be generated using <literal>rslsync
-          --generate-secret</literal>. Note that this secret will be
-          put inside the Nix store, so it is realistically not very
-          secret.
+          disabled. Secrets can be generated using `rslsync --generate-secret`.
 
           If you would like to be able to modify the contents of this
           directories, it is recommended that you make your user a
-          member of the <literal>rslsync</literal> group.
+          member of the `rslsync` group.
 
           Directories in this list should be in the
-          <literal>rslsync</literal> group, and that group must have
+          `rslsync` group, and that group must have
           write access to the directory. It is also recommended that
-          <literal>chmod g+s</literal> is applied to the directory
+          `chmod g+s` is applied to the directory
           so that any sub directories created will also belong to
-          the <literal>rslsync</literal> group. Also,
-          <literal>setfacl -d -m group:rslsync:rwx</literal> and
-          <literal>setfacl -m group:rslsync:rwx</literal> should also
+          the `rslsync` group. Also,
+          `setfacl -d -m group:rslsync:rwx` and
+          `setfacl -m group:rslsync:rwx` should also
           be applied so that the sub directories are writable by
           the group.
         '';
@@ -256,10 +282,14 @@ in
         Restart   = "on-abort";
         UMask     = "0002";
         User      = "rslsync";
+        RuntimeDirectory = "rslsync";
+        ExecStartPre = "${createConfig}/bin/create-resilio-config";
         ExecStart = ''
-          ${resilioSync}/bin/rslsync --nodaemon --config ${configFile}
+          ${resilioSync}/bin/rslsync --nodaemon --config ${runConfigPath}
         '';
       };
     };
   };
+
+  meta.maintainers = with maintainers; [ jwoudenberg ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/robustirc-bridge.nix b/nixpkgs/nixos/modules/services/networking/robustirc-bridge.nix
index c5afbaf8ea18..9b93828c396c 100644
--- a/nixpkgs/nixos/modules/services/networking/robustirc-bridge.nix
+++ b/nixpkgs/nixos/modules/services/networking/robustirc-bridge.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.robustirc-bridge = {
-      enable = mkEnableOption "RobustIRC bridge";
+      enable = mkEnableOption (lib.mdDoc "RobustIRC bridge");
 
       extraFlags = mkOption {
         type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/routedns.nix b/nixpkgs/nixos/modules/services/networking/routedns.nix
index 6f3d769e8644..2a29a06700ce 100644
--- a/nixpkgs/nixos/modules/services/networking/routedns.nix
+++ b/nixpkgs/nixos/modules/services/networking/routedns.nix
@@ -12,7 +12,7 @@ let
 in
 {
   options.services.routedns = {
-    enable = mkEnableOption "RouteDNS - DNS stub resolver, proxy and router";
+    enable = mkEnableOption (lib.mdDoc "RouteDNS - DNS stub resolver, proxy and router");
 
     settings = mkOption {
       type = settingsFormat.type;
diff --git a/nixpkgs/nixos/modules/services/networking/rpcbind.nix b/nixpkgs/nixos/modules/services/networking/rpcbind.nix
index 0a5df6987092..63c4859fbd07 100644
--- a/nixpkgs/nixos/modules/services/networking/rpcbind.nix
+++ b/nixpkgs/nixos/modules/services/networking/rpcbind.nix
@@ -13,8 +13,8 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable `rpcbind', an ONC RPC directory service
+        description = lib.mdDoc ''
+          Whether to enable `rpcbind`, an ONC RPC directory service
           notably used by NFS and NIS, and which can be queried
           using the rpcinfo(1) command. `rpcbind` is a replacement for
           `portmap`.
@@ -35,6 +35,16 @@ with lib;
 
     systemd.services.rpcbind = {
       wantedBy = [ "multi-user.target" ];
+      # rpcbind performs a check for /var/run/rpcbind.lock at startup
+      # and will crash if /var/run isn't present. In the stock NixOS
+      # var.conf tmpfiles configuration file, /var/run is symlinked to
+      # /run, so rpcbind can enter a race condition in which /var/run
+      # isn't symlinked yet but tries to interact with the path, so
+      # controlling the order explicitly here ensures that rpcbind can
+      # start successfully. The `wants` instead of `requires` should
+      # avoid creating a strict/brittle dependency.
+      wants = [ "systemd-tmpfiles-setup.service" ];
+      after = [ "systemd-tmpfiles-setup.service" ];
     };
 
     users.users.rpc = {
diff --git a/nixpkgs/nixos/modules/services/networking/rxe.nix b/nixpkgs/nixos/modules/services/networking/rxe.nix
index 868e2c81ccbd..7dbb4823b4bc 100644
--- a/nixpkgs/nixos/modules/services/networking/rxe.nix
+++ b/nixpkgs/nixos/modules/services/networking/rxe.nix
@@ -10,14 +10,14 @@ in {
 
   options = {
     networking.rxe = {
-      enable = mkEnableOption "RDMA over converged ethernet";
+      enable = mkEnableOption (lib.mdDoc "RDMA over converged ethernet");
       interfaces = mkOption {
         type = types.listOf types.str;
         default = [ ];
         example = [ "eth0" ];
-        description = ''
+        description = lib.mdDoc ''
           Enable RDMA on the listed interfaces. The corresponding virtual
-          RDMA interfaces will be named rxe_&lt;interface&gt;.
+          RDMA interfaces will be named rxe_\<interface\>.
           UDP port 4791 must be open on the respective ethernet interfaces.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/networking/sabnzbd.nix b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
index 18e1d9f48b25..8f3545df8995 100644
--- a/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
@@ -15,12 +15,12 @@ in
 
   options = {
     services.sabnzbd = {
-      enable = mkEnableOption "the sabnzbd server";
+      enable = mkEnableOption (lib.mdDoc "the sabnzbd server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.sabnzbd;
-        defaultText = "pkgs.sabnzbd";
+        defaultText = lib.literalExpression "pkgs.sabnzbd";
         description = lib.mdDoc "The sabnzbd executable package run by the service.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/seafile.nix b/nixpkgs/nixos/modules/services/networking/seafile.nix
index d9617952ea59..b07d51b9b49a 100644
--- a/nixpkgs/nixos/modules/services/networking/seafile.nix
+++ b/nixpkgs/nixos/modules/services/networking/seafile.nix
@@ -37,7 +37,7 @@ in {
   ###### Interface
 
   options.services.seafile = {
-    enable = mkEnableOption "Seafile server";
+    enable = mkEnableOption (lib.mdDoc "Seafile server");
 
     ccnetSettings = mkOption {
       type = types.submodule {
@@ -131,9 +131,9 @@ in {
     seahubExtraConf = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Extra config to append to `seahub_settings.py` file.
-        Refer to <link xlink:href="https://manual.seafile.com/config/seahub_settings_py/"/>
+        Refer to <https://manual.seafile.com/config/seahub_settings_py/>
         for all available options.
       '';
     };
diff --git a/nixpkgs/nixos/modules/services/networking/searx.nix b/nixpkgs/nixos/modules/services/networking/searx.nix
index 238479864f88..6c57ddbde2d4 100644
--- a/nixpkgs/nixos/modules/services/networking/searx.nix
+++ b/nixpkgs/nixos/modules/services/networking/searx.nix
@@ -81,35 +81,33 @@ in
               };
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Searx settings. These will be merged with (taking precedence over)
           the default configuration. It's also possible to refer to
           environment variables
-          (defined in <xref linkend="opt-services.searx.environmentFile"/>)
-          using the syntax <literal>@VARIABLE_NAME@</literal>.
-          <note>
-            <para>
-              For available settings, see the Searx
-              <link xlink:href="https://searx.github.io/searx/admin/settings.html">docs</link>.
-            </para>
-          </note>
+          (defined in [](#opt-services.searx.environmentFile))
+          using the syntax `@VARIABLE_NAME@`.
+
+          ::: {.note}
+          For available settings, see the Searx
+          [docs](https://searx.github.io/searx/admin/settings.html).
+          :::
         '';
       };
 
       settingsFile = mkOption {
         type = types.path;
         default = "${runDir}/settings.yml";
-        description = ''
+        description = lib.mdDoc ''
           The path of the Searx server settings.yml file. If no file is
           specified, a default file is used (default config file has debug mode
           enabled). Note: setting this options overrides
-          <xref linkend="opt-services.searx.settings"/>.
-          <warning>
-            <para>
-              This file, along with any secret key it contains, will be copied
-              into the world-readable Nix store.
-            </para>
-          </warning>
+          [](#opt-services.searx.settings).
+
+          ::: {.warning}
+          This file, along with any secret key it contains, will be copied
+          into the world-readable Nix store.
+          :::
         '';
       };
 
@@ -123,15 +121,14 @@ in
       runInUwsgi = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run searx in uWSGI as a "vassal", instead of using its
           built-in HTTP server. This is the recommended mode for public or
-          large instances, but is unecessary for LAN or local-only use.
-          <warning>
-            <para>
-              The built-in HTTP server logs all queries by default.
-            </para>
-          </warning>
+          large instances, but is unnecessary for LAN or local-only use.
+
+          ::: {.warning}
+          The built-in HTTP server logs all queries by default.
+          :::
         '';
       };
 
@@ -195,7 +192,10 @@ in
         ExecStart = "${cfg.package}/bin/searx-run";
       } // optionalAttrs (cfg.environmentFile != null)
         { EnvironmentFile = builtins.toPath cfg.environmentFile; };
-      environment.SEARX_SETTINGS_PATH = cfg.settingsFile;
+      environment = {
+        SEARX_SETTINGS_PATH = cfg.settingsFile;
+        SEARXNG_SETTINGS_PATH = cfg.settingsFile;
+      };
     };
 
     systemd.services.uwsgi = mkIf (cfg.runInUwsgi)
@@ -223,7 +223,7 @@ in
         module = "searx.webapp";
         env = [
           "SEARX_SETTINGS_PATH=${cfg.settingsFile}"
-          # searxng compatiblity https://github.com/searxng/searxng/issues/1519
+          # searxng compatibility https://github.com/searxng/searxng/issues/1519
           "SEARXNG_SETTINGS_PATH=${cfg.settingsFile}"
         ];
         buffer-size = 32768;
diff --git a/nixpkgs/nixos/modules/services/networking/shadowsocks.nix b/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
index 8eee40711a6d..2034dca6f26b 100644
--- a/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
+++ b/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
@@ -48,7 +48,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8388;
         description = lib.mdDoc ''
           Port which the server uses.
diff --git a/nixpkgs/nixos/modules/services/networking/shellhub-agent.nix b/nixpkgs/nixos/modules/services/networking/shellhub-agent.nix
index c13f183d4fe0..7cce23cb9c4e 100644
--- a/nixpkgs/nixos/modules/services/networking/shellhub-agent.nix
+++ b/nixpkgs/nixos/modules/services/networking/shellhub-agent.nix
@@ -12,9 +12,9 @@ in
 
     services.shellhub-agent = {
 
-      enable = mkEnableOption "ShellHub Agent daemon";
+      enable = mkEnableOption (lib.mdDoc "ShellHub Agent daemon");
 
-      package = mkPackageOption pkgs "shellhub-agent" { };
+      package = mkPackageOptionMD pkgs "shellhub-agent" { };
 
       preferredHostname = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/shorewall.nix b/nixpkgs/nixos/modules/services/networking/shorewall.nix
index 795295d1628f..ba59d71120da 100644
--- a/nixpkgs/nixos/modules/services/networking/shorewall.nix
+++ b/nixpkgs/nixos/modules/services/networking/shorewall.nix
@@ -8,15 +8,14 @@ in {
       enable = lib.mkOption {
         type        = types.bool;
         default     = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Shorewall IPv4 Firewall.
-          <warning>
-            <para>
-            Enabling this service WILL disable the existing NixOS
-            firewall! Default firewall rules provided by packages are not
-            considered at the moment.
-            </para>
-          </warning>
+
+          ::: {.warning}
+          Enabling this service WILL disable the existing NixOS
+          firewall! Default firewall rules provided by packages are not
+          considered at the moment.
+          :::
         '';
       };
       package = lib.mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/shorewall6.nix b/nixpkgs/nixos/modules/services/networking/shorewall6.nix
index 1d6d84eb89b3..e54be290bfb3 100644
--- a/nixpkgs/nixos/modules/services/networking/shorewall6.nix
+++ b/nixpkgs/nixos/modules/services/networking/shorewall6.nix
@@ -8,15 +8,14 @@ in {
       enable = lib.mkOption {
         type        = types.bool;
         default     = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Shorewall IPv6 Firewall.
-          <warning>
-            <para>
-            Enabling this service WILL disable the existing NixOS
-            firewall! Default firewall rules provided by packages are not
-            considered at the moment.
-            </para>
-          </warning>
+
+          ::: {.warning}
+          Enabling this service WILL disable the existing NixOS
+          firewall! Default firewall rules provided by packages are not
+          considered at the moment.
+          :::
         '';
       };
       package = lib.mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/shout.nix b/nixpkgs/nixos/modules/services/networking/shout.nix
index 1ef21ad5bf8f..0b1687d44d9e 100644
--- a/nixpkgs/nixos/modules/services/networking/shout.nix
+++ b/nixpkgs/nixos/modules/services/networking/shout.nix
@@ -23,7 +23,7 @@ let
 
 in {
   options.services.shout = {
-    enable = mkEnableOption "Shout web IRC client";
+    enable = mkEnableOption (lib.mdDoc "Shout web IRC client");
 
     private = mkOption {
       type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/networking/sitespeed-io.nix b/nixpkgs/nixos/modules/services/networking/sitespeed-io.nix
new file mode 100644
index 000000000000..f7eab0bb19d7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sitespeed-io.nix
@@ -0,0 +1,122 @@
+{ lib, config, pkgs, ... }:
+let
+  cfg = config.services.sitespeed-io;
+  format = pkgs.formats.json { };
+in
+{
+  options.services.sitespeed-io = {
+    enable = lib.mkEnableOption (lib.mdDoc "Sitespeed.io");
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "sitespeed-io";
+      description = lib.mdDoc "User account under which sitespeed-io runs.";
+    };
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.sitespeed-io;
+      defaultText = "pkgs.sitespeed-io";
+      description = lib.mdDoc "Sitespeed.io package to use.";
+    };
+
+    dataDir = lib.mkOption {
+      default = "/var/lib/sitespeed-io";
+      type = lib.types.str;
+      description = lib.mdDoc "The base sitespeed-io data directory.";
+    };
+
+    period = lib.mkOption {
+      type = lib.types.str;
+      default = "hourly";
+      description = lib.mdDoc ''
+        Systemd calendar expression when to run. See {manpage}`systemd.time(7)`.
+      '';
+    };
+
+    runs = lib.mkOption {
+      default = [ ];
+      description = lib.mdDoc ''
+        A list of run configurations. The service will call sitespeed-io once
+        for every run listed here. This lets you examine different websites
+        with different sitespeed-io settings.
+      '';
+      type = lib.types.listOf (lib.types.submodule {
+        options = {
+          urls = lib.mkOption {
+            type = with lib.types; listOf str;
+            default = [];
+            description = lib.mdDoc ''
+              URLs the service should monitor.
+            '';
+          };
+
+          settings = lib.mkOption {
+            type = lib.types.submodule {
+              freeformType = format.type;
+              options = { };
+            };
+            default = { };
+            description = lib.mdDoc ''
+              Configuration for sitespeed-io, see
+              <https://www.sitespeed.io/documentation/sitespeed.io/configuration/>
+              for available options. The value here will be directly transformed to
+              JSON and passed as `--config` to the program.
+            '';
+          };
+
+          extraArgs = lib.mkOption {
+            type = with lib.types; listOf str;
+            default = [];
+            description = lib.mdDoc ''
+              Extra command line arguments to pass to the program.
+            '';
+          };
+        };
+      });
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+    {
+      assertion = cfg.runs != [];
+      message = "At least one run must be configured.";
+    }
+    {
+      assertion = lib.all (run: run.urls != []) cfg.runs;
+      message = "All runs must have at least one url configured.";
+    }
+  ];
+
+    systemd.services.sitespeed-io = {
+      description = "Check website status";
+      startAt = cfg.period;
+      serviceConfig = {
+        WorkingDirectory = cfg.dataDir;
+        User = cfg.user;
+      };
+      preStart = "chmod u+w -R ${cfg.dataDir}"; # Make sure things are writable
+      script = (lib.concatMapStrings (run: ''
+        ${lib.getExe cfg.package} \
+          --config ${format.generate "sitespeed.json" run.settings} \
+          ${lib.escapeShellArgs run.extraArgs} \
+          ${builtins.toFile "urls.txt" (lib.concatLines run.urls)} &
+      '') cfg.runs) +
+      ''
+        wait
+      '';
+    };
+
+    users = {
+      extraUsers.${cfg.user} = {
+        isSystemUser = true;
+        group = cfg.user;
+        home = cfg.dataDir;
+        createHome = true;
+        homeMode = "755";
+      };
+      extraGroups.${cfg.user} = { };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/skydns.nix b/nixpkgs/nixos/modules/services/networking/skydns.nix
index f73a8718841a..84cf6b0deac1 100644
--- a/nixpkgs/nixos/modules/services/networking/skydns.nix
+++ b/nixpkgs/nixos/modules/services/networking/skydns.nix
@@ -7,7 +7,7 @@ let
 
 in {
   options.services.skydns = {
-    enable = mkEnableOption "skydns service";
+    enable = mkEnableOption (lib.mdDoc "skydns service");
 
     etcd = {
       machines = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/smartdns.nix b/nixpkgs/nixos/modules/services/networking/smartdns.nix
index aa132747885d..af8ee8b00c0a 100644
--- a/nixpkgs/nixos/modules/services/networking/smartdns.nix
+++ b/nixpkgs/nixos/modules/services/networking/smartdns.nix
@@ -20,7 +20,7 @@ let
     } cfg.settings);
 in {
   options.services.smartdns = {
-    enable = mkEnableOption "SmartDNS DNS server";
+    enable = mkEnableOption (lib.mdDoc "SmartDNS DNS server");
 
     bindPort = mkOption {
       type = types.port;
diff --git a/nixpkgs/nixos/modules/services/networking/smokeping.nix b/nixpkgs/nixos/modules/services/networking/smokeping.nix
index 7f1abcc6824f..c7aec7d9489f 100644
--- a/nixpkgs/nixos/modules/services/networking/smokeping.nix
+++ b/nixpkgs/nixos/modules/services/networking/smokeping.nix
@@ -8,52 +8,49 @@ let
   smokepingPidDir = "/run";
   configFile =
     if cfg.config == null
-      then
-        ''
-          *** General ***
-          cgiurl   = ${cfg.cgiUrl}
-          contact = ${cfg.ownerEmail}
-          datadir  = ${smokepingHome}/data
-          imgcache = ${smokepingHome}/cache
-          imgurl   = ${cfg.imgUrl}
-          linkstyle = ${cfg.linkStyle}
-          ${lib.optionalString (cfg.mailHost != "") "mailhost = ${cfg.mailHost}"}
-          owner = ${cfg.owner}
-          pagedir = ${smokepingHome}/cache
-          piddir  = ${smokepingPidDir}
-          ${lib.optionalString (cfg.sendmail != null) "sendmail = ${cfg.sendmail}"}
-          smokemail = ${cfg.smokeMailTemplate}
-          *** Presentation ***
-          template = ${cfg.presentationTemplate}
-          ${cfg.presentationConfig}
-          *** Alerts ***
-          ${cfg.alertConfig}
-          *** Database ***
-          ${cfg.databaseConfig}
-          *** Probes ***
-          ${cfg.probeConfig}
-          *** Targets ***
-          ${cfg.targetConfig}
-          ${cfg.extraConfig}
-        ''
-      else
-        cfg.config;
+    then
+      ''
+        *** General ***
+        cgiurl   = ${cfg.cgiUrl}
+        contact = ${cfg.ownerEmail}
+        datadir  = ${smokepingHome}/data
+        imgcache = ${smokepingHome}/cache
+        imgurl   = ${cfg.imgUrl}
+        linkstyle = ${cfg.linkStyle}
+        ${lib.optionalString (cfg.mailHost != "") "mailhost = ${cfg.mailHost}"}
+        owner = ${cfg.owner}
+        pagedir = ${smokepingHome}/cache
+        piddir  = ${smokepingPidDir}
+        ${lib.optionalString (cfg.sendmail != null) "sendmail = ${cfg.sendmail}"}
+        smokemail = ${cfg.smokeMailTemplate}
+        *** Presentation ***
+        template = ${cfg.presentationTemplate}
+        ${cfg.presentationConfig}
+        *** Alerts ***
+        ${cfg.alertConfig}
+        *** Database ***
+        ${cfg.databaseConfig}
+        *** Probes ***
+        ${cfg.probeConfig}
+        *** Targets ***
+        ${cfg.targetConfig}
+        ${cfg.extraConfig}
+      ''
+    else
+      cfg.config;
 
   configPath = pkgs.writeText "smokeping.conf" configFile;
   cgiHome = pkgs.writeScript "smokeping.fcgi" ''
     #!${pkgs.bash}/bin/bash
-    ${cfg.package}/bin/smokeping_cgi ${configPath}
+    ${cfg.package}/bin/smokeping_cgi /etc/smokeping.conf
   '';
 in
 
 {
   options = {
     services.smokeping = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Enable the smokeping service";
-      };
+      enable = mkEnableOption (lib.mdDoc "smokeping service");
+
       alertConfig = mkOption {
         type = types.lines;
         default = ''
@@ -82,8 +79,10 @@ in
       config = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = "Full smokeping config supplied by the user. Overrides " +
-          "and replaces any other configuration supplied.";
+        description = lib.mdDoc ''
+          Full smokeping config supplied by the user. Overrides
+          and replaces any other configuration supplied.
+        '';
       };
       databaseConfig = mkOption {
         type = types.lines;
@@ -142,7 +141,7 @@ in
         '';
       };
       linkStyle = mkOption {
-        type = types.enum ["original" "absolute" "relative"];
+        type = types.enum [ "original" "absolute" "relative" ];
         default = "relative";
         example = "absolute";
         description = lib.mdDoc "DNS name for the urls generated in the cgi.";
@@ -184,7 +183,7 @@ in
         '';
       };
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8081;
         description = lib.mdDoc "TCP port to use for the web server.";
       };
@@ -302,12 +301,14 @@ in
     ];
     security.wrappers = {
       fping =
-        { setuid = true;
+        {
+          setuid = true;
           owner = "root";
           group = "root";
           source = "${pkgs.fping}/bin/fping";
         };
     };
+    environment.etc."smokeping.conf".source = configPath;
     environment.systemPackages = [ pkgs.fping ];
     users.users.${cfg.user} = {
       isNormalUser = false;
@@ -316,28 +317,39 @@ in
       description = "smokeping daemon user";
       home = smokepingHome;
       createHome = true;
+      # When `cfg.webService` is enabled, `thttpd` makes SmokePing available
+      # under `${cfg.host}:${cfg.port}/smokeping.fcgi` as per the `ln -s` below.
+      # We also want that going to `${cfg.host}:${cfg.port}` without `smokeping.fcgi`
+      # makes it easy for the user to find SmokePing.
+      # However `thttpd` does not seem to support easy redirections from `/` to `smokeping.fcgi`
+      # and only allows directory listings or `/` -> `index.html` resolution if the directory
+      # has `chmod 755` (see https://acme.com/software/thttpd/thttpd_man.html#PERMISSIONS,
+      # " directories should be 755 if you want to allow indexing").
+      # Otherwise it shows `403 Forbidden` on `/`.
+      # Thus, we need to make `smokepingHome` (which is given to `thttpd -d` below) `755`.
+      homeMode = "755";
     };
-    users.groups.${cfg.user} = {};
+    users.groups.${cfg.user} = { };
     systemd.services.smokeping = {
-      requiredBy = [ "multi-user.target"];
+      reloadTriggers = [ configPath ];
+      requiredBy = [ "multi-user.target" ];
       serviceConfig = {
         User = cfg.user;
         Restart = "on-failure";
-        ExecStart = "${cfg.package}/bin/smokeping --config=${configPath} --nodaemon";
+        ExecStart = "${cfg.package}/bin/smokeping --config=/etc/smokeping.conf --nodaemon";
       };
       preStart = ''
         mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data
-        rm -f ${smokepingHome}/cropper
-        ln -s ${cfg.package}/htdocs/cropper ${smokepingHome}/cropper
-        rm -f ${smokepingHome}/smokeping.fcgi
-        ln -s ${cgiHome} ${smokepingHome}/smokeping.fcgi
+        ln -snf ${cfg.package}/htdocs/css ${smokepingHome}/css
+        ln -snf ${cfg.package}/htdocs/js ${smokepingHome}/js
+        ln -snf ${cgiHome} ${smokepingHome}/smokeping.fcgi
         ${cfg.package}/bin/smokeping --check --config=${configPath}
         ${cfg.package}/bin/smokeping --static --config=${configPath}
       '';
     };
     systemd.services.thttpd = mkIf cfg.webService {
-      requiredBy = [ "multi-user.target"];
-      requires = [ "smokeping.service"];
+      requiredBy = [ "multi-user.target" ];
+      requires = [ "smokeping.service" ];
       path = with pkgs; [ bash rrdtool smokeping thttpd ];
       serviceConfig = {
         Restart = "always";
diff --git a/nixpkgs/nixos/modules/services/networking/sniproxy.nix b/nixpkgs/nixos/modules/services/networking/sniproxy.nix
index dedeb96f7369..b805b7b44d72 100644
--- a/nixpkgs/nixos/modules/services/networking/sniproxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/sniproxy.nix
@@ -18,7 +18,7 @@ in
 
   options = {
     services.sniproxy = {
-      enable = mkEnableOption "sniproxy server";
+      enable = mkEnableOption (lib.mdDoc "sniproxy server");
 
       user = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/snowflake-proxy.nix b/nixpkgs/nixos/modules/services/networking/snowflake-proxy.nix
index d759b07e8bf0..ca015ed9d44b 100644
--- a/nixpkgs/nixos/modules/services/networking/snowflake-proxy.nix
+++ b/nixpkgs/nixos/modules/services/networking/snowflake-proxy.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.snowflake-proxy = {
-      enable = mkEnableOption "System to defeat internet censorship";
+      enable = mkEnableOption (lib.mdDoc "System to defeat internet censorship");
 
       broker = mkOption {
         description = lib.mdDoc "Broker URL (default \"https://snowflake-broker.torproject.net/\")";
@@ -71,7 +71,7 @@ in
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = "~@clock @cpu-emulation @debug @mount @obsolete @reboot @swap @privileged @resources";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
         UMask = "0077";
       };
     };
diff --git a/nixpkgs/nixos/modules/services/networking/softether.nix b/nixpkgs/nixos/modules/services/networking/softether.nix
index 47d10bf64ca3..c8e888eafcc2 100644
--- a/nixpkgs/nixos/modules/services/networking/softether.nix
+++ b/nixpkgs/nixos/modules/services/networking/softether.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.softether;
 
-  package = cfg.package.override { dataDir = cfg.dataDir; };
+  package = cfg.package.override { inherit (cfg) dataDir; };
 
 in
 {
@@ -16,7 +16,7 @@ in
 
     services.softether = {
 
-      enable = mkEnableOption "SoftEther VPN services";
+      enable = mkEnableOption (lib.mdDoc "SoftEther VPN services");
 
       package = mkOption {
         type = types.package;
@@ -27,12 +27,12 @@ in
         '';
       };
 
-      vpnserver.enable = mkEnableOption "SoftEther VPN Server";
+      vpnserver.enable = mkEnableOption (lib.mdDoc "SoftEther VPN Server");
 
-      vpnbridge.enable = mkEnableOption "SoftEther VPN Bridge";
+      vpnbridge.enable = mkEnableOption (lib.mdDoc "SoftEther VPN Bridge");
 
       vpnclient = {
-        enable = mkEnableOption "SoftEther VPN Client";
+        enable = mkEnableOption (lib.mdDoc "SoftEther VPN Client");
         up = mkOption {
           type = types.lines;
           default = "";
@@ -88,7 +88,7 @@ in
       };
     }
 
-    (mkIf (cfg.vpnserver.enable) {
+    (mkIf cfg.vpnserver.enable {
       systemd.services.vpnserver = {
         description = "SoftEther VPN Server";
         after = [ "softether-init.service" ];
@@ -109,7 +109,7 @@ in
       };
     })
 
-    (mkIf (cfg.vpnbridge.enable) {
+    (mkIf cfg.vpnbridge.enable {
       systemd.services.vpnbridge = {
         description = "SoftEther VPN Bridge";
         after = [ "softether-init.service" ];
@@ -130,7 +130,7 @@ in
       };
     })
 
-    (mkIf (cfg.vpnclient.enable) {
+    (mkIf cfg.vpnclient.enable {
       systemd.services.vpnclient = {
         description = "SoftEther VPN Client";
         after = [ "softether-init.service" ];
diff --git a/nixpkgs/nixos/modules/services/networking/soju.nix b/nixpkgs/nixos/modules/services/networking/soju.nix
index dddacea20044..7f0ac3e3b8e6 100644
--- a/nixpkgs/nixos/modules/services/networking/soju.nix
+++ b/nixpkgs/nixos/modules/services/networking/soju.nix
@@ -27,7 +27,7 @@ in
   ###### interface
 
   options.services.soju = {
-    enable = mkEnableOption "soju";
+    enable = mkEnableOption (lib.mdDoc "soju");
 
     listen = mkOption {
       type = types.listOf types.str;
@@ -79,7 +79,7 @@ in
     acceptProxyIP = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Allow the specified IPs to act as a proxy. Proxys have the ability to
         overwrite the remote and local connection addresses (via the X-Forwarded-\*
         HTTP header fields). The special name "localhost" accepts the loopback
@@ -120,5 +120,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ malvo ];
+  meta.maintainers = with maintainers; [ malte-v ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/solanum.nix b/nixpkgs/nixos/modules/services/networking/solanum.nix
index daa3650fc99a..07a37279fecc 100644
--- a/nixpkgs/nixos/modules/services/networking/solanum.nix
+++ b/nixpkgs/nixos/modules/services/networking/solanum.nix
@@ -16,7 +16,7 @@ in
 
     services.solanum = {
 
-      enable = mkEnableOption "Solanum IRC daemon";
+      enable = mkEnableOption (lib.mdDoc "Solanum IRC daemon");
 
       config = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/spacecookie.nix b/nixpkgs/nixos/modules/services/networking/spacecookie.nix
index 4aa76de6f41a..b2956edfcb7f 100644
--- a/nixpkgs/nixos/modules/services/networking/spacecookie.nix
+++ b/nixpkgs/nixos/modules/services/networking/spacecookie.nix
@@ -25,7 +25,7 @@ in {
 
     services.spacecookie = {
 
-      enable = mkEnableOption "spacecookie";
+      enable = mkEnableOption (lib.mdDoc "spacecookie");
 
       package = mkOption {
         type = types.package;
@@ -90,7 +90,7 @@ in {
           };
 
           options.log = {
-            enable = mkEnableOption "logging for spacecookie"
+            enable = mkEnableOption (lib.mdDoc "logging for spacecookie")
               // { default = true; example = false; };
 
             hide-ips = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
index 41c4ec2d2951..af64969c2fcd 100644
--- a/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
@@ -40,7 +40,7 @@ in
         type = types.listOf types.str;
         description = lib.mdDoc ''
           List of network interfaces where listening for connections.
-          When providing the empty list, `[]', lshd listens on all
+          When providing the empty list, `[]`, lshd listens on all
           network interfaces.
         '';
         example = [ "localhost" "1.2.3.4:443" ];
@@ -169,11 +169,11 @@ in
             else (concatStrings (map (i: "--interface=\"${i}\"")
                                      interfaces))} \
           -h "${hostKey}" \
-          ${if !syslog then "--no-syslog" else ""} \
+          ${optionalString (!syslog) "--no-syslog" } \
           ${if passwordAuthentication then "--password" else "--no-password" } \
           ${if publicKeyAuthentication then "--publickey" else "--no-publickey" } \
           ${if rootLogin then "--root-login" else "--no-root-login" } \
-          ${if loginShell != null then "--login-shell=\"${loginShell}\"" else "" } \
+          ${optionalString (loginShell != null) "--login-shell=\"${loginShell}\"" } \
           ${if srpKeyExchange then "--srp-keyexchange" else "--no-srp-keyexchange" } \
           ${if !tcpForwarding then "--no-tcpip-forward" else "--tcpip-forward"} \
           ${if x11Forwarding then "--x11-forward" else "--no-x11-forward" } \
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
index 27e037d66a0f..1367381b7a96 100644
--- a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
@@ -12,8 +12,24 @@ let
     then cfgc.package
     else pkgs.buildPackages.openssh;
 
+  # reports boolean as yes / no
+  mkValueStringSshd = with lib; v:
+        if isInt           v then toString v
+        else if isString   v then v
+        else if true  ==   v then "yes"
+        else if false ==   v then "no"
+        else if isList     v then concatStringsSep "," v
+        else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
+
+  # dont use the "=" operator
+  settingsFormat = (pkgs.formats.keyValue {
+      mkKeyValue = lib.generators.mkKeyValueDefault {
+      mkValueString = mkValueStringSshd;
+    } " ";});
+
+  configFile = settingsFormat.generate "config" cfg.settings;
   sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
-    cat >$out <<EOL
+    cat ${configFile} - >$out <<EOL
     ${cfg.extraConfig}
     EOL
 
@@ -24,6 +40,7 @@ let
   cfg  = config.services.openssh;
   cfgc = config.programs.ssh;
 
+
   nssModulesPath = config.system.nssModules.path;
 
   userOptions = {
@@ -79,9 +96,20 @@ in
 
 {
   imports = [
-    (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
-    (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
+    (mkAliasOptionModuleMD [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
+    (mkAliasOptionModuleMD [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
     (mkRenamedOptionModule [ "services" "openssh" "challengeResponseAuthentication" ] [ "services" "openssh" "kbdInteractiveAuthentication" ])
+
+    (mkRenamedOptionModule [ "services" "openssh" "kbdInteractiveAuthentication" ] [  "services" "openssh" "settings" "KbdInteractiveAuthentication" ])
+    (mkRenamedOptionModule [ "services" "openssh" "passwordAuthentication" ] [  "services" "openssh" "settings" "PasswordAuthentication" ])
+    (mkRenamedOptionModule [ "services" "openssh" "useDns" ] [  "services" "openssh" "settings" "UseDns" ])
+    (mkRenamedOptionModule [ "services" "openssh" "permitRootLogin" ] [  "services" "openssh" "settings" "PermitRootLogin" ])
+    (mkRenamedOptionModule [ "services" "openssh" "logLevel" ] [  "services" "openssh" "settings" "LogLevel" ])
+    (mkRenamedOptionModule [ "services" "openssh" "macs" ] [  "services" "openssh" "settings" "Macs" ])
+    (mkRenamedOptionModule [ "services" "openssh" "ciphers" ] [  "services" "openssh" "settings" "Ciphers" ])
+    (mkRenamedOptionModule [ "services" "openssh" "kexAlgorithms" ] [  "services" "openssh" "settings" "KexAlgorithms" ])
+    (mkRenamedOptionModule [ "services" "openssh" "gatewayPorts" ] [  "services" "openssh" "settings" "GatewayPorts" ])
+    (mkRenamedOptionModule [ "services" "openssh" "forwardX11" ] [  "services" "openssh" "settings" "X11Forwarding" ])
   ];
 
   ###### interface
@@ -109,14 +137,6 @@ in
         '';
       };
 
-      forwardX11 = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to allow X11 connections to be forwarded.
-        '';
-      };
-
       allowSFTP = mkOption {
         type = types.bool;
         default = true;
@@ -145,24 +165,6 @@ in
         '';
       };
 
-      permitRootLogin = mkOption {
-        default = "prohibit-password";
-        type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
-        description = lib.mdDoc ''
-          Whether the root user can login using ssh.
-        '';
-      };
-
-      gatewayPorts = mkOption {
-        type = types.str;
-        default = "no";
-        description = lib.mdDoc ''
-          Specifies whether remote hosts are allowed to connect to
-          ports forwarded for the client.  See
-          {manpage}`sshd_config(5)`.
-        '';
-      };
-
       ports = mkOption {
         type = types.listOf types.port;
         default = [22];
@@ -210,22 +212,6 @@ in
         '';
       };
 
-      passwordAuthentication = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Specifies whether password authentication is allowed.
-        '';
-      };
-
-      kbdInteractiveAuthentication = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Specifies whether keyboard-interactive authentication is allowed.
-        '';
-      };
-
       hostKeys = mkOption {
         type = types.listOf types.attrs;
         default =
@@ -288,84 +274,135 @@ in
         '';
       };
 
-      kexAlgorithms = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "sntrup761x25519-sha512@openssh.com"
-          "curve25519-sha256"
-          "curve25519-sha256@libssh.org"
-          "diffie-hellman-group-exchange-sha256"
-        ];
-        description = lib.mdDoc ''
-          Allowed key exchange algorithms
-
-          Uses the lower bound recommended in both
-          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
-          and
-          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
-        '';
-      };
-
-      ciphers = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "chacha20-poly1305@openssh.com"
-          "aes256-gcm@openssh.com"
-          "aes128-gcm@openssh.com"
-          "aes256-ctr"
-          "aes192-ctr"
-          "aes128-ctr"
-        ];
-        description = lib.mdDoc ''
-          Allowed ciphers
 
-          Defaults to recommended settings from both
-          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
-          and
-          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
-        '';
-      };
 
-      macs = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "hmac-sha2-512-etm@openssh.com"
-          "hmac-sha2-256-etm@openssh.com"
-          "umac-128-etm@openssh.com"
-          "hmac-sha2-512"
-          "hmac-sha2-256"
-          "umac-128@openssh.com"
-        ];
-        description = lib.mdDoc ''
-          Allowed MACs
+      settings = mkOption {
+        description = lib.mdDoc "Configuration for `sshd_config(5)`.";
+        default = { };
+        example = literalExpression ''{
+          UseDns = true;
+          PasswordAuthentication = false;
+        }'';
+        type = types.submodule ({name, ...}: {
+          freeformType = settingsFormat.type;
+          options = {
+            LogLevel = mkOption {
+              type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
+              default = "INFO"; # upstream default
+              description = lib.mdDoc ''
+                Gives the verbosity level that is used when logging messages from sshd(8). Logging with a DEBUG level
+                violates the privacy of users and is not recommended.
+              '';
+            };
+            UseDns = mkOption {
+              type = types.bool;
+              # apply if cfg.useDns then "yes" else "no"
+              default = false;
+              description = lib.mdDoc ''
+                Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for
+                the remote IP address maps back to the very same IP address.
+                If this option is set to no (the default) then only addresses and not host names may be used in
+                ~/.ssh/authorized_keys from and sshd_config Match Host directives.
+              '';
+            };
+            X11Forwarding = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                Whether to allow X11 connections to be forwarded.
+              '';
+            };
+            PasswordAuthentication = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Specifies whether password authentication is allowed.
+              '';
+            };
+            PermitRootLogin = mkOption {
+              default = "prohibit-password";
+              type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
+              description = lib.mdDoc ''
+                Whether the root user can login using ssh.
+              '';
+            };
+            KbdInteractiveAuthentication = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Specifies whether keyboard-interactive authentication is allowed.
+              '';
+            };
+            GatewayPorts = mkOption {
+              type = types.str;
+              default = "no";
+              description = lib.mdDoc ''
+                Specifies whether remote hosts are allowed to connect to
+                ports forwarded for the client.  See
+                {manpage}`sshd_config(5)`.
+              '';
+            };
+            KexAlgorithms = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "sntrup761x25519-sha512@openssh.com"
+                "curve25519-sha256"
+                "curve25519-sha256@libssh.org"
+                "diffie-hellman-group-exchange-sha256"
+              ];
+              description = lib.mdDoc ''
+                Allowed key exchange algorithms
 
-          Defaults to recommended settings from both
-          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
-          and
-          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
-        '';
-      };
+                Uses the lower bound recommended in both
+                <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
+                and
+                <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
+              '';
+            };
+            Macs = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "hmac-sha2-512-etm@openssh.com"
+                "hmac-sha2-256-etm@openssh.com"
+                "umac-128-etm@openssh.com"
+              ];
+              description = lib.mdDoc ''
+                Allowed MACs
 
-      logLevel = mkOption {
-        type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
-        default = "INFO"; # upstream default
-        description = lib.mdDoc ''
-          Gives the verbosity level that is used when logging messages from sshd(8). The possible values are:
-          QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is INFO. DEBUG and DEBUG1
-          are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level
-          violates the privacy of users and is not recommended.
-        '';
-      };
+                Defaults to recommended settings from both
+                <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
+                and
+                <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
+              '';
+            };
+            StrictModes = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether sshd should check file modes and ownership of directories
+              '';
+            };
+            Ciphers = mkOption {
+              type = types.listOf types.str;
+              default = [
+                "chacha20-poly1305@openssh.com"
+                "aes256-gcm@openssh.com"
+                "aes128-gcm@openssh.com"
+                "aes256-ctr"
+                "aes192-ctr"
+                "aes128-ctr"
+              ];
+              description = lib.mdDoc ''
+                Allowed ciphers
 
-      useDns = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for
-          the remote IP address maps back to the very same IP address.
-          If this option is set to no (the default) then only addresses and not host names may be used in
-          ~/.ssh/authorized_keys from and sshd_config Match Host directives.
-        '';
+                Defaults to recommended settings from both
+                <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
+                and
+                <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
+              '';
+            };
+          };
+        });
       };
 
       extraConfig = mkOption {
@@ -383,14 +420,6 @@ in
           the `moduli` file shipped with OpenSSH will be used.
         '';
       };
-
-      strictModes = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether sshd should check file modes and ownership of directories
-        '';
-      };
     };
 
     users.users = mkOption {
@@ -451,10 +480,10 @@ in
                       mkdir -m 0755 -p "$(dirname '${k.path}')"
                       ssh-keygen \
                         -t "${k.type}" \
-                        ${if k ? bits then "-b ${toString k.bits}" else ""} \
-                        ${if k ? rounds then "-a ${toString k.rounds}" else ""} \
-                        ${if k ? comment then "-C '${k.comment}'" else ""} \
-                        ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \
+                        ${optionalString (k ? bits) "-b ${toString k.bits}"} \
+                        ${optionalString (k ? rounds) "-a ${toString k.rounds}"} \
+                        ${optionalString (k ? comment) "-C '${k.comment}'"} \
+                        ${optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \
                         -f "${k.path}" \
                         -N ""
                   fi
@@ -506,14 +535,14 @@ in
     security.pam.services.sshd =
       { startSession = true;
         showMotd = true;
-        unixAuth = cfg.passwordAuthentication;
+        unixAuth = cfg.settings.PasswordAuthentication;
       };
 
     # These values are merged with the ones defined externally, see:
     # https://github.com/NixOS/nixpkgs/pull/10155
     # https://github.com/NixOS/nixpkgs/pull/41745
     services.openssh.authorizedKeysFiles =
-      [ "%h/.ssh/authorized_keys" "%h/.ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
+      [ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ];
 
     services.openssh.extraConfig = mkOrder 0
       ''
@@ -527,30 +556,16 @@ in
         '') cfg.ports}
 
         ${concatMapStrings ({ port, addr, ... }: ''
-          ListenAddress ${addr}${if port != null then ":" + toString port else ""}
+          ListenAddress ${addr}${optionalString (port != null) (":" + toString port)}
         '') cfg.listenAddresses}
 
         ${optionalString cfgc.setXAuthLocation ''
             XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
         ''}
-
-        X11Forwarding ${if cfg.forwardX11 then "yes" else "no"}
-
         ${optionalString cfg.allowSFTP ''
           Subsystem sftp ${cfg.sftpServerExecutable} ${concatStringsSep " " cfg.sftpFlags}
         ''}
-
-        PermitRootLogin ${cfg.permitRootLogin}
-        GatewayPorts ${cfg.gatewayPorts}
-        PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
-        KbdInteractiveAuthentication ${if cfg.kbdInteractiveAuthentication then "yes" else "no"}
-
         PrintMotd no # handled by pam_motd
-
-        ${optionalString (cfg.authorizedKeysCommand != null) ''
-          AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
-          AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser}
-        ''}
         AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
         ${optionalString (cfg.authorizedKeysCommand != "none") ''
           AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
@@ -560,26 +575,30 @@ in
         ${flip concatMapStrings cfg.hostKeys (k: ''
           HostKey ${k.path}
         '')}
-
-        KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}
-        Ciphers ${concatStringsSep "," cfg.ciphers}
-        MACs ${concatStringsSep "," cfg.macs}
-
-        LogLevel ${cfg.logLevel}
-
-        UseDNS ${if cfg.useDns then "yes" else "no"}
-
-        StrictModes ${if cfg.strictModes then "yes" else "no"}
-
       '';
 
-    assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
-                    message = "cannot enable X11 forwarding without setting xauth location";}]
+    assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true;
+                    message = "cannot enable X11 forwarding without setting xauth location";}
+                  (let
+                    duplicates =
+                      # Filter out the groups with more than 1 element
+                      lib.filter (l: lib.length l > 1) (
+                        # Grab the groups, we don't care about the group identifiers
+                        lib.attrValues (
+                          # Group the settings that are the same in lower case
+                          lib.groupBy lib.strings.toLower (attrNames cfg.settings)
+                        )
+                      );
+                    formattedDuplicates = lib.concatMapStringsSep ", " (dupl: "(${lib.concatStringsSep ", " dupl})") duplicates;
+                  in
+                  {
+                    assertion = lib.length duplicates == 0;
+                    message = ''Duplicate sshd config key; does your capitalization match the option's? Duplicate keys: ${formattedDuplicates}'';
+                  })]
       ++ forEach cfg.listenAddresses ({ addr, ... }: {
         assertion = addr != null;
         message = "addr must be specified in each listenAddresses entry";
       });
-
   };
 
 }
diff --git a/nixpkgs/nixos/modules/services/networking/sslh.nix b/nixpkgs/nixos/modules/services/networking/sslh.nix
index 03c0bd23141a..daf2f2f3668e 100644
--- a/nixpkgs/nixos/modules/services/networking/sslh.nix
+++ b/nixpkgs/nixos/modules/services/networking/sslh.nix
@@ -43,7 +43,7 @@ in
 
   options = {
     services.sslh = {
-      enable = mkEnableOption "sslh";
+      enable = mkEnableOption (lib.mdDoc "sslh");
 
       verbose = mkOption {
         type = types.bool;
@@ -70,7 +70,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 443;
         description = lib.mdDoc "Listening port.";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
index a92834f0ecff..c51e8ad9f5fc 100644
--- a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -8,7 +8,7 @@ let
   swanctlParams = import ./swanctl-params.nix lib;
 in  {
   options.services.strongswan-swanctl = {
-    enable = mkEnableOption "strongswan-swanctl service";
+    enable = mkEnableOption (lib.mdDoc "strongswan-swanctl service");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
index d5a8daf98ec6..dc6d8f48e626 100644
--- a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
@@ -57,12 +57,12 @@ rec {
 
   documentDefault = description : strongswanDefault :
     if strongswanDefault == null
-    then description
-    else description + ''
+    then mdDoc description
+    else mdDoc (description + ''
 
 
-      StrongSwan default: <literal><![CDATA[${builtins.toJSON strongswanDefault}]]></literal>
-    '';
+      StrongSwan default: ````${builtins.toJSON strongswanDefault}````
+    '');
 
   single = f: name: value: { ${name} = f value; };
 
@@ -121,7 +121,7 @@ rec {
     option = mkOption {
       type = types.attrsOf option;
       default = {};
-      inherit description;
+      description = mdDoc description;
     };
     render = single (attrs:
       (paramsToRenderedStrings attrs
@@ -139,7 +139,7 @@ rec {
     option = mkOption {
       type = types.attrsOf option;
       default = {};
-      inherit description;
+      description = mdDoc description;
     };
     render = prefix: attrs:
       let prefixedAttrs = mapAttrs' (name: nameValuePair "${prefix}-${name}") attrs;
@@ -152,7 +152,7 @@ rec {
     option = mkOption {
       type = types.attrsOf (types.submodule {options = paramsToOptions params;});
       default = {};
-      inherit description;
+      description = lib.mdDoc description;
     };
     render = postfix: attrs:
       let postfixedAttrs = mapAttrs' (name: nameValuePair "${name}-${postfix}") attrs;
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 737d0331f195..1ad5fdbcef02 100644
--- a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -16,14 +16,14 @@ let
       Absolute path to the certificate to load. Passed as-is to the daemon, so
       it must be readable by it.
 
-      Configure either this or <option>handle</option>, but not both, in one section.
+      Configure either this or {option}`handle`, but not both, in one section.
     '';
 
     handle = mkOptionalHexParam ''
       Hex-encoded CKA_ID or handle of the certificate on a token or TPM,
       respectively.
 
-      Configure either this or <option>file</option>, but not both, in one section.
+      Configure either this or {option}`file`, but not both, in one section.
     '';
 
     slot = mkOptionalIntParam ''
@@ -39,11 +39,11 @@ in {
 
     cacert = mkOptionalStrParam ''
       The certificates may use a relative path from the swanctl
-      <literal>x509ca</literal> directory or an absolute path.
+      `x509ca` directory or an absolute path.
 
-      Configure one of <option>cacert</option>,
-      <option>file</option>, or
-      <option>handle</option> per section.
+      Configure one of {option}`cacert`,
+      {option}`file`, or
+      {option}`handle` per section.
     '';
 
     cert_uri_base = mkOptionalStrParam ''
@@ -71,12 +71,11 @@ in {
 
     version = mkIntParam 0 ''
       IKE major version to use for connection.
-      <itemizedlist>
-      <listitem><para>1 uses IKEv1 aka ISAKMP,</para></listitem>
-      <listitem><para>2 uses IKEv2.</para></listitem>
-      <listitem><para>A connection using the default of 0 accepts both IKEv1 and IKEv2 as
-      responder, and initiates the connection actively with IKEv2.</para></listitem>
-      </itemizedlist>
+
+      - 1 uses IKEv1 aka ISAKMP,
+      - 2 uses IKEv2.
+      - A connection using the default of 0 accepts both IKEv1 and IKEv2 as
+        responder, and initiates the connection actively with IKEv2.
     '';
 
     local_addrs	= mkCommaSepListParam [] ''
@@ -107,9 +106,9 @@ in {
 
     local_port = mkIntParam 500 ''
       Local UDP port for IKE communication. By default the port of the socket
-      backend is used, which is usually <literal>500</literal>. If port
-      <literal>500</literal> is used, automatic IKE port floating to port
-      <literal>4500</literal> is used to work around NAT issues.
+      backend is used, which is usually `500`. If port
+      `500` is used, automatic IKE port floating to port
+      `4500` is used to work around NAT issues.
 
       Using a non-default local IKE port requires support from the socket
       backend in use (socket-dynamic).
@@ -117,8 +116,8 @@ in {
 
     remote_port = mkIntParam 500 ''
       Remote UDP port for IKE communication. If the default of port
-      <literal>500</literal> is used, automatic IKE port floating to port
-      <literal>4500</literal> is used to work around NAT issues.
+      `500` is used, automatic IKE port floating to port
+      `4500` is used to work around NAT issues.
     '';
 
     proposals = mkCommaSepListParam ["default"] ''
@@ -134,15 +133,15 @@ in {
       combinations in IKEv1.
 
       Algorithm keywords get separated using dashes. Multiple proposals may be
-      specified in a list. The special value <literal>default</literal> forms a
+      specified in a list. The special value `default` forms a
       default proposal of supported algorithms considered safe, and is usually a
       good choice for interoperability.
     '';
 
     vips = mkCommaSepListParam [] ''
       List of virtual IPs to request in IKEv2 configuration payloads or IKEv1
-      Mode Config. The wildcard addresses <literal>0.0.0.0</literal> and
-      <literal>::</literal> request an arbitrary address, specific addresses may
+      Mode Config. The wildcard addresses `0.0.0.0` and
+      `::` request an arbitrary address, specific addresses may
       be defined. The responder may return a different address, though, or none
       at all.
     '';
@@ -207,40 +206,41 @@ in {
 
     fragmentation = mkEnumParam ["yes" "accept" "force" "no"] "yes" ''
       Use IKE fragmentation (proprietary IKEv1 extension or RFC 7383 IKEv2
-      fragmentation). Acceptable values are <literal>yes</literal> (the default
-      since 5.5.1), <literal>accept</literal> (since versions:5.5.3),
-      <literal>force</literal> and <literal>no</literal>.
-      <itemizedlist>
-      <listitem><para>If set to <literal>yes</literal>, and the peer
-      supports it, oversized IKE messages will be sent in fragments.</para></listitem>
-      <listitem><para>If set to
-      <literal>accept</literal>, support for fragmentation is announced to the peer but the daemon
-      does not send its own messages in fragments.</para></listitem>
-      <listitem><para>If set to <literal>force</literal> (only
-      supported for IKEv1) the initial IKE message will already be fragmented if
-      required.</para></listitem>
-      <listitem><para>Finally, setting the option to <literal>no</literal> will disable announcing
-      support for this feature.</para></listitem>
-      </itemizedlist>
+      fragmentation). Acceptable values are `yes` (the default
+      since 5.5.1), `accept` (since versions:5.5.3),
+      `force` and `no`.
+
+      - If set to `yes`, and the peer
+        supports it, oversized IKE messages will be sent in fragments.
+      - If set to
+        `accept`, support for fragmentation is announced to the peer but the daemon
+        does not send its own messages in fragments.
+      - If set to `force` (only
+        supported for IKEv1) the initial IKE message will already be fragmented if
+        required.
+      - Finally, setting the option to `no` will disable announcing
+        support for this feature.
 
       Note that fragmented IKE messages sent by a peer are always processed
       irrespective of the value of this option (even when set to no).
     '';
 
-    childless = mkEnumParam [ "allow" "force" "never" ] "allow" ''
-      Use childless IKE_SA initiation (RFC 6023) for IKEv2.  Acceptable values
-      are <literal>allow</literal> (the default), <literal>force</literal> and
-      <literal>never</literal>. If set to <literal>allow</literal>, responders
+    childless = mkEnumParam [ "allow" "prefer" "force" "never" ] "allow" ''
+      Use childless IKE_SA initiation (_allow_, _prefer_, _force_ or _never_).
+
+      Use childless IKE_SA initiation (RFC 6023) for IKEv2, with the first
+      CHILD_SA created with a separate CREATE_CHILD_SA exchange (e.g. to use an
+      independent DH exchange for all CHILD_SAs).  Acceptable values are `allow`
+      (the default), `prefer`, `force` and `never`. If set to `allow`, responders
       will accept childless IKE_SAs (as indicated via notify in the IKE_SA_INIT
-      response) while initiators continue to create regular IKE_SAs with the
-      first CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated
-      explicitly without any children (which will fail if the responder does not
-      support or has disabled this extension).  If set to
-      <literal>force</literal>, only childless initiation is accepted and the
-      first CHILD_SA is created with a separate CREATE_CHILD_SA exchange
-      (e.g. to use an independent DH exchange for all CHILD_SAs). Finally,
-      setting the option to <literal>never</literal> disables support for
-      childless IKE_SAs as responder.
+      response) while initiators continue to create regular IKE_SAs with the first
+      CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated explicitly
+      without any children (which will fail if the responder does not support or
+      has disabled this extension). The effect of `prefer` is the same as `allow`
+      on responders, but as initiator a childless IKE_SA is initiated if the
+      responder supports it. If set to `force`, only childless initiation is
+      accepted in either role.  Finally, setting the option to `never` disables
+      support for childless IKE_SAs as responder.
     '';
 
     send_certreq = mkYesNoParam yes ''
@@ -254,14 +254,13 @@ in {
 
     send_cert = mkEnumParam ["always" "never" "ifasked" ] "ifasked" ''
       Send certificate payloads when using certificate authentication.
-      <itemizedlist>
-      <listitem><para>With the default of <literal>ifasked</literal> the daemon sends
-      certificate payloads only if certificate requests have been received.</para></listitem>
-      <listitem><para><literal>never</literal> disables sending of certificate payloads
-      altogether,</para></listitem>
-      <listitem><para><literal>always</literal> causes certificate payloads to be sent
-      unconditionally whenever certificate authentication is used.</para></listitem>
-      </itemizedlist>
+
+      - With the default of `ifasked` the daemon sends
+        certificate payloads only if certificate requests have been received.
+      - `never` disables sending of certificate payloads
+        altogether,
+      - `always` causes certificate payloads to be sent
+        unconditionally whenever certificate authentication is used.
     '';
 
     ppk_id = mkOptionalStrParam ''
@@ -275,9 +274,9 @@ in {
     keyingtries = mkIntParam 1 ''
       Number of retransmission sequences to perform during initial
       connect. Instead of giving up initiation after the first retransmission
-      sequence with the default value of <literal>1</literal>, additional
+      sequence with the default value of `1`, additional
       sequences may be started according to the configured value. A value of
-      <literal>0</literal> initiates a new sequence until the connection
+      `0` initiates a new sequence until the connection
       establishes or fails with a permanent error.
     '';
 
@@ -285,24 +284,15 @@ in {
       Connection uniqueness policy to enforce. To avoid multiple connections
       from the same user, a uniqueness policy can be enforced.
 
-      <itemizedlist>
-      <listitem><para>
-      The value <literal>never</literal> does never enforce such a policy, even
-      if a peer included INITIAL_CONTACT notification messages,
-      </para></listitem>
-      <listitem><para>
-      whereas <literal>no</literal> replaces existing connections for the same
-      identity if a new one has the INITIAL_CONTACT notify.
-      </para></listitem>
-      <listitem><para>
-      <literal>keep</literal> rejects new connection attempts if the same user
-      already has an active connection,
-      </para></listitem>
-      <listitem><para>
-      <literal>replace</literal> deletes any existing connection if a new one
-      for the same user gets established.
-      </para></listitem>
-      </itemizedlist>
+      - The value `never` does never enforce such a policy, even
+        if a peer included INITIAL_CONTACT notification messages,
+      - whereas `no` replaces existing connections for the same
+        identity if a new one has the INITIAL_CONTACT notify.
+      - `keep` rejects new connection attempts if the same user
+        already has an active connection,
+      - `replace` deletes any existing connection if a new one
+        for the same user gets established.
+
       To compare connections for uniqueness, the remote IKE identity is used. If
       EAP or XAuth authentication is involved, the EAP-Identity or XAuth
       username is used to enforce the uniqueness policy instead.
@@ -310,7 +300,7 @@ in {
       On initiators this setting specifies whether an INITIAL_CONTACT notify is
       sent during IKE_AUTH if no existing connection is found with the remote
       peer (determined by the identities of the first authentication
-      round). Unless set to <literal>never</literal> the client will send a notify.
+      round). Unless set to `never` the client will send a notify.
     '';
 
     reauth_time	= mkDurationParam "0s" ''
@@ -347,8 +337,8 @@ in {
       In contrast to CHILD_SA rekeying, over_time is relative in time to the
       rekey_time and reauth_time values, as it applies to both.
 
-      The default is 10% of the longer of <option>rekey_time</option> and
-      <option>reauth_time</option>.
+      The default is 10% of the longer of {option}`rekey_time` and
+      {option}`reauth_time`.
     '';
 
     rand_time = mkOptionalDurationParam ''
@@ -357,7 +347,7 @@ in {
       procedure simultaneously, a random time gets subtracted from the
       rekey/reauth times.
 
-      The default is equal to the configured <option>over_time</option>.
+      The default is equal to the configured {option}`over_time`.
     '';
 
     pools = mkCommaSepListParam [] ''
@@ -369,11 +359,22 @@ in {
     if_id_in = mkStrParam "0" ''
       XFRM interface ID set on inbound policies/SA, can be overridden by child
       config, see there for details.
+
+      The special value `%unique` allocates a unique interface ID per IKE_SA,
+      which is inherited by all its CHILD_SAs (unless overridden there), beyond
+      that the value `%unique-dir` assigns a different unique interface ID for
+      each direction (in/out).
+
     '';
 
     if_id_out = mkStrParam "0" ''
       XFRM interface ID set on outbound policies/SA, can be overridden by child
       config, see there for details.
+
+      The special value `%unique` allocates a unique interface ID per IKE_SA,
+      which is inherited by all its CHILD_SAs (unless overridden there), beyond
+      that the value `%unique-dir` assigns a different unique interface ID for
+      each direction (in/out).
     '';
 
     mediation = mkYesNoParam no ''
@@ -409,7 +410,7 @@ in {
       certs = mkCommaSepListParam [] ''
         List of certificate candidates to use for
         authentication. The certificates may use a relative path from the
-        swanctl <literal>x509</literal> directory or an absolute path.
+        swanctl `x509` directory or an absolute path.
 
         The certificate used for authentication is selected based on the
         received certificate request payloads. If no appropriate CA can be
@@ -425,7 +426,7 @@ in {
       pubkeys = mkCommaSepListParam [] ''
         List of raw public key candidates to use for
         authentication. The public keys may use a relative path from the swanctl
-        <literal>pubkey</literal> directory or an absolute path.
+        `pubkey` directory or an absolute path.
 
         Even though multiple local public keys could be defined in principle,
         only the first public key in the list is used for authentication.
@@ -433,59 +434,44 @@ in {
 
       auth = mkStrParam "pubkey" ''
         Authentication to perform locally.
-        <itemizedlist>
-        <listitem><para>
-        The default <literal>pubkey</literal> uses public key authentication
-        using a private key associated to a usable certificate.
-        </para></listitem>
-        <listitem><para>
-        <literal>psk</literal> uses pre-shared key authentication.
-        </para></listitem>
-        <listitem><para>
-        The IKEv1 specific <literal>xauth</literal> is used for XAuth or Hybrid
-        authentication,
-        </para></listitem>
-        <listitem><para>
-        while the IKEv2 specific <literal>eap</literal> keyword defines EAP
-        authentication.
-        </para></listitem>
-        <listitem><para>
-        For <literal>xauth</literal>, a specific backend name may be appended,
-        separated by a dash. The appropriate <literal>xauth</literal> backend is
-        selected to perform the XAuth exchange. For traditional XAuth, the
-        <literal>xauth</literal> method is usually defined in the second
-        authentication round following an initial <literal>pubkey</literal> (or
-        <literal>psk</literal>) round. Using <literal>xauth</literal> in the
-        first round performs Hybrid Mode client authentication.
-        </para></listitem>
-        <listitem><para>
-        For <literal>eap</literal>, a specific EAP method name may be appended, separated by a
-        dash. An EAP module implementing the appropriate method is selected to
-        perform the EAP conversation.
-        </para></listitem>
-        <listitem><para>
-        Since 5.4.0, if both peers support RFC 7427 ("Signature Authentication
-        in IKEv2") specific hash algorithms to be used during IKEv2
-        authentication may be configured. To do so use <literal>ike:</literal>
-        followed by a trust chain signature scheme constraint (see description
-        of the <option>remote</option> section's <option>auth</option>
-        keyword). For example, with <literal>ike:pubkey-sha384-sha256</literal>
-        a public key signature scheme with either SHA-384 or SHA-256 would get
-        used for authentication, in that order and depending on the hash
-        algorithms supported by the peer. If no specific hash algorithms are
-        configured, the default is to prefer an algorithm that matches or
-        exceeds the strength of the signature key. If no constraints with
-        <literal>ike:</literal> prefix are configured any signature scheme
-        constraint (without <literal>ike:</literal> prefix) will also apply to
-        IKEv2 authentication, unless this is disabled in
-        <literal>strongswan.conf</literal>. To use RSASSA-PSS signatures use
-        <literal>rsa/pss</literal> instead of <literal>pubkey</literal> or
-        <literal>rsa</literal> as in e.g.
-        <literal>ike:rsa/pss-sha256</literal>. If <literal>pubkey</literal> or
-        <literal>rsa</literal> constraints are configured RSASSA-PSS signatures
-        will only be used if enabled in <literal>strongswan.conf</literal>(5).
-        </para></listitem>
-        </itemizedlist>
+
+        - The default `pubkey` uses public key authentication
+          using a private key associated to a usable certificate.
+        - `psk` uses pre-shared key authentication.
+        - The IKEv1 specific `xauth` is used for XAuth or Hybrid
+          authentication,
+        - while the IKEv2 specific `eap` keyword defines EAP
+          authentication.
+        - For `xauth`, a specific backend name may be appended,
+          separated by a dash. The appropriate `xauth` backend is
+          selected to perform the XAuth exchange. For traditional XAuth, the
+          `xauth` method is usually defined in the second
+          authentication round following an initial `pubkey` (or
+          `psk`) round. Using `xauth` in the
+          first round performs Hybrid Mode client authentication.
+        - For `eap`, a specific EAP method name may be appended, separated by a
+          dash. An EAP module implementing the appropriate method is selected to
+          perform the EAP conversation.
+        - Since 5.4.0, if both peers support RFC 7427 ("Signature Authentication
+          in IKEv2") specific hash algorithms to be used during IKEv2
+          authentication may be configured. To do so use `ike:`
+          followed by a trust chain signature scheme constraint (see description
+          of the {option}`remote` section's {option}`auth`
+          keyword). For example, with `ike:pubkey-sha384-sha256`
+          a public key signature scheme with either SHA-384 or SHA-256 would get
+          used for authentication, in that order and depending on the hash
+          algorithms supported by the peer. If no specific hash algorithms are
+          configured, the default is to prefer an algorithm that matches or
+          exceeds the strength of the signature key. If no constraints with
+          `ike:` prefix are configured any signature scheme
+          constraint (without `ike:` prefix) will also apply to
+          IKEv2 authentication, unless this is disabled in
+          `strongswan.conf`. To use RSASSA-PSS signatures use
+          `rsa/pss` instead of `pubkey` or
+          `rsa` as in e.g.
+          `ike:rsa/pss-sha256`. If `pubkey` or
+          `rsa` constraints are configured RSASSA-PSS signatures
+          will only be used if enabled in `strongswan.conf`(5).
       '';
 
       id = mkOptionalStrParam ''
@@ -519,7 +505,7 @@ in {
       peer. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
       Authentication or IKEv1 XAuth.
 
-      Each round is defined in a section having <literal>local</literal> as
+      Each round is defined in a section having `local` as
       prefix, and an optional unique suffix. To define a single authentication
       round, the suffix may be omitted.
     '';
@@ -540,7 +526,7 @@ in {
 
       eap_id = mkOptionalStrParam ''
         Identity to use as peer identity during EAP authentication. If set to
-        <literal>%any</literal> the EAP-Identity method will be used to ask the
+        `%any` the EAP-Identity method will be used to ask the
         client for an EAP identity.
       '';
 
@@ -559,7 +545,7 @@ in {
 
       certs = mkCommaSepListParam [] ''
         List of certificates to accept for authentication. The certificates may
-        use a relative path from the swanctl <literal>x509</literal> directory
+        use a relative path from the swanctl `x509` directory
         or an absolute path.
       '';
 
@@ -573,7 +559,7 @@ in {
         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
+        effect as specifying `cacerts` 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.
@@ -582,7 +568,7 @@ in {
       cacerts = mkCommaSepListParam [] ''
         List of CA certificates to accept for
         authentication. The certificates may use a relative path from the
-        swanctl <literal>x509ca</literal> directory or an absolute path.
+        swanctl `x509ca` directory or an absolute path.
       '';
 
       cacert = mkPostfixedAttrsOfParams certParams ''
@@ -594,57 +580,50 @@ in {
       pubkeys = mkCommaSepListParam [] ''
         List of raw public keys to accept for
         authentication. The public keys may use a relative path from the swanctl
-        <literal>pubkey</literal> directory or an absolute path.
+        `pubkey` directory or an absolute path.
       '';
 
       revocation = mkEnumParam ["strict" "ifuri" "relaxed"] "relaxed" ''
         Certificate revocation policy for CRL or OCSP revocation.
-        <itemizedlist>
-        <listitem><para>
-        A <literal>strict</literal> revocation policy fails if no revocation information is
-        available, i.e. the certificate is not known to be unrevoked.
-        </para></listitem>
-        <listitem><para>
-        <literal>ifuri</literal> fails only if a CRL/OCSP URI is available, but certificate
-        revocation checking fails, i.e. there should be revocation information
-        available, but it could not be obtained.
-        </para></listitem>
-        <listitem><para>
-        The default revocation policy <literal>relaxed</literal> fails only if a certificate is
-        revoked, i.e. it is explicitly known that it is bad.
-        </para></listitem>
-        </itemizedlist>
+
+        - A `strict` revocation policy fails if no revocation information is
+          available, i.e. the certificate is not known to be unrevoked.
+        - `ifuri` fails only if a CRL/OCSP URI is available, but certificate
+          revocation checking fails, i.e. there should be revocation information
+          available, but it could not be obtained.
+        - The default revocation policy `relaxed` fails only if a certificate is
+          revoked, i.e. it is explicitly known that it is bad.
       '';
 
       auth = mkStrParam "pubkey" ''
-        Authentication to expect from remote. See the <option>local</option>
-        section's <option>auth</option> keyword description about the details of
+        Authentication to expect from remote. See the {option}`local`
+        section's {option}`auth` keyword description about the details of
         supported mechanisms.
 
         Since 5.4.0, to require a trustchain public key strength for the remote
         side, specify the key type followed by the minimum strength in bits (for
-        example <literal>ecdsa-384</literal> or
-        <literal>rsa-2048-ecdsa-256</literal>). To limit the acceptable set of
+        example `ecdsa-384` or
+        `rsa-2048-ecdsa-256`). To limit the acceptable set of
         hashing algorithms for trustchain validation, append hash algorithms to
         pubkey or a key strength definition (for example
-        <literal>pubkey-sha256-sha512</literal>,
-        <literal>rsa-2048-sha256-sha384-sha512</literal> or
-        <literal>rsa-2048-sha256-ecdsa-256-sha256-sha384</literal>).
-        Unless disabled in <literal>strongswan.conf</literal>, or explicit IKEv2
+        `pubkey-sha256-sha512`,
+        `rsa-2048-sha256-sha384-sha512` or
+        `rsa-2048-sha256-ecdsa-256-sha256-sha384`).
+        Unless disabled in `strongswan.conf`, or explicit IKEv2
         signature constraints are configured (refer to the description of the
-        <option>local</option> section's <option>auth</option> keyword for
+        {option}`local` section's {option}`auth` keyword for
         details), such key types and hash algorithms are also applied as
         constraints against IKEv2 signature authentication schemes used by the
         remote side. To require RSASSA-PSS signatures use
-        <literal>rsa/pss</literal> instead of <literal>pubkey</literal> or
-        <literal>rsa</literal> as in e.g. <literal>rsa/pss-sha256</literal>. If
-        <literal>pubkey</literal> or <literal>rsa</literal> constraints are
+        `rsa/pss` instead of `pubkey` or
+        `rsa` as in e.g. `rsa/pss-sha256`. If
+        `pubkey` or `rsa` constraints are
         configured RSASSA-PSS signatures will only be accepted if enabled in
-        <literal>strongswan.conf</literal>(5).
+        `strongswan.conf`(5).
 
         To specify trust chain constraints for EAP-(T)TLS, append a colon to the
         EAP method, followed by the key type/size and hash algorithm as
-        discussed above (e.g. <literal>eap-tls:ecdsa-384-sha384</literal>).
+        discussed above (e.g. `eap-tls:ecdsa-384-sha384`).
       '';
 
     } ''
@@ -653,7 +632,7 @@ in {
       connection. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
       Authentication or IKEv1 XAuth.
 
-      Each round is defined in a section having <literal>remote</literal> as
+      Each round is defined in a section having `remote` as
       prefix, and an optional unique suffix. To define a single authentication
       round, the suffix may be omitted.
     '';
@@ -673,7 +652,7 @@ in {
         combinations in IKEv1.
 
         Algorithm keywords get separated using dashes. Multiple proposals may be
-        specified in a list. The special value <literal>default</literal> forms
+        specified in a list. The special value `default` forms
         a default proposal of supported algorithms considered safe, and is
         usually a good choice for interoperability. By default no AH proposals
         are included, instead ESP is proposed.
@@ -697,9 +676,9 @@ in {
         SA is established, but may later cause rekeying to fail.
 
         Extended Sequence Number support may be indicated with the
-        <literal>esn</literal> and <literal>noesn</literal> values, both may be
+        `esn` and `noesn` values, both may be
         included to indicate support for both modes. If omitted,
-        <literal>noesn</literal> is assumed.
+        `noesn` is assumed.
 
         In IKEv2, multiple algorithms of the same kind can be specified in a
         single proposal, from which one gets selected. In IKEv1, only one
@@ -708,7 +687,7 @@ in {
         combinations in IKEv1.
 
         Algorithm keywords get separated using dashes. Multiple proposals may be
-        specified as a list. The special value <literal>default</literal> forms
+        specified as a list. The special value `default` forms
         a default proposal of supported algorithms considered safe, and is
         usually a good choice for interoperability. If no algorithms are
         specified for AH nor ESP, the default set of algorithms for ESP is
@@ -726,7 +705,7 @@ in {
       local_ts = mkCommaSepListParam ["dynamic"] ''
         List of local traffic selectors to include in CHILD_SA. Each selector is
         a CIDR subnet definition, followed by an optional proto/port
-        selector. The special value <literal>dynamic</literal> may be used
+        selector. The special value `dynamic` may be used
         instead of a subnet definition, which gets replaced by the tunnel outer
         address or the virtual IP, if negotiated. This is the default.
 
@@ -735,7 +714,7 @@ in {
         name may be specified. After the optional protocol restriction, an
         optional port restriction may be specified, separated by a slash. The
         port restriction may be numeric, a getservent(3) service name, or the
-        special value <literal>opaque</literal> for RFC 4301 OPAQUE
+        special value `opaque` for RFC 4301 OPAQUE
         selectors. Port ranges may be specified as well, none of the kernel
         backends currently support port ranges, though.
 
@@ -752,31 +731,31 @@ in {
 
       remote_ts = mkCommaSepListParam ["dynamic"] ''
         List of remote selectors to include in CHILD_SA. See
-        <option>local_ts</option> for a description of the selector syntax.
+        {option}`local_ts` for a description of the selector syntax.
       '';
 
       rekey_time = mkDurationParam "1h" ''
         Time to schedule CHILD_SA rekeying. CHILD_SA rekeying refreshes key
         material, optionally using a Diffie-Hellman exchange if a group is
         specified in the proposal.  To avoid rekey collisions initiated by both
-        ends simultaneously, a value in the range of <option>rand_time</option>
+        ends simultaneously, a value in the range of {option}`rand_time`
         gets subtracted to form the effective soft lifetime.
 
         By default CHILD_SA rekeying is scheduled every hour, minus
-        <option>rand_time</option>.
+        {option}`rand_time`.
       '';
 
       life_time = mkOptionalDurationParam ''
         Maximum lifetime before CHILD_SA gets closed. Usually this hard lifetime
         is never reached, because the CHILD_SA gets rekeyed before. If that fails
         for whatever reason, this limit closes the CHILD_SA.  The default is 10%
-        more than the <option>rekey_time</option>.
+        more than the {option}`rekey_time`.
       '';
 
       rand_time = mkOptionalDurationParam ''
         Time range from which to choose a random value to subtract from
-        <option>rekey_time</option>. The default is the difference between
-        <option>life_time</option> and <option>rekey_time</option>.
+        {option}`rekey_time`. The default is the difference between
+        {option}`life_time` and {option}`rekey_time`.
       '';
 
       rekey_bytes = mkIntParam 0 ''
@@ -785,7 +764,7 @@ in {
         exchange if a group is specified in the proposal.
 
         To avoid rekey collisions initiated by both ends simultaneously, a value
-        in the range of <option>rand_bytes</option> gets subtracted to form the
+        in the range of {option}`rand_bytes` gets subtracted to form the
         effective soft volume limit.
 
         Volume based CHILD_SA rekeying is disabled by default.
@@ -795,13 +774,13 @@ in {
         Maximum bytes processed before CHILD_SA gets closed. Usually this hard
         volume limit is never reached, because the CHILD_SA gets rekeyed
         before. If that fails for whatever reason, this limit closes the
-        CHILD_SA.  The default is 10% more than <option>rekey_bytes</option>.
+        CHILD_SA.  The default is 10% more than {option}`rekey_bytes`.
       '';
 
       rand_bytes = mkOptionalIntParam ''
         Byte range from which to choose a random value to subtract from
-        <option>rekey_bytes</option>. The default is the difference between
-        <option>life_bytes</option> and <option>rekey_bytes</option>.
+        {option}`rekey_bytes`. The default is the difference between
+        {option}`life_bytes` and {option}`rekey_bytes`.
       '';
 
       rekey_packets = mkIntParam 0 ''
@@ -810,7 +789,7 @@ in {
         exchange if a group is specified in the proposal.
 
         To avoid rekey collisions initiated by both ends simultaneously, a value
-        in the range of <option>rand_packets</option> gets subtracted to form
+        in the range of {option}`rand_packets` gets subtracted to form
         the effective soft packet count limit.
 
         Packet count based CHILD_SA rekeying is disabled by default.
@@ -822,13 +801,13 @@ in {
         rekeyed before. If that fails for whatever reason, this limit closes the
         CHILD_SA.
 
-        The default is 10% more than <option>rekey_bytes</option>.
+        The default is 10% more than {option}`rekey_bytes`.
       '';
 
       rand_packets = mkOptionalIntParam ''
         Packet range from which to choose a random value to subtract from
-        <option>rekey_packets</option>. The default is the difference between
-        <option>life_packets</option> and <option>rekey_packets</option>.
+        {option}`rekey_packets`. The default is the difference between
+        {option}`life_packets` and {option}`rekey_packets`.
       '';
 
       updown = mkOptionalStrParam ''
@@ -836,7 +815,7 @@ in {
       '';
 
       hostaccess = mkYesNoParam no ''
-        Hostaccess variable to pass to <literal>updown</literal> script.
+        Hostaccess variable to pass to `updown` script.
       '';
 
       mode = mkEnumParam [ "tunnel"
@@ -847,33 +826,20 @@ in {
                            "drop"
                          ] "tunnel" ''
         IPsec Mode to establish CHILD_SA with.
-        <itemizedlist>
-        <listitem><para>
-        <literal>tunnel</literal> negotiates the CHILD_SA in IPsec Tunnel Mode,
-        </para></listitem>
-        <listitem><para>
-        whereas <literal>transport</literal> uses IPsec Transport Mode.
-        </para></listitem>
-        <listitem><para>
-        <literal>transport_proxy</literal> signifying the special Mobile IPv6
-        Transport Proxy Mode.
-        </para></listitem>
-        <listitem><para>
-        <literal>beet</literal> is the Bound End to End Tunnel mixture mode,
-        working with fixed inner addresses without the need to include them in
-        each packet.
-        </para></listitem>
-        <listitem><para>
-        Both <literal>transport</literal> and <literal>beet</literal> modes are
-        subject to mode negotiation; <literal>tunnel</literal> mode is
-        negotiated if the preferred mode is not available.
-        </para></listitem>
-        <listitem><para>
-        <literal>pass</literal> and <literal>drop</literal> are used to install
-        shunt policies which explicitly bypass the defined traffic from IPsec
-        processing or drop it, respectively.
-        </para></listitem>
-        </itemizedlist>
+
+        - `tunnel` negotiates the CHILD_SA in IPsec Tunnel Mode,
+        - whereas `transport` uses IPsec Transport Mode.
+        - `transport_proxy` signifying the special Mobile IPv6
+          Transport Proxy Mode.
+        - `beet` is the Bound End to End Tunnel mixture mode,
+          working with fixed inner addresses without the need to include them in
+          each packet.
+        - Both `transport` and `beet` modes are
+          subject to mode negotiation; `tunnel` mode is
+          negotiated if the preferred mode is not available.
+        - `pass` and `drop` are used to install
+          shunt policies which explicitly bypass the defined traffic from IPsec
+          processing or drop it, respectively.
       '';
 
       policies = mkYesNoParam yes ''
@@ -932,18 +898,18 @@ in {
         set. This allows installing duplicate policies and enables Netfilter
         rules to select specific SAs/policies for incoming traffic. Note that
         inbound marks are only set on policies, by default, unless
-        <option>mark_in_sa</option> is enabled. The special value
-        <literal>%unique</literal> sets a unique mark on each CHILD_SA instance,
-        beyond that the value <literal>%unique-dir</literal> assigns a different
+        {option}`mark_in_sa` is enabled. The special value
+        `%unique` sets a unique mark on each CHILD_SA instance,
+        beyond that the value `%unique-dir` assigns a different
         unique mark for each
 
         An additional mask may be appended to the mark, separated by
-        <literal>/</literal>. The default mask if omitted is
-        <literal>0xffffffff</literal>.
+        `/`. The default mask if omitted is
+        `0xffffffff`.
       '';
 
       mark_in_sa = mkYesNoParam no ''
-        Whether to set <option>mark_in</option> on the inbound SA. By default,
+        Whether to set {option}`mark_in` on the inbound SA. By default,
         the inbound mark is only set on the inbound policy. The tuple destination
         address, protocol and SPI is unique and the mark is not required to find
         the correct SA, allowing to mark traffic after decryption instead (where
@@ -957,13 +923,13 @@ in {
         require marks on each packet to match a policy/SA having that option
         set. This allows installing duplicate policies and enables Netfilter
         rules to select specific policies/SAs for outgoing traffic. The special
-        value <literal>%unique</literal> sets a unique mark on each CHILD_SA
-        instance, beyond that the value <literal>%unique-dir</literal> assigns a
+        value `%unique` sets a unique mark on each CHILD_SA
+        instance, beyond that the value `%unique-dir` assigns a
         different unique mark for each CHILD_SA direction (in/out).
 
         An additional mask may be appended to the mark, separated by
-        <literal>/</literal>. The default mask if omitted is
-        <literal>0xffffffff</literal>.
+        `/`. The default mask if omitted is
+        `0xffffffff`.
       '';
 
       set_mark_in = mkStrParam "0/0x00000000" ''
@@ -973,10 +939,10 @@ in {
         differently (e.g. via policy routing).
 
         An additional mask may be appended to the mark, separated by
-        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
-        special value <literal>%same</literal> uses the value (but not the mask)
-        from <option>mark_in</option> as mark value, which can be fixed,
-        <literal>%unique</literal> or <literal>%unique-dir</literal>.
+        `/`. The default mask if omitted is 0xffffffff. The
+        special value `%same` uses the value (but not the mask)
+        from {option}`mark_in` as mark value, which can be fixed,
+        `%unique` or `%unique-dir`.
 
         Setting marks in XFRM input requires Linux 4.19 or higher.
       '';
@@ -987,10 +953,10 @@ in {
         traffic (e.g. via policy routing).
 
         An additional mask may be appended to the mark, separated by
-        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
-        special value <literal>%same</literal> uses the value (but not the mask)
-        from <option>mark_out</option> as mark value, which can be fixed,
-        <literal>%unique_</literal> or <literal>%unique-dir</literal>.
+        `/`. The default mask if omitted is 0xffffffff. The
+        special value `%same` uses the value (but not the mask)
+        from {option}`mark_out` as mark value, which can be fixed,
+        `%unique_` or `%unique-dir`.
 
         Setting marks in XFRM output is supported since Linux 4.14. Setting a
         mask requires at least Linux 4.19.
@@ -999,18 +965,18 @@ in {
       if_id_in = mkStrParam "0" ''
         XFRM interface ID set on inbound policies/SA. This allows installing
         duplicate policies/SAs and associates them with an interface with the
-        same ID. The special value <literal>%unique</literal> sets a unique
+        same ID. The special value `%unique` sets a unique
         interface ID on each CHILD_SA instance, beyond that the value
-        <literal>%unique-dir</literal> assigns a different unique interface ID
+        `%unique-dir` assigns a different unique interface ID
         for each CHILD_SA direction (in/out).
       '';
 
       if_id_out = mkStrParam "0" ''
         XFRM interface ID set on outbound policies/SA. This allows installing
         duplicate policies/SAs and associates them with an interface with the
-        same ID. The special value <literal>%unique</literal> sets a unique
+        same ID. The special value `%unique` sets a unique
         interface ID on each CHILD_SA instance, beyond that the value
-        <literal>%unique-dir</literal> assigns a different unique interface ID
+        `%unique-dir` assigns a different unique interface ID
         for each CHILD_SA direction (in/out).
 
         The daemon will not install routes for CHILD_SAs that have this option set.
@@ -1020,24 +986,26 @@ in {
         Pads ESP packets with additional data to have a consistent ESP packet
         size for improved Traffic Flow Confidentiality. The padding defines the
         minimum size of all ESP packets sent.  The default value of
-        <literal>0</literal> disables TFC padding, the special value
-        <literal>mtu</literal> adds TFC padding to create a packet size equal to
+        `0` disables TFC padding, the special value
+        `mtu` adds TFC padding to create a packet size equal to
         the Path Maximum Transfer Unit.
       '';
 
       replay_window = mkIntParam 32 ''
         IPsec replay window to configure for this CHILD_SA. Larger values than
-        the default of <literal>32</literal> are supported using the Netlink
-        backend only, a value of <literal>0</literal> disables IPsec replay
+        the default of `32` are supported using the Netlink
+        backend only, a value of `0` disables IPsec replay
         protection.
       '';
 
-      hw_offload = mkEnumParam ["yes" "no" "auto"] "no" ''
+      hw_offload = mkEnumParam ["yes" "no" "auto" "crypto" "packet"] "no" ''
         Enable hardware offload for this CHILD_SA, if supported by the IPsec
-        implementation. The value <literal>yes</literal> enforces offloading
-        and the installation will fail if it's not supported by either kernel or
-        device. The value <literal>auto</literal> enables offloading, if it's
-        supported, but the installation does not fail otherwise.
+        implementation. The values `crypto` or `packet` enforce crypto or full
+        packet offloading and the installation will fail if the selected mode is not
+        supported by either kernel or device. On Linux, `packet` also offloads
+        policies, including trap policies. The value `auto` enables full packet
+        or crypto offloading, if either is supported, but the installation does not
+        fail otherwise.
       '';
 
       copy_df = mkYesNoParam yes ''
@@ -1055,55 +1023,42 @@ in {
       copy_dscp = mkEnumParam [ "out" "in" "yes" "no" ] "out" ''
         Whether to copy the DSCP (Differentiated Services Field Codepoint)
         header field to/from the outer IP header in tunnel mode. The value
-        <literal>out</literal> only copies the field from the inner to the outer
-        header, the value <literal>in</literal> does the opposite and only
+        `out` only copies the field from the inner to the outer
+        header, the value `in` does the opposite and only
         copies the field from the outer to the inner header when decapsulating,
-        the value <literal>yes</literal> copies the field in both directions,
-        and the value <literal>no</literal> disables copying the field
-        altogether. Setting this to <literal>yes</literal> or
-        <literal>in</literal> could allow an attacker to adversely affect other
+        the value `yes` copies the field in both directions,
+        and the value `no` disables copying the field
+        altogether. Setting this to `yes` or
+        `in` could allow an attacker to adversely affect other
         traffic at the receiver, which is why the default is
-        <literal>out</literal>. Controlling this behavior is not supported by
+        `out`. Controlling this behavior is not supported by
         all kernel interfaces.
       '';
 
       start_action = mkEnumParam ["none" "trap" "start"] "none" ''
         Action to perform after loading the configuration.
-        <itemizedlist>
-        <listitem><para>
-        The default of <literal>none</literal> loads the connection only, which
-        then can be manually initiated or used as a responder configuration.
-        </para></listitem>
-        <listitem><para>
-        The value <literal>trap</literal> installs a trap policy, which triggers
-        the tunnel as soon as matching traffic has been detected.
-        </para></listitem>
-        <listitem><para>
-        The value <literal>start</literal> initiates the connection actively.
-        </para></listitem>
-        </itemizedlist>
+
+        - The default of `none` loads the connection only, which
+          then can be manually initiated or used as a responder configuration.
+        - The value `trap` installs a trap policy, which triggers
+          the tunnel as soon as matching traffic has been detected.
+        - The value `start` initiates the connection actively.
+
         When unloading or replacing a CHILD_SA configuration having a
-        <option>start_action</option> different from <literal>none</literal>,
+        {option}`start_action` different from `none`,
         the inverse action is performed. Configurations with
-        <literal>start</literal> get closed, while such with
-        <literal>trap</literal> get uninstalled.
+        `start` get closed, while such with
+        `trap` get uninstalled.
       '';
 
       close_action = mkEnumParam ["none" "trap" "start"] "none" ''
         Action to perform after a CHILD_SA gets closed by the peer.
-        <itemizedlist>
-        <listitem><para>
-        The default of <literal>none</literal> does not take any action,
-        </para></listitem>
-        <listitem><para>
-        <literal>trap</literal> installs a trap policy for the CHILD_SA.
-        </para></listitem>
-        <listitem><para>
-        <literal>start</literal> tries to re-create the CHILD_SA.
-        </para></listitem>
-        </itemizedlist>
-
-        <option>close_action</option> does not provide any guarantee that the
+
+        - The default of `none` does not take any action,
+        - `trap` installs a trap policy for the CHILD_SA.
+        - `start` tries to re-create the CHILD_SA.
+
+        {option}`close_action` does not provide any guarantee that the
         CHILD_SA is kept alive. It acts on explicit close messages only, but not
         on negotiation failures. Use trap policies to reliably re-create failed
         CHILD_SAs.
@@ -1111,9 +1066,9 @@ in {
 
     } ''
       CHILD_SA configuration sub-section. Each connection definition may have
-      one or more sections in its <option>children</option> subsection. The
+      one or more sections in its {option}`children` subsection. The
       section name defines the name of the CHILD_SA configuration, which must be
-      unique within the connection (denoted &#60;child&#62; below).
+      unique within the connection (denoted \<child\> below).
     '';
   } ''
     Section defining IKE connection configurations, each in its own subsection
@@ -1130,13 +1085,13 @@ in {
 
       id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
         Identity the EAP/XAuth secret belongs to. Multiple unique identities may
-        be specified, each having an <literal>id</literal> prefix, if a secret
+        be specified, each having an `id` prefix, if a secret
         is shared between multiple users.
       '';
 
     } ''
       EAP secret section for a specific secret. Each EAP secret is defined in a
-      unique section having the <literal>eap</literal> prefix. EAP secrets are
+      unique section having the `eap` prefix. EAP secrets are
       used for XAuth authentication as well.
     '';
 
@@ -1160,7 +1115,7 @@ in {
       '';
     } ''
       NTLM secret section for a specific secret. Each NTLM secret is defined in
-      a unique section having the <literal>ntlm</literal> prefix. NTLM secrets
+      a unique section having the `ntlm` prefix. NTLM secrets
       may only be used for EAP-MSCHAPv2 authentication.
     '';
 
@@ -1173,30 +1128,30 @@ in {
 
       id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
         IKE identity the IKE preshared secret belongs to. Multiple unique
-        identities may be specified, each having an <literal>id</literal>
+        identities may be specified, each having an `id`
         prefix, if a secret is shared between multiple peers.
       '';
     } ''
       IKE preshared secret section for a specific secret. Each IKE PSK is
-      defined in a unique section having the <literal>ike</literal> prefix.
+      defined in a unique section having the `ike` prefix.
     '';
 
     ppk = mkPrefixedAttrsOfParams {
       secret = mkOptionalStrParam ''
         Value of the PPK. It may either be an ASCII string, a hex encoded string
-        if it has a <literal>0x</literal> prefix or a Base64 encoded string if
-        it has a <literal>0s</literal> prefix in its value. Should have at least
+        if it has a `0x` prefix or a Base64 encoded string if
+        it has a `0s` prefix in its value. Should have at least
         256 bits of entropy for 128-bit security.
       '';
 
       id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
         PPK identity the PPK belongs to. Multiple unique identities may be
-        specified, each having an <literal>id</literal> prefix, if a secret is
+        specified, each having an `id` prefix, if a secret is
         shared between multiple peers.
       '';
     } ''
       Postquantum Preshared Key (PPK) section for a specific secret. Each PPK is
-      defined in a unique section having the <literal>ppk</literal> prefix.
+      defined in a unique section having the `ppk` prefix.
     '';
 
     private = mkPrefixedAttrsOfParams {
@@ -1209,25 +1164,25 @@ in {
       '';
     } ''
       Private key decryption passphrase for a key in the
-      <literal>private</literal> folder.
+      `private` folder.
     '';
 
     rsa = mkPrefixedAttrsOfParams {
       file = mkOptionalStrParam ''
-        File name in the <literal>rsa</literal> folder for which this passphrase
+        File name in the `rsa` folder for which this passphrase
         should be used.
       '';
       secret = mkOptionalStrParam ''
         Value of decryption passphrase for RSA key.
       '';
     } ''
-      Private key decryption passphrase for a key in the <literal>rsa</literal>
+      Private key decryption passphrase for a key in the `rsa`
       folder.
     '';
 
     ecdsa = mkPrefixedAttrsOfParams {
       file = mkOptionalStrParam ''
-        File name in the <literal>ecdsa</literal> folder for which this
+        File name in the `ecdsa` folder for which this
         passphrase should be used.
       '';
       secret = mkOptionalStrParam ''
@@ -1235,12 +1190,12 @@ in {
       '';
     } ''
       Private key decryption passphrase for a key in the
-      <literal>ecdsa</literal> folder.
+      `ecdsa` folder.
     '';
 
     pkcs8 = mkPrefixedAttrsOfParams {
       file = mkOptionalStrParam ''
-        File name in the <literal>pkcs8</literal> folder for which this
+        File name in the `pkcs8` folder for which this
         passphrase should be used.
       '';
       secret = mkOptionalStrParam ''
@@ -1248,12 +1203,12 @@ in {
       '';
     } ''
       Private key decryption passphrase for a key in the
-      <literal>pkcs8</literal> folder.
+      `pkcs8` folder.
     '';
 
     pkcs12 = mkPrefixedAttrsOfParams {
       file = mkOptionalStrParam ''
-        File name in the <literal>pkcs12</literal> folder for which this
+        File name in the `pkcs12` folder for which this
         passphrase should be used.
       '';
       secret = mkOptionalStrParam ''
@@ -1261,7 +1216,7 @@ in {
       '';
     } ''
       PKCS#12 decryption passphrase for a container in the
-      <literal>pkcs12</literal> folder.
+      `pkcs12` folder.
     '';
 
     token = mkPrefixedAttrsOfParams {
@@ -1281,7 +1236,7 @@ in {
       pin = mkOptionalStrParam ''
         Optional PIN required to access the key on the token. If none is
         provided the user is prompted during an interactive
-        <literal>--load-creds</literal> call.
+        `--load-creds` call.
       '';
     } "Definition for a private key that's stored on a token/smartcard/TPM.";
 
@@ -1291,7 +1246,7 @@ in {
     addrs = mkOptionalStrParam ''
       Subnet or range defining addresses allocated in pool. Accepts a single
       CIDR subnet defining the pool to allocate addresses from or an address
-      range (&#60;from&#62;-&#60;to&#62;). Pools must be unique and non-overlapping.
+      range (\<from\>-\<to\>). Pools must be unique and non-overlapping.
     '';
 
     dns           = mkCommaSepListParam [] "Address or CIDR subnets";
@@ -1305,6 +1260,6 @@ in {
   } ''
     Section defining named pools. Named pools may be referenced by connections
     with the pools option to assign virtual IPs and other configuration
-    attributes. Each pool must have a unique name (denoted &#60;name&#62; below).
+    attributes. Each pool must have a unique name (denoted \<name\> below).
   '';
 }
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan.nix b/nixpkgs/nixos/modules/services/networking/strongswan.nix
index f1b0a3f0d3b8..e58526814d1a 100644
--- a/nixpkgs/nixos/modules/services/networking/strongswan.nix
+++ b/nixpkgs/nixos/modules/services/networking/strongswan.nix
@@ -4,7 +4,7 @@ let
 
   inherit (builtins) toFile;
   inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList
-                mkIf mkEnableOption mkOption types literalExpression;
+                mkIf mkEnableOption mkOption types literalExpression optionalString;
 
   cfg = config.services.strongswan;
 
@@ -34,8 +34,8 @@ let
 
   strongswanConf = {setup, connections, ca, secretsFile, managePlugins, enabledPlugins}: toFile "strongswan.conf" ''
     charon {
-      ${if managePlugins then "load_modular = no" else ""}
-      ${if managePlugins then ("load = " + (concatStringsSep " " enabledPlugins)) else ""}
+      ${optionalString managePlugins "load_modular = no"}
+      ${optionalString managePlugins ("load = " + (concatStringsSep " " enabledPlugins))}
       plugins {
         stroke {
           secrets_file = ${secretsFile}
@@ -51,7 +51,7 @@ let
 in
 {
   options.services.strongswan = {
-    enable = mkEnableOption "strongSwan";
+    enable = mkEnableOption (lib.mdDoc "strongSwan");
 
     secrets = mkOption {
       type = types.listOf types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/stubby.nix b/nixpkgs/nixos/modules/services/networking/stubby.nix
index f9d6869ad97c..183002ff72b9 100644
--- a/nixpkgs/nixos/modules/services/networking/stubby.nix
+++ b/nixpkgs/nixos/modules/services/networking/stubby.nix
@@ -7,7 +7,9 @@ let
   settingsFormat = pkgs.formats.yaml { };
   confFile = settingsFormat.generate "stubby.yml" cfg.settings;
 in {
-  imports = map (x:
+  imports = [
+    (mkRemovedOptionModule [ "stubby" "debugLogging" ] "Use services.stubby.logLevel = \"debug\"; instead.")
+  ] ++ map (x:
     (mkRemovedOptionModule [ "services" "stubby" x ]
       "Stubby configuration moved to services.stubby.settings.")) [
         "authenticationMode"
@@ -23,7 +25,7 @@ in {
   options = {
     services.stubby = {
 
-      enable = mkEnableOption "Stubby DNS resolver";
+      enable = mkEnableOption (lib.mdDoc "Stubby DNS resolver");
 
       settings = mkOption {
         type = types.attrsOf settingsFormat.type;
@@ -49,10 +51,22 @@ in {
         '';
       };
 
-      debugLogging = mkOption {
-        default = false;
-        type = types.bool;
-        description = lib.mdDoc "Enable or disable debug level logging.";
+      logLevel = let
+        logLevels = {
+          emerg = 0;
+          alert = 1;
+          crit = 2;
+          error = 3;
+          warning = 4;
+          notice = 5;
+          info = 6;
+          debug = 7;
+        };
+      in mkOption {
+        default = null;
+        type = types.nullOr (types.enum (attrNames logLevels ++ attrValues logLevels));
+        apply = v: if isString v then logLevels.${v} else v;
+        description = lib.mdDoc "Log verbosity (syslog keyword or level).";
       };
 
     };
@@ -80,7 +94,7 @@ in {
         Type = "notify";
         AmbientCapabilities = "CAP_NET_BIND_SERVICE";
         CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
-        ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile} ${optionalString cfg.debugLogging "-l"}";
+        ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile} ${optionalString (cfg.logLevel != null) "-v ${toString cfg.logLevel}"}";
         DynamicUser = true;
         CacheDirectory = "stubby";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/stunnel.nix b/nixpkgs/nixos/modules/services/networking/stunnel.nix
index 3bd0367a0bb1..996e9b225392 100644
--- a/nixpkgs/nixos/modules/services/networking/stunnel.nix
+++ b/nixpkgs/nixos/modules/services/networking/stunnel.nix
@@ -78,7 +78,7 @@ in
 
       servers = mkOption {
         description = lib.mdDoc ''
-          Define the server configuations.
+          Define the server configurations.
 
           See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`.
         '';
@@ -154,8 +154,8 @@ in
     environment.systemPackages = [ pkgs.stunnel ];
 
     environment.etc."stunnel.cfg".text = ''
-      ${ if cfg.user != null then "setuid = ${cfg.user}" else "" }
-      ${ if cfg.group != null then "setgid = ${cfg.group}" else "" }
+      ${ optionalString (cfg.user != null) "setuid = ${cfg.user}" }
+      ${ optionalString (cfg.group != null) "setgid = ${cfg.group}" }
 
       debug = ${cfg.logLevel}
 
diff --git a/nixpkgs/nixos/modules/services/networking/supplicant.nix b/nixpkgs/nixos/modules/services/networking/supplicant.nix
index 0a48e73932e8..13d84736e2c2 100644
--- a/nixpkgs/nixos/modules/services/networking/supplicant.nix
+++ b/nixpkgs/nixos/modules/services/networking/supplicant.nix
@@ -13,7 +13,7 @@ let
   serviceName = iface: "supplicant-${if (iface=="WLAN") then "wlan@" else (
                                      if (iface=="LAN") then "lan@" else (
                                      if (iface=="DBUS") then "dbus"
-                                     else (replaceChars [" "] ["-"] iface)))}";
+                                     else (replaceStrings [" "] ["-"] iface)))}";
 
   # TODO: Use proper privilege separation for wpa_supplicant
   supplicantService = iface: suppl:
@@ -27,7 +27,7 @@ let
       driverArg = optionalString (suppl.driver != null) "-D${suppl.driver}";
       bridgeArg = optionalString (suppl.bridge!="") "-b${suppl.bridge}";
       confFileArg = optionalString (suppl.configFile.path!=null) "-c${suppl.configFile.path}";
-      extraConfFile = pkgs.writeText "supplicant-extra-conf-${replaceChars [" "] ["-"] iface}" ''
+      extraConfFile = pkgs.writeText "supplicant-extra-conf-${replaceStrings [" "] ["-"] iface}" ''
         ${optionalString suppl.userControlled.enable "ctrl_interface=DIR=${suppl.userControlled.socketDir} GROUP=${suppl.userControlled.group}"}
         ${optionalString suppl.configFile.writable "update_config=1"}
         ${suppl.extraConf}
@@ -223,7 +223,7 @@ in
         text = ''
           ${flip (concatMapStringsSep "\n") (filter (n: n!="WLAN" && n!="LAN" && n!="DBUS") (attrNames cfg)) (iface:
             flip (concatMapStringsSep "\n") (splitString " " iface) (i: ''
-              ACTION=="add", SUBSYSTEM=="net", ENV{INTERFACE}=="${i}", TAG+="systemd", ENV{SYSTEMD_WANTS}+="supplicant-${replaceChars [" "] ["-"] iface}.service", TAG+="SUPPLICANT_ASSIGNED"''))}
+              ACTION=="add", SUBSYSTEM=="net", ENV{INTERFACE}=="${i}", TAG+="systemd", ENV{SYSTEMD_WANTS}+="supplicant-${replaceStrings [" "] ["-"] iface}.service", TAG+="SUPPLICANT_ASSIGNED"''))}
 
           ${optionalString (hasAttr "WLAN" cfg) ''
             ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="/run/current-system/systemd/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-wlan@$result.service"
diff --git a/nixpkgs/nixos/modules/services/networking/supybot.nix b/nixpkgs/nixos/modules/services/networking/supybot.nix
index df7d92189a77..22ba015cc55d 100644
--- a/nixpkgs/nixos/modules/services/networking/supybot.nix
+++ b/nixpkgs/nixos/modules/services/networking/supybot.nix
@@ -67,10 +67,10 @@ in
         type = types.functionTo (types.listOf types.package);
         default = p: [];
         defaultText = literalExpression "p: []";
-        description = ''
+        description = lib.mdDoc ''
           Extra Python packages available to supybot plugins. The
           value must be a function which receives the attrset defined
-          in <varname>python3Packages</varname> as the sole argument.
+          in {var}`python3Packages` as the sole argument.
         '';
         example = literalExpression "p: [ p.lxml p.requests ]";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/syncplay.nix b/nixpkgs/nixos/modules/services/networking/syncplay.nix
index 726f65671072..0a66d93bf153 100644
--- a/nixpkgs/nixos/modules/services/networking/syncplay.nix
+++ b/nixpkgs/nixos/modules/services/networking/syncplay.nix
@@ -8,7 +8,8 @@ let
   cmdArgs =
     [ "--port" cfg.port ]
     ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ]
-    ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ];
+    ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ]
+    ++ cfg.extraArgs;
 
 in
 {
@@ -33,7 +34,22 @@ in
         default = null;
         description = lib.mdDoc ''
           Salt to allow room operator passwords generated by this server
-          instance to still work when the server is restarted.
+          instance to still work when the server is restarted.  The salt will be
+          readable in the nix store and the processlist.  If this is not
+          intended use `saltFile` instead.  Mutually exclusive with
+          <option>services.syncplay.saltFile</option>.
+        '';
+      };
+
+      saltFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          Path to the file that contains the server salt.  This allows room
+          operator passwords generated by this server instance to still work
+          when the server is restarted.  `null`, the server doesn't load the
+          salt from a file.  Mutually exclusive with
+          <option>services.syncplay.salt</option>.
         '';
       };
 
@@ -46,6 +62,14 @@ in
         '';
       };
 
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = lib.mdDoc ''
+          Additional arguments to be passed to the service.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nobody";
@@ -74,21 +98,31 @@ in
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.salt == null || cfg.saltFile == null;
+        message = "services.syncplay.salt and services.syncplay.saltFile are mutually exclusive.";
+      }
+    ];
     systemd.services.syncplay = {
       description = "Syncplay Service";
-      wantedBy    = [ "multi-user.target" ];
-      after       = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
 
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
-        LoadCredential = lib.mkIf (cfg.passwordFile != null) "password:${cfg.passwordFile}";
+        LoadCredential = lib.optional (cfg.passwordFile != null) "password:${cfg.passwordFile}"
+          ++ lib.optional (cfg.saltFile != null) "salt:${cfg.saltFile}";
       };
 
       script = ''
         ${lib.optionalString (cfg.passwordFile != null) ''
           export SYNCPLAY_PASSWORD=$(cat "''${CREDENTIALS_DIRECTORY}/password")
         ''}
+        ${lib.optionalString (cfg.saltFile != null) ''
+          export SYNCPLAY_SALT=$(cat "''${CREDENTIALS_DIRECTORY}/salt")
+        ''}
         exec ${pkgs.syncplay-nogui}/bin/syncplay-server ${escapeShellArgs cmdArgs}
       '';
     };
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix b/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
index e92557d65455..64c4e731b982 100644
--- a/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
+++ b/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
@@ -22,7 +22,7 @@ in {
   ###### interface
 
   options.services.syncthing.relay = {
-    enable = mkEnableOption "Syncthing relay service";
+    enable = mkEnableOption (lib.mdDoc "Syncthing relay service");
 
     listenAddress = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing.nix b/nixpkgs/nixos/modules/services/networking/syncthing.nix
index 373fd03223d6..3d41fe4013ea 100644
--- a/nixpkgs/nixos/modules/services/networking/syncthing.nix
+++ b/nixpkgs/nixos/modules/services/networking/syncthing.nix
@@ -55,8 +55,8 @@ let
 
     # generate the new config by merging with the NixOS config options
     new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * {
-        "devices": (${builtins.toJSON devices}${optionalString (! cfg.overrideDevices) " + .devices"}),
-        "folders": (${builtins.toJSON folders}${optionalString (! cfg.overrideFolders) " + .folders"})
+        "devices": (${builtins.toJSON devices}${optionalString (cfg.devices == {} || ! cfg.overrideDevices) " + .devices"}),
+        "folders": (${builtins.toJSON folders}${optionalString (cfg.folders == {} || ! cfg.overrideFolders) " + .folders"})
     } * ${builtins.toJSON cfg.extraOptions}')
 
     # send the new config
@@ -74,7 +74,7 @@ in {
     services.syncthing = {
 
       enable = mkEnableOption
-        "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync";
+        (lib.mdDoc "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync");
 
       cert = mkOption {
         type = types.nullOr types.str;
@@ -212,10 +212,18 @@ in {
             };
 
             path = mkOption {
-              type = types.str;
+              # TODO for release 23.05: allow relative paths again and set
+              # working directory to cfg.dataDir
+              type = types.str // {
+                check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/");
+                description = types.str.description + " starting with / or ~/";
+              };
               default = name;
               description = lib.mdDoc ''
                 The path to the folder which should be shared.
+                Only absolute paths (starting with `/`) and paths relative to
+                the [user](#opt-services.syncthing.user)'s home directory
+                (starting with `~/`) are allowed.
               '';
             };
 
@@ -268,10 +276,10 @@ in {
                   {
                     versioning = {
                       type = "staggered";
+                      fsPath = "/syncthing/backup";
                       params = {
                         cleanInterval = "3600";
                         maxAge = "31536000";
-                        versionsPath = "/syncthing/backup";
                       };
                     };
                   }
@@ -296,6 +304,14 @@ in {
                       See <https://docs.syncthing.net/users/versioning.html>.
                     '';
                   };
+                  fsPath = mkOption {
+                    default = "";
+                    type = either str path;
+                    description = mdDoc ''
+                      Path to the versioning folder.
+                      See <https://docs.syncthing.net/users/versioning.html>.
+                    '';
+                  };
                   params = mkOption {
                     type = attrsOf (either str path);
                     description = mdDoc ''
@@ -317,11 +333,12 @@ in {
             };
 
             type = mkOption {
-              type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+              type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ];
               default = "sendreceive";
               description = lib.mdDoc ''
                 Whether to only send changes for this folder, only receive them
-                or both.
+                or both. `receiveencrypted` can be used for untrusted devices. See
+                <https://docs.syncthing.net/users/untrusted.html> for reference.
               '';
             };
 
@@ -367,6 +384,29 @@ in {
         description = mdDoc ''
           Extra configuration options for Syncthing.
           See <https://docs.syncthing.net/users/config.html>.
+          Note that this attribute set does not exactly match the documented
+          xml format. Instead, this is the format of the json rest api. There
+          are slight differences. For example, this xml:
+          ```xml
+          <options>
+            <listenAddress>default</listenAddress>
+            <minHomeDiskFree unit="%">1</minHomeDiskFree>
+          </options>
+          ```
+          corresponds to the json:
+          ```json
+          {
+            options: {
+              listenAddresses = [
+                "default"
+              ];
+              minHomeDiskFree = {
+                unit = "%";
+                value = 1;
+              };
+            };
+          }
+          ```
         '';
         example = {
           options.localAnnounceEnabled = false;
@@ -396,7 +436,8 @@ in {
         example = "yourUser";
         description = mdDoc ''
           The user to run Syncthing as.
-          By default, a user named `${defaultUser}` will be created.
+          By default, a user named `${defaultUser}` will be created whose home
+          directory is [dataDir](#opt-services.syncthing.dataDir).
         '';
       };
 
@@ -520,6 +561,8 @@ in {
     };
 
     systemd.services = {
+      # upstream reference:
+      # https://github.com/syncthing/syncthing/blob/main/etc/linux-systemd/system/syncthing%40.service
       syncthing = mkIf cfg.systemService {
         description = "Syncthing service";
         after = [ "network.target" ];
@@ -531,7 +574,7 @@ in {
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
           Restart = "on-failure";
-          SuccessExitStatus = "2 3 4";
+          SuccessExitStatus = "3 4";
           RestartForceExitStatus="3 4";
           User = cfg.user;
           Group = cfg.group;
diff --git a/nixpkgs/nixos/modules/services/networking/tailscale.nix b/nixpkgs/nixos/modules/services/networking/tailscale.nix
index 12ac6d6da5a8..c81cf293ab6d 100644
--- a/nixpkgs/nixos/modules/services/networking/tailscale.nix
+++ b/nixpkgs/nixos/modules/services/networking/tailscale.nix
@@ -4,15 +4,12 @@ with lib;
 
 let
   cfg = config.services.tailscale;
-  firewallOn = config.networking.firewall.enable;
-  rpfMode = config.networking.firewall.checkReversePath;
   isNetworkd = config.networking.useNetworkd;
-  rpfIsStrict = rpfMode == true || rpfMode == "strict";
 in {
   meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 ];
 
   options.services.tailscale = {
-    enable = mkEnableOption "Tailscale client daemon";
+    enable = mkEnableOption (lib.mdDoc "Tailscale client daemon");
 
     port = mkOption {
       type = types.port;
@@ -38,10 +35,23 @@ in {
       defaultText = literalExpression "pkgs.tailscale";
       description = lib.mdDoc "The package to use for tailscale";
     };
+
+    useRoutingFeatures = mkOption {
+      type = types.enum [ "none" "client" "server" "both" ];
+      default = "none";
+      example = "server";
+      description = lib.mdDoc ''
+        Enables settings required for Tailscale's routing features like subnet routers and exit nodes.
+
+        To use these these features, you will still need to call `sudo tailscale up` with the relevant flags like `--advertise-exit-node` and `--exit-node`.
+
+        When set to `client` or `both`, reverse path filtering will be set to loose instead of strict.
+        When set to `server` or `both`, IP forwarding will be enabled.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
-    warnings = optional (firewallOn && rpfIsStrict) "Strict reverse path filtering breaks Tailscale exit node use and some subnet routing setups. Consider setting `networking.firewall.checkReversePath` = 'loose'";
     environment.systemPackages = [ cfg.package ]; # for the CLI
     systemd.packages = [ cfg.package ];
     systemd.services.tailscaled = {
@@ -71,6 +81,13 @@ in {
       stopIfChanged = false;
     };
 
+    boot.kernel.sysctl = mkIf (cfg.useRoutingFeatures == "server" || cfg.useRoutingFeatures == "both") {
+      "net.ipv4.conf.all.forwarding" = mkOverride 97 true;
+      "net.ipv6.conf.all.forwarding" = mkOverride 97 true;
+    };
+
+    networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose";
+
     networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ];
 
     systemd.network.networks."50-tailscale" = mkIf isNetworkd {
diff --git a/nixpkgs/nixos/modules/services/networking/tayga.nix b/nixpkgs/nixos/modules/services/networking/tayga.nix
new file mode 100644
index 000000000000..299ae2777f7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tayga.nix
@@ -0,0 +1,195 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.tayga;
+
+  # Converts an address set to a string
+  strAddr = addr: "${addr.address}/${toString addr.prefixLength}";
+
+  configFile = pkgs.writeText "tayga.conf" ''
+    tun-device ${cfg.tunDevice}
+
+    ipv4-addr ${cfg.ipv4.address}
+    ${optionalString (cfg.ipv6.address != null) "ipv6-addr ${cfg.ipv6.address}"}
+
+    prefix ${strAddr cfg.ipv6.pool}
+    dynamic-pool ${strAddr cfg.ipv4.pool}
+    data-dir ${cfg.dataDir}
+  '';
+
+  addrOpts = v:
+    assert v == 4 || v == 6;
+    {
+      options = {
+        address = mkOption {
+          type = types.str;
+          description = lib.mdDoc "IPv${toString v} address.";
+        };
+
+        prefixLength = mkOption {
+          type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
+          description = lib.mdDoc ''
+            Subnet mask of the interface, specified as the number of
+            bits in the prefix ("${if v == 4 then "24" else "64"}").
+          '';
+        };
+      };
+    };
+
+  versionOpts = v: {
+    options = {
+      router = {
+        address = mkOption {
+          type = types.str;
+          description = lib.mdDoc "The IPv${toString v} address of the router.";
+        };
+      };
+
+      address = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = lib.mdDoc "The source IPv${toString v} address of the TAYGA server.";
+      };
+
+      pool = mkOption {
+        type = with types; nullOr (submodule (addrOpts v));
+        description = lib.mdDoc "The pool of IPv${toString v} addresses which are used for translation.";
+      };
+    };
+  };
+in
+{
+  options = {
+    services.tayga = {
+      enable = mkEnableOption (lib.mdDoc "Tayga");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.tayga;
+        defaultText = lib.literalMD "pkgs.tayga";
+        description = lib.mdDoc "This option specifies the TAYGA package to use.";
+      };
+
+      ipv4 = mkOption {
+        type = types.submodule (versionOpts 4);
+        description = lib.mdDoc "IPv4-specific configuration.";
+        example = literalExpression ''
+          {
+            address = "192.0.2.0";
+            router = {
+              address = "192.0.2.1";
+            };
+            pool = {
+              address = "192.0.2.1";
+              prefixLength = 24;
+            };
+          }
+        '';
+      };
+
+      ipv6 = mkOption {
+        type = types.submodule (versionOpts 6);
+        description = lib.mdDoc "IPv6-specific configuration.";
+        example = literalExpression ''
+          {
+            address = "2001:db8::1";
+            router = {
+              address = "64:ff9b::1";
+            };
+            pool = {
+              address = "64:ff9b::";
+              prefixLength = 96;
+            };
+          }
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/tayga";
+        description = lib.mdDoc "Directory for persistent data";
+      };
+
+      tunDevice = mkOption {
+        type = types.str;
+        default = "nat64";
+        description = lib.mdDoc "Name of the nat64 tun device";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.interfaces."${cfg.tunDevice}" = {
+      virtual = true;
+      virtualType = "tun";
+      virtualOwner = mkIf config.networking.useNetworkd "";
+      ipv4 = {
+        addresses = [
+          { address = cfg.ipv4.router.address; prefixLength = 32; }
+        ];
+        routes = [
+          cfg.ipv4.pool
+        ];
+      };
+      ipv6 = {
+        addresses = [
+          { address = cfg.ipv6.router.address; prefixLength = 128; }
+        ];
+        routes = [
+          cfg.ipv6.pool
+        ];
+      };
+    };
+
+    systemd.services.tayga = {
+      description = "Stateless NAT64 implementation";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/tayga -d --nodetach --config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        Restart = "always";
+
+        # Hardening Score:
+        #  - nixos-scripts: 2.1
+        #  - systemd-networkd: 1.6
+        ProtectHome = true;
+        SystemCallFilter = [
+          "@network-io"
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        ProtectKernelLogs = true;
+        AmbientCapabilities = [
+          "CAP_NET_ADMIN"
+        ];
+        CapabilityBoundingSet = "";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        StateDirectory = "tayga";
+        DynamicUser = mkIf config.networking.useNetworkd true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictNamespaces = true;
+        NoNewPrivileges = true;
+        ProtectControlGroups = true;
+        SystemCallArchitectures = "native";
+        PrivateTmp = true;
+        LockPersonality = true;
+        ProtectSystem = true;
+        PrivateUsers = true;
+        ProtectProc = "invisible";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/teamspeak3.nix b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
index 3be9fb31ec79..f09ef1a959ed 100644
--- a/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
@@ -152,6 +152,7 @@ in
         WorkingDirectory = cfg.dataDir;
         User = user;
         Group = group;
+        Restart = "on-failure";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/networking/tedicross.nix b/nixpkgs/nixos/modules/services/networking/tedicross.nix
index 3d7f298efacd..cee7e11f4fb1 100644
--- a/nixpkgs/nixos/modules/services/networking/tedicross.nix
+++ b/nixpkgs/nixos/modules/services/networking/tedicross.nix
@@ -13,7 +13,7 @@ let
 in {
   options = {
     services.tedicross = {
-      enable = mkEnableOption "the TediCross Telegram-Discord bridge service";
+      enable = mkEnableOption (lib.mdDoc "the TediCross Telegram-Discord bridge service");
 
       config = mkOption {
         type = types.attrs;
diff --git a/nixpkgs/nixos/modules/services/networking/teleport.nix b/nixpkgs/nixos/modules/services/networking/teleport.nix
index d03648df34b0..399af711c0e1 100644
--- a/nixpkgs/nixos/modules/services/networking/teleport.nix
+++ b/nixpkgs/nixos/modules/services/networking/teleport.nix
@@ -9,7 +9,15 @@ in
 {
   options = {
     services.teleport = with lib.types; {
-      enable = mkEnableOption "the Teleport service";
+      enable = mkEnableOption (lib.mdDoc "the Teleport service");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.teleport;
+        defaultText = lib.literalMD "pkgs.teleport";
+        example = lib.literalMD "pkgs.teleport_11";
+        description = lib.mdDoc "The teleport package to use";
+      };
 
       settings = mkOption {
         type = settingsYaml.type;
@@ -41,7 +49,7 @@ in
         '';
       };
 
-      insecure.enable = mkEnableOption ''
+      insecure.enable = mkEnableOption (lib.mdDoc ''
         starting teleport in insecure mode.
 
         This is dangerous!
@@ -49,14 +57,14 @@ in
         Proceed with caution!
 
         Teleport starts with disabled certificate validation on Proxy Service, validation still occurs on Auth Service
-      '';
+      '');
 
       diag = {
-        enable = mkEnableOption ''
+        enable = mkEnableOption (lib.mdDoc ''
           endpoints for monitoring purposes.
 
-          See <link xlink:href="https://goteleport.com/docs/setup/admin/troubleshooting/#troubleshooting/"/>
-        '';
+          See <https://goteleport.com/docs/setup/admin/troubleshooting/#troubleshooting/>
+        '');
 
         addr = mkOption {
           type = str;
@@ -65,7 +73,7 @@ in
         };
 
         port = mkOption {
-          type = int;
+          type = port;
           default = 3000;
           description = lib.mdDoc "Metrics and diagnostics port.";
         };
@@ -74,14 +82,14 @@ in
   };
 
   config = mkIf config.services.teleport.enable {
-    environment.systemPackages = [ pkgs.teleport ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.teleport = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.teleport}/bin/teleport start \
+          ${cfg.package}/bin/teleport start \
             ${optionalString cfg.insecure.enable "--insecure"} \
             ${optionalString cfg.diag.enable "--diag-addr=${cfg.diag.addr}:${toString cfg.diag.port}"} \
             ${optionalString (cfg.settings != { }) "--config=${settingsYaml.generate "teleport.yaml" cfg.settings}"}
diff --git a/nixpkgs/nixos/modules/services/networking/tetrd.nix b/nixpkgs/nixos/modules/services/networking/tetrd.nix
index 0801ce129246..6284a5b1fb1b 100644
--- a/nixpkgs/nixos/modules/services/networking/tetrd.nix
+++ b/nixpkgs/nixos/modules/services/networking/tetrd.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 {
-  options.services.tetrd.enable = lib.mkEnableOption "tetrd";
+  options.services.tetrd.enable = lib.mkEnableOption (lib.mdDoc "tetrd");
 
   config = lib.mkIf config.services.tetrd.enable {
     environment = {
diff --git a/nixpkgs/nixos/modules/services/networking/thelounge.nix b/nixpkgs/nixos/modules/services/networking/thelounge.nix
index 8db541d8072b..2c4c32bc7cf3 100644
--- a/nixpkgs/nixos/modules/services/networking/thelounge.nix
+++ b/nixpkgs/nixos/modules/services/networking/thelounge.nix
@@ -23,7 +23,9 @@ in
   imports = [ (mkRemovedOptionModule [ "services" "thelounge" "private" ] "The option was renamed to `services.thelounge.public` to follow upstream changes.") ];
 
   options.services.thelounge = {
-    enable = mkEnableOption "The Lounge web IRC client";
+    enable = mkEnableOption (lib.mdDoc "The Lounge web IRC client");
+
+    package = mkPackageOptionMD pkgs "thelounge" { };
 
     public = mkOption {
       type = types.bool;
@@ -93,11 +95,11 @@ in
       serviceConfig = {
         User = "thelounge";
         StateDirectory = baseNameOf dataDir;
-        ExecStart = "${pkgs.thelounge}/bin/thelounge start";
+        ExecStart = "${getExe cfg.package} start";
       };
     };
 
-    environment.systemPackages = [ pkgs.thelounge ];
+    environment.systemPackages = [ cfg.package ];
   };
 
   meta = {
diff --git a/nixpkgs/nixos/modules/services/networking/tinc.nix b/nixpkgs/nixos/modules/services/networking/tinc.nix
index 1f93d82f96eb..7db83e6a584b 100644
--- a/nixpkgs/nixos/modules/services/networking/tinc.nix
+++ b/nixpkgs/nixos/modules/services/networking/tinc.nix
@@ -349,91 +349,94 @@ in
 
   ###### implementation
 
-  config = mkIf (cfg.networks != { }) {
-
-    environment.etc = foldr (a: b: a // b) { }
-      (flip mapAttrsToList cfg.networks (network: data:
-        flip mapAttrs' data.hosts (host: text: nameValuePair
-          ("tinc/${network}/hosts/${host}")
-          ({ mode = "0644"; user = "tinc.${network}"; inherit text; })
-        ) // {
-          "tinc/${network}/tinc.conf" = {
-            mode = "0444";
-            text = ''
-              ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)}
-              ${data.extraConfig}
-            '';
+  config = mkIf (cfg.networks != { }) (
+    let
+      etcConfig = foldr (a: b: a // b) { }
+        (flip mapAttrsToList cfg.networks (network: data:
+          flip mapAttrs' data.hosts (host: text: nameValuePair
+            ("tinc/${network}/hosts/${host}")
+            ({ mode = "0644"; user = "tinc.${network}"; inherit text; })
+          ) // {
+            "tinc/${network}/tinc.conf" = {
+              mode = "0444";
+              text = ''
+                ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)}
+                ${data.extraConfig}
+              '';
+            };
+          }
+        ));
+    in {
+      environment.etc = etcConfig;
+
+      systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
+        ("tinc.${network}")
+        (let version = getVersion data.package; in {
+          description = "Tinc Daemon - ${network}";
+          wantedBy = [ "multi-user.target" ];
+          path = [ data.package ];
+          reloadTriggers = mkIf (versionAtLeast version "1.1pre") [ (builtins.toJSON etcConfig) ];
+          restartTriggers = mkIf (versionOlder version "1.1pre") [ (builtins.toJSON etcConfig) ];
+          serviceConfig = {
+            Type = "simple";
+            Restart = "always";
+            RestartSec = "3";
+            ExecReload = mkIf (versionAtLeast version "1.1pre") "${data.package}/bin/tinc -n ${network} reload";
+            ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
           };
-        }
-      ));
-
-    systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
-      ("tinc.${network}")
-      ({
-        description = "Tinc Daemon - ${network}";
-        wantedBy = [ "multi-user.target" ];
-        path = [ data.package ];
-        restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ];
-        serviceConfig = {
-          Type = "simple";
-          Restart = "always";
-          RestartSec = "3";
-          ExecReload = mkIf (versionAtLeast (getVersion data.package) "1.1pre") "${data.package}/bin/tinc -n ${network} reload";
-          ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
+          preStart = ''
+            mkdir -p /etc/tinc/${network}/hosts
+            chown tinc.${network} /etc/tinc/${network}/hosts
+            mkdir -p /etc/tinc/${network}/invitations
+            chown tinc.${network} /etc/tinc/${network}/invitations
+
+            # Determine how we should generate our keys
+            if type tinc >/dev/null 2>&1; then
+              # Tinc 1.1+ uses the tinc helper application for key generation
+            ${if data.ed25519PrivateKeyFile != null then "  # ed25519 Keyfile managed by nix" else ''
+              # Prefer ED25519 keys (only in 1.1+)
+              [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
+            ''}
+            ${if data.rsaPrivateKeyFile != null then "  # RSA Keyfile managed by nix" else ''
+              [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
+            ''}
+              # In case there isn't anything to do
+              true
+            else
+              # Tinc 1.0 uses the tincd application
+              [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
+            fi
+          '';
+        })
+      );
+
+      environment.systemPackages = let
+        cli-wrappers = pkgs.stdenv.mkDerivation {
+          name = "tinc-cli-wrappers";
+          nativeBuildInputs = [ pkgs.makeWrapper ];
+          buildCommand = ''
+            mkdir -p $out/bin
+            ${concatStringsSep "\n" (mapAttrsToList (network: data:
+              optionalString (versionAtLeast data.package.version "1.1pre") ''
+                makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
+                  --add-flags "--pidfile=/run/tinc.${network}.pid" \
+                  --add-flags "--config=/etc/tinc/${network}"
+              '') cfg.networks)}
+          '';
         };
-        preStart = ''
-          mkdir -p /etc/tinc/${network}/hosts
-          chown tinc.${network} /etc/tinc/${network}/hosts
-          mkdir -p /etc/tinc/${network}/invitations
-          chown tinc.${network} /etc/tinc/${network}/invitations
-
-          # Determine how we should generate our keys
-          if type tinc >/dev/null 2>&1; then
-            # Tinc 1.1+ uses the tinc helper application for key generation
-          ${if data.ed25519PrivateKeyFile != null then "  # ed25519 Keyfile managed by nix" else ''
-            # Prefer ED25519 keys (only in 1.1+)
-            [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
-          ''}
-          ${if data.rsaPrivateKeyFile != null then "  # RSA Keyfile managed by nix" else ''
-            [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
-          ''}
-            # In case there isn't anything to do
-            true
-          else
-            # Tinc 1.0 uses the tincd application
-            [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
-          fi
-        '';
-      })
-    );
-
-    environment.systemPackages = let
-      cli-wrappers = pkgs.stdenv.mkDerivation {
-        name = "tinc-cli-wrappers";
-        buildInputs = [ pkgs.makeWrapper ];
-        buildCommand = ''
-          mkdir -p $out/bin
-          ${concatStringsSep "\n" (mapAttrsToList (network: data:
-            optionalString (versionAtLeast data.package.version "1.1pre") ''
-              makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
-                --add-flags "--pidfile=/run/tinc.${network}.pid" \
-                --add-flags "--config=/etc/tinc/${network}"
-            '') cfg.networks)}
-        '';
-      };
-    in [ cli-wrappers ];
-
-    users.users = flip mapAttrs' cfg.networks (network: _:
-      nameValuePair ("tinc.${network}") ({
-        description = "Tinc daemon user for ${network}";
-        isSystemUser = true;
-        group = "tinc.${network}";
-      })
-    );
-    users.groups = flip mapAttrs' cfg.networks (network: _:
-      nameValuePair "tinc.${network}" {}
-    );
-  };
+      in [ cli-wrappers ];
+
+      users.users = flip mapAttrs' cfg.networks (network: _:
+        nameValuePair ("tinc.${network}") ({
+          description = "Tinc daemon user for ${network}";
+          isSystemUser = true;
+          group = "tinc.${network}";
+        })
+      );
+      users.groups = flip mapAttrs' cfg.networks (network: _:
+        nameValuePair "tinc.${network}" {}
+      );
+    });
 
   meta.maintainers = with maintainers; [ minijackson mic92 ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/tmate-ssh-server.nix b/nixpkgs/nixos/modules/services/networking/tmate-ssh-server.nix
new file mode 100644
index 000000000000..ff4ce0773309
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tmate-ssh-server.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.tmate-ssh-server;
+
+  defaultKeysDir = "/etc/tmate-ssh-server-keys";
+  edKey = "${defaultKeysDir}/ssh_host_ed25519_key";
+  rsaKey = "${defaultKeysDir}/ssh_host_rsa_key";
+
+  keysDir =
+    if cfg.keysDir == null
+    then defaultKeysDir
+    else cfg.keysDir;
+
+  domain = config.networking.domain;
+in
+{
+  options.services.tmate-ssh-server = {
+    enable = mkEnableOption (mdDoc "tmate ssh server");
+
+    package = mkOption {
+      type = types.package;
+      description = mdDoc "The package containing tmate-ssh-server";
+      defaultText = literalExpression "pkgs.tmate-ssh-server";
+      default = pkgs.tmate-ssh-server;
+    };
+
+    host = mkOption {
+      type = types.str;
+      description = mdDoc "External host name";
+      defaultText = lib.literalExpression "config.networking.domain or config.networking.hostName";
+      default =
+        if domain == null then
+          config.networking.hostName
+        else
+          domain;
+    };
+
+    port = mkOption {
+      type = types.port;
+      description = mdDoc "Listen port for the ssh server";
+      default = 2222;
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "Whether to automatically open the specified ports in the firewall.";
+    };
+
+    advertisedPort = mkOption {
+      type = types.port;
+      description = mdDoc "External port advertised to clients";
+    };
+
+    keysDir = mkOption {
+      type = with types; nullOr str;
+      description = mdDoc "Directory containing ssh keys, defaulting to auto-generation";
+      default = null;
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    networking.firewall.allowedTCPPorts = optionals cfg.openFirewall [ cfg.port ];
+
+    services.tmate-ssh-server = {
+      advertisedPort = mkDefault cfg.port;
+    };
+
+    environment.systemPackages =
+      let
+        tmate-config = pkgs.writeText "tmate.conf"
+          ''
+            set -g tmate-server-host "${cfg.host}"
+            set -g tmate-server-port ${toString cfg.port}
+            set -g tmate-server-ed25519-fingerprint "@ed25519_fingerprint@"
+            set -g tmate-server-rsa-fingerprint "@rsa_fingerprint@"
+          '';
+      in
+      [
+        (pkgs.writeShellApplication {
+          name = "tmate-client-config";
+          runtimeInputs = with pkgs;[ openssh coreutils sd ];
+          text = ''
+            RSA_SIG="$(ssh-keygen -l -E SHA256 -f "${keysDir}/ssh_host_rsa_key.pub" | cut -d ' ' -f 2)"
+            ED25519_SIG="$(ssh-keygen -l -E SHA256 -f "${keysDir}/ssh_host_ed25519_key.pub" | cut -d ' ' -f 2)"
+            sd -sp '@ed25519_fingerprint@' "$ED25519_SIG" ${tmate-config} | \
+              sd -sp '@rsa_fingerprint@' "$RSA_SIG"
+          '';
+        })
+      ];
+
+    systemd.services.tmate-ssh-server = {
+      description = "tmate SSH Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/tmate-ssh-server -h ${cfg.host} -p ${toString cfg.port} -q ${toString cfg.advertisedPort} -k ${keysDir}";
+      };
+      preStart = mkIf (cfg.keysDir == null) ''
+        if [[ ! -d ${defaultKeysDir} ]]
+        then
+          mkdir -p ${defaultKeysDir}
+        fi
+        if [[ ! -f ${edKey} ]]
+        then
+          ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f ${edKey} -N ""
+        fi
+        if [[ ! -f ${rsaKey} ]]
+        then
+          ${pkgs.openssh}/bin/ssh-keygen -t rsa -f ${rsaKey} -N ""
+        fi
+      '';
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [ jlesquembre ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix b/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
index e6dc36bf9ecf..5c7e7a4c2208 100644
--- a/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -29,7 +29,7 @@ in
           };
 
           port = mkOption {
-            type = types.int;
+            type = types.port;
             default = 33445;
             description = lib.mdDoc "Listening port (UDP).";
           };
diff --git a/nixpkgs/nixos/modules/services/networking/tox-node.nix b/nixpkgs/nixos/modules/services/networking/tox-node.nix
index 9371066be8e2..884fd55dae51 100644
--- a/nixpkgs/nixos/modules/services/networking/tox-node.nix
+++ b/nixpkgs/nixos/modules/services/networking/tox-node.nix
@@ -8,7 +8,7 @@ let
   homeDir = "/var/lib/tox-node";
 
   configFile = let
-    src = "${pkg.src}/dpkg/config.yml";
+    src = "${pkg.src}/tox_node/dpkg/config.yml";
     confJSON = pkgs.writeText "config.json" (
       builtins.toJSON {
         log-type = cfg.logType;
@@ -28,7 +28,7 @@ let
 
 in {
   options.services.tox-node = {
-    enable = mkEnableOption "Tox Node service";
+    enable = mkEnableOption (lib.mdDoc "Tox Node service");
 
     logType = mkOption {
       type = types.enum [ "Stderr" "Stdout" "Syslog" "None" ];
diff --git a/nixpkgs/nixos/modules/services/networking/toxvpn.nix b/nixpkgs/nixos/modules/services/networking/toxvpn.nix
index 618726b0640d..3a14b5f73091 100644
--- a/nixpkgs/nixos/modules/services/networking/toxvpn.nix
+++ b/nixpkgs/nixos/modules/services/networking/toxvpn.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.toxvpn = {
-      enable = mkEnableOption "toxvpn running on startup";
+      enable = mkEnableOption (lib.mdDoc "toxvpn running on startup");
 
       localip = mkOption {
         type        = types.str;
@@ -14,7 +14,7 @@ with lib;
       };
 
       port = mkOption {
-        type        = types.int;
+        type        = types.port;
         default     = 33445;
         description = lib.mdDoc "udp port for toxcore, port-forward to help with connectivity if you run many nodes behind one NAT";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/tvheadend.nix b/nixpkgs/nixos/modules/services/networking/tvheadend.nix
index dd5fa209be6b..466dbbccad53 100644
--- a/nixpkgs/nixos/modules/services/networking/tvheadend.nix
+++ b/nixpkgs/nixos/modules/services/networking/tvheadend.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.tvheadend = {
-      enable = mkEnableOption "Tvheadend";
+      enable = mkEnableOption (lib.mdDoc "Tvheadend");
       httpPort = mkOption {
         type        = types.int;
         default     = 9981;
diff --git a/nixpkgs/nixos/modules/services/networking/twingate.nix b/nixpkgs/nixos/modules/services/networking/twingate.nix
new file mode 100644
index 000000000000..17140bffd218
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/twingate.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.twingate;
+
+in {
+
+  options.services.twingate = {
+    enable = mkEnableOption (lib.mdDoc "Twingate Client daemon");
+  };
+
+  config = mkIf cfg.enable {
+
+    networking.firewall.checkReversePath = lib.mkDefault false;
+    networking.networkmanager.enable = true;
+
+    environment.systemPackages = [ pkgs.twingate ]; # for the CLI
+    systemd.packages = [ pkgs.twingate ];
+
+    systemd.services.twingate.preStart = ''
+      cp -r -n ${pkgs.twingate}/etc/twingate/. /etc/twingate/
+    '';
+
+    systemd.services.twingate.wantedBy = [ "multi-user.target" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ucarp.nix b/nixpkgs/nixos/modules/services/networking/ucarp.nix
index 7e8b1026db70..1214cec63f54 100644
--- a/nixpkgs/nixos/modules/services/networking/ucarp.nix
+++ b/nixpkgs/nixos/modules/services/networking/ucarp.nix
@@ -28,7 +28,7 @@ let
   );
 in {
   options.networking.ucarp = {
-    enable = mkEnableOption "ucarp, userspace implementation of CARP";
+    enable = mkEnableOption (lib.mdDoc "ucarp, userspace implementation of CARP");
 
     interface = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/unbound.nix b/nixpkgs/nixos/modules/services/networking/unbound.nix
index 5bbb0f79d57c..0426dbb0c83c 100644
--- a/nixpkgs/nixos/modules/services/networking/unbound.nix
+++ b/nixpkgs/nixos/modules/services/networking/unbound.nix
@@ -40,7 +40,7 @@ in {
   options = {
     services.unbound = {
 
-      enable = mkEnableOption "Unbound domain name server";
+      enable = mkEnableOption (lib.mdDoc "Unbound domain name server");
 
       package = mkOption {
         type = types.package;
@@ -245,7 +245,7 @@ in {
         NotifyAccess = "main";
         Type = "notify";
 
-        # FIXME: Which of these do we actualy need, can we drop the chroot flag?
+        # FIXME: Which of these do we actually need, can we drop the chroot flag?
         AmbientCapabilities = [
           "CAP_NET_BIND_SERVICE"
           "CAP_NET_RAW"
@@ -286,6 +286,8 @@ in {
         LockPersonality = true;
         RestrictSUIDSGID = true;
 
+        ReadWritePaths = [ cfg.stateDir ];
+
         Restart = "on-failure";
         RestartSec = "5s";
       };
diff --git a/nixpkgs/nixos/modules/services/networking/unifi.nix b/nixpkgs/nixos/modules/services/networking/unifi.nix
index d30f7c89633b..3579d67aa54b 100644
--- a/nixpkgs/nixos/modules/services/networking/unifi.nix
+++ b/nixpkgs/nixos/modules/services/networking/unifi.nix
@@ -24,8 +24,8 @@ in
 
     services.unifi.jrePackage = mkOption {
       type = types.package;
-      default = pkgs.jre8;
-      defaultText = literalExpression "pkgs.jre8";
+      default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
+      defaultText = literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
       description = lib.mdDoc ''
         The JRE package to use. Check the release notes to ensure it is supported.
       '';
@@ -33,8 +33,8 @@ in
 
     services.unifi.unifiPackage = mkOption {
       type = types.package;
-      default = pkgs.unifiLTS;
-      defaultText = literalExpression "pkgs.unifiLTS";
+      default = pkgs.unifi5;
+      defaultText = literalExpression "pkgs.unifi5";
       description = lib.mdDoc ''
         The unifi package to use.
       '';
@@ -42,10 +42,10 @@ in
 
     services.unifi.mongodbPackage = mkOption {
       type = types.package;
-      default = pkgs.mongodb;
+      default = pkgs.mongodb-4_4;
       defaultText = literalExpression "pkgs.mongodb";
       description = lib.mdDoc ''
-        The mongodb package to use.
+        The mongodb package to use. Please note: unifi7 officially only supports mongodb up until 3.6 but works with 4.4.
       '';
     };
 
@@ -76,7 +76,7 @@ in
       default = null;
       example = 4096;
       description = lib.mdDoc ''
-        Set the maximimum heap size for the JVM in MB. If this option isn't set, the
+        Set the maximum heap size for the JVM in MB. If this option isn't set, the
         JVM will decide this value at runtime.
       '';
     };
@@ -193,6 +193,4 @@ in
     (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/uptermd.nix b/nixpkgs/nixos/modules/services/networking/uptermd.nix
index 387478de99eb..f824d617f59e 100644
--- a/nixpkgs/nixos/modules/services/networking/uptermd.nix
+++ b/nixpkgs/nixos/modules/services/networking/uptermd.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.uptermd = {
-      enable = mkEnableOption "uptermd";
+      enable = mkEnableOption (lib.mdDoc "uptermd");
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/services/networking/v2ray.nix b/nixpkgs/nixos/modules/services/networking/v2ray.nix
index f063ddfed0a7..ba2aa5bc1de7 100644
--- a/nixpkgs/nixos/modules/services/networking/v2ray.nix
+++ b/nixpkgs/nixos/modules/services/networking/v2ray.nix
@@ -34,7 +34,7 @@ with lib;
 
           Either `configFile` or `config` must be specified.
 
-          See <https://www.v2fly.org/en_US/config/overview.html>.
+          See <https://www.v2fly.org/en_US/v5/config/overview.html>.
         '';
       };
 
@@ -51,12 +51,12 @@ with lib;
             protocol = "freedom";
           }];
         };
-        description = ''
+        description = lib.mdDoc ''
           The configuration object.
 
           Either `configFile` or `config` must be specified.
 
-          See <link xlink:href="https://www.v2fly.org/en_US/config/overview.html"/>.
+          See <https://www.v2fly.org/en_US/v5/config/overview.html>.
         '';
       };
     };
@@ -71,7 +71,7 @@ with lib;
         name = "v2ray.json";
         text = builtins.toJSON cfg.config;
         checkPhase = ''
-          ${cfg.package}/bin/v2ray -test -config $out
+          ${cfg.package}/bin/v2ray test -c $out
         '';
       };
 
@@ -83,13 +83,15 @@ with lib;
       }
     ];
 
+    environment.etc."v2ray/config.json".source = configFile;
+
+    systemd.packages = [ cfg.package ];
+
     systemd.services.v2ray = {
-      description = "v2ray Daemon";
-      after = [ "network.target" ];
+      restartTriggers = [ config.environment.etc."v2ray/config.json".source ];
+
+      # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
       wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/v2ray -config ${configFile}";
-      };
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/v2raya.nix b/nixpkgs/nixos/modules/services/networking/v2raya.nix
new file mode 100644
index 000000000000..0bea73798daf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/v2raya.nix
@@ -0,0 +1,50 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options = {
+    services.v2raya = {
+      enable = options.mkEnableOption (mdDoc "the v2rayA service");
+    };
+  };
+
+  config = mkIf config.services.v2raya.enable {
+    environment.systemPackages = [ pkgs.v2raya ];
+
+    systemd.services.v2raya =
+      let
+        nftablesEnabled = config.networking.nftables.enable;
+        iptablesServices = [
+          "iptables.service"
+        ] ++ optional config.networking.enableIPv6 "ip6tables.service";
+        tableServices = if nftablesEnabled then [ "nftables.service" ] else iptablesServices;
+      in
+      {
+        unitConfig = {
+          Description = "v2rayA service";
+          Documentation = "https://github.com/v2rayA/v2rayA/wiki";
+          After = [
+            "network.target"
+            "nss-lookup.target"
+          ] ++ tableServices;
+          Wants = [ "network.target" ];
+        };
+
+        serviceConfig = {
+          User = "root";
+          ExecStart = "${getExe pkgs.v2raya} --log-disable-timestamp";
+          Environment = [ "V2RAYA_LOG_FILE=/var/log/v2raya/v2raya.log" ];
+          LimitNPROC = 500;
+          LimitNOFILE = 1000000;
+          Restart = "on-failure";
+          Type = "simple";
+        };
+
+        wantedBy = [ "multi-user.target" ];
+        path = with pkgs; [ iptables bash iproute2 ]; # required by v2rayA TProxy functionality
+      };
+  };
+
+  meta.maintainers = with maintainers; [ elliot ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/vdirsyncer.nix b/nixpkgs/nixos/modules/services/networking/vdirsyncer.nix
new file mode 100644
index 000000000000..f9b880c763e3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/vdirsyncer.nix
@@ -0,0 +1,214 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.vdirsyncer;
+
+  toIniJson = with generators; toINI {
+    mkKeyValue = mkKeyValueDefault {
+      mkValueString = builtins.toJSON;
+    } "=";
+  };
+
+  toConfigFile = name: cfg':
+    if
+      cfg'.configFile != null
+    then
+      cfg'.configFile
+    else
+      pkgs.writeText "vdirsyncer-${name}.conf" (toIniJson (
+        {
+          general = cfg'.config.general // (lib.optionalAttrs (cfg'.config.statusPath == null) {
+            status_path = "/var/lib/vdirsyncer/${name}";
+          });
+        } // (
+          mapAttrs' (name: nameValuePair "pair ${name}") cfg'.config.pairs
+        ) // (
+          mapAttrs' (name: nameValuePair "storage ${name}") cfg'.config.storages
+        )
+      ));
+
+  userUnitConfig = name: cfg': {
+    serviceConfig = {
+      User = if cfg'.user == null then "vdirsyncer" else cfg'.user;
+      Group = if cfg'.group == null then "vdirsyncer" else cfg'.group;
+    }  // (optionalAttrs (cfg'.user == null) {
+      DynamicUser = true;
+    }) // (optionalAttrs (cfg'.additionalGroups != []) {
+      SupplementaryGroups = cfg'.additionalGroups;
+    }) // (optionalAttrs (cfg'.config.statusPath == null) {
+      StateDirectory = "vdirsyncer/${name}";
+      StateDirectoryMode = "0700";
+    });
+  };
+
+  commonUnitConfig = {
+    after = [ "network.target" ];
+    serviceConfig = {
+      Type = "oneshot";
+      # Sandboxing
+      PrivateTmp = true;
+      NoNewPrivileges = true;
+      ProtectSystem = "strict";
+      ProtectHome = true;
+      ProtectKernelTunables = true;
+      ProtectKernelModules = true;
+      ProtectControlGroups = true;
+      RestrictNamespaces = true;
+      MemoryDenyWriteExecute = true;
+      RestrictRealtime = true;
+      RestrictSUIDSGID = true;
+      RestrictAddressFamilies = "AF_INET AF_INET6";
+      LockPersonality = true;
+    };
+  };
+
+in
+{
+  options = {
+    services.vdirsyncer = {
+      enable = mkEnableOption (mdDoc "vdirsyncer");
+
+      package = mkPackageOptionMD pkgs "vdirsyncer" {};
+
+      jobs = mkOption {
+        description = mdDoc "vdirsyncer job configurations";
+        type = types.attrsOf (types.submodule {
+          options = {
+            enable = (mkEnableOption (mdDoc "this vdirsyncer job")) // {
+              default = true;
+              example = false;
+            };
+
+            user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = mdDoc ''
+                User account to run vdirsyncer as, otherwise as a systemd
+                dynamic user
+              '';
+            };
+
+            group = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = mdDoc "group to run vdirsyncer as";
+            };
+
+            additionalGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = mdDoc "additional groups to add the dynamic user to";
+            };
+
+            forceDiscover = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Run `yes | vdirsyncer discover` prior to `vdirsyncer sync`
+              '';
+            };
+
+            timerConfig = mkOption {
+              type = types.attrs;
+              default = {
+                OnBootSec = "1h";
+                OnUnitActiveSec = "6h";
+              };
+              description = mdDoc "systemd timer configuration";
+            };
+
+            configFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc "existing configuration file";
+            };
+
+            config = {
+              statusPath = mkOption {
+                type = types.nullOr types.str;
+                default = null;
+                defaultText = literalExpression "/var/lib/vdirsyncer/\${attrName}";
+                description = mdDoc "vdirsyncer's status path";
+              };
+
+              general = mkOption {
+                type = types.attrs;
+                default = {};
+                description = mdDoc "general configuration";
+              };
+
+              pairs = mkOption {
+                type = types.attrsOf types.attrs;
+                default = {};
+                description = mdDoc "vdirsyncer pair configurations";
+                example = literalExpression ''
+                  {
+                    my_contacts = {
+                      a = "my_cloud_contacts";
+                      b = "my_local_contacts";
+                      collections = [ "from a" ];
+                      conflict_resolution = "a wins";
+                      metadata = [ "color" "displayname" ];
+                    };
+                  };
+                '';
+              };
+
+              storages = mkOption {
+                type = types.attrsOf types.attrs;
+                default = {};
+                description = mdDoc "vdirsyncer storage configurations";
+                example = literalExpression ''
+                  {
+                    my_cloud_contacts = {
+                      type = "carddav";
+                      url = "https://dav.example.com/";
+                      read_only = true;
+                      username = "user";
+                      "password.fetch" = [ "command" "cat" "/etc/vdirsyncer/cloud.passwd" ];
+                    };
+                    my_local_contacts = {
+                      type = "carddav";
+                      url = "https://localhost/";
+                      username = "user";
+                      "password.fetch" = [ "command" "cat" "/etc/vdirsyncer/local.passwd" ];
+                    };
+                  }
+                '';
+              };
+            };
+          };
+        });
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services = mapAttrs' (name: cfg': nameValuePair "vdirsyncer@${name}" (
+      foldr recursiveUpdate {} [
+        commonUnitConfig
+        (userUnitConfig name cfg')
+        {
+          description = "synchronize calendars and contacts (${name})";
+          environment.VDIRSYNCER_CONFIG = toConfigFile name cfg';
+          serviceConfig.ExecStart =
+            (optional cfg'.forceDiscover (
+              pkgs.writeShellScript "vdirsyncer-discover-yes" ''
+                set -e
+                yes | ${cfg.package}/bin/vdirsyncer discover
+              ''
+            )) ++ [ "${cfg.package}/bin/vdirsyncer sync" ];
+        }
+      ]
+    )) (filterAttrs (name: cfg': cfg'.enable) cfg.jobs);
+
+    systemd.timers = mapAttrs' (name: cfg': nameValuePair "vdirsyncer@${name}" {
+      wantedBy = [ "timers.target" ];
+      description = "synchronize calendars and contacts (${name})";
+      inherit (cfg') timerConfig;
+    }) cfg.jobs;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/vsftpd.nix b/nixpkgs/nixos/modules/services/networking/vsftpd.nix
index b26adbf8719b..b1f0f7403243 100644
--- a/nixpkgs/nixos/modules/services/networking/vsftpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/vsftpd.nix
@@ -27,7 +27,8 @@ let
       type = types.bool;
       name = nixosName;
       value = mkOption {
-        inherit description default;
+        description = lib.mdDoc description;
+        inherit default;
         type = types.bool;
       };
     };
@@ -68,16 +69,16 @@ let
       Whether users are included.
     '')
     (yesNoOption "userlistDeny" "userlist_deny" false ''
-      Specifies whether <option>userlistFile</option> is a list of user
+      Specifies whether {option}`userlistFile` is a list of user
       names to allow or deny access.
-      The default <literal>false</literal> means whitelist/allow.
+      The default `false` means whitelist/allow.
     '')
     (yesNoOption "forceLocalLoginsSSL" "force_local_logins_ssl" false ''
-      Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
+      Only applies if {option}`sslEnable` is true. Non anonymous (local) users
       must use a secure SSL connection to send a password.
     '')
     (yesNoOption "forceLocalDataSSL" "force_local_data_ssl" false ''
-      Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
+      Only applies if {option}`sslEnable` is true. Non anonymous (local) users
       must use a secure SSL connection for sending/receiving data on data connection.
     '')
     (yesNoOption "portPromiscuous" "port_promiscuous" false ''
@@ -86,17 +87,17 @@ let
       know what you are doing!
     '')
     (yesNoOption "ssl_tlsv1" "ssl_tlsv1" true  ''
-      Only applies if <option>ssl_enable</option> is activated. If
+      Only applies if {option}`ssl_enable` is activated. If
       enabled, this option will permit TLS v1 protocol connections.
       TLS v1 connections are preferred.
     '')
     (yesNoOption "ssl_sslv2" "ssl_sslv2" false ''
-      Only applies if <option>ssl_enable</option> is activated. If
+      Only applies if {option}`ssl_enable` is activated. If
       enabled, this option will permit SSL v2 protocol connections.
       TLS v1 connections are preferred.
     '')
     (yesNoOption "ssl_sslv3" "ssl_sslv3" false ''
-      Only applies if <option>ssl_enable</option> is activated. If
+      Only applies if {option}`ssl_enable` is activated. If
       enabled, this option will permit SSL v3 protocol connections.
       TLS v1 connections are preferred.
     '')
@@ -149,7 +150,7 @@ in
 
     services.vsftpd = {
 
-      enable = mkEnableOption "vsftpd";
+      enable = mkEnableOption (lib.mdDoc "vsftpd");
 
       userlist = mkOption {
         default = [];
@@ -167,7 +168,7 @@ in
 
           The default is a file containing the users from {option}`userlist`.
 
-          If explicitely set to null userlist_file will not be set in vsftpd's config file.
+          If explicitly set to null userlist_file will not be set in vsftpd's config file.
         '';
       };
 
@@ -184,9 +185,9 @@ in
         type = types.nullOr types.str;
         example = "/etc/vsftpd/userDb";
         default = null;
-        description = ''
-          Only applies if <option>enableVirtualUsers</option> is true.
-          Path pointing to the <literal>pam_userdb</literal> user
+        description = lib.mdDoc ''
+          Only applies if {option}`enableVirtualUsers` is true.
+          Path pointing to the `pam_userdb` user
           database used by vsftpd to authenticate the virtual users.
 
           This user list should be stored in the Berkeley DB database
@@ -194,21 +195,21 @@ in
 
           To generate a new user database, create a text file, add
           your users using the following format:
-          <programlisting>
+          ```
           user1
           password1
           user2
           password2
-          </programlisting>
+          ```
 
-          You can then install <literal>pkgs.db</literal> to generate
+          You can then install `pkgs.db` to generate
           the Berkeley DB using
-          <programlisting>
+          ```
           db_load -T -t hash -f logins.txt userDb.db
-          </programlisting>
+          ```
 
-          Caution: <literal>pam_userdb</literal> will automatically
-          append a <literal>.db</literal> suffix to the filename you
+          Caution: `pam_userdb` will automatically
+          append a `.db` suffix to the filename you
           provide though this option. This option shouldn't include
           this filetype suffix.
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/wasabibackend.nix b/nixpkgs/nixos/modules/services/networking/wasabibackend.nix
index 00d772a718c6..938145b35ee8 100644
--- a/nixpkgs/nixos/modules/services/networking/wasabibackend.nix
+++ b/nixpkgs/nixos/modules/services/networking/wasabibackend.nix
@@ -29,7 +29,7 @@ in {
   options = {
 
     services.wasabibackend = {
-      enable = mkEnableOption "Wasabi backend service";
+      enable = mkEnableOption (lib.mdDoc "Wasabi backend service");
 
       dataDir = mkOption {
         type = types.path;
diff --git a/nixpkgs/nixos/modules/services/networking/webhook.nix b/nixpkgs/nixos/modules/services/networking/webhook.nix
new file mode 100644
index 000000000000..2a78491941cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/webhook.nix
@@ -0,0 +1,214 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.webhook;
+  defaultUser = "webhook";
+
+  hookFormat = pkgs.formats.json {};
+
+  hookType = types.submodule ({ name, ... }: {
+    freeformType = hookFormat.type;
+    options = {
+      id = mkOption {
+        type = types.str;
+        default = name;
+        description = mdDoc ''
+          The ID of your hook. This value is used to create the HTTP endpoint (`protocol://yourserver:port/prefix/''${id}`).
+        '';
+      };
+      execute-command = mkOption {
+        type = types.str;
+        description = mdDoc "The command that should be executed when the hook is triggered.";
+      };
+    };
+  });
+
+  hookFiles = mapAttrsToList (name: hook: hookFormat.generate "webhook-${name}.json" [ hook ]) cfg.hooks
+           ++ mapAttrsToList (name: hook: pkgs.writeText "webhook-${name}.json.tmpl" "[${hook}]") cfg.hooksTemplated;
+
+in {
+  options = {
+    services.webhook = {
+      enable = mkEnableOption (mdDoc ''
+        [Webhook](https://github.com/adnanh/webhook), a server written in Go that allows you to create HTTP endpoints (hooks),
+        which execute configured commands for any person or service that knows the URL
+      '');
+
+      package = mkPackageOptionMD pkgs "webhook" {};
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = mdDoc ''
+          Webhook will be run under this user.
+
+          If set, you must create this user yourself!
+        '';
+      };
+      group = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = mdDoc ''
+          Webhook will be run under this group.
+
+          If set, you must create this group yourself!
+        '';
+      };
+      ip = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = mdDoc ''
+          The IP webhook should serve hooks on.
+
+          The default means it can be reached on any interface if `openFirewall = true`.
+        '';
+      };
+      port = mkOption {
+        type = types.port;
+        default = 9000;
+        description = mdDoc "The port webhook should be reachable from.";
+      };
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open the configured port in the firewall for external ingress traffic.
+          Preferably the Webhook server is instead put behind a reverse proxy.
+        '';
+      };
+      enableTemplates = mkOption {
+        type = types.bool;
+        default = cfg.hooksTemplated != {};
+        defaultText = literalExpression "hooksTemplated != {}";
+        description = mdDoc ''
+          Enable the generated hooks file to be parsed as a Go template.
+          See [the documentation](https://github.com/adnanh/webhook/blob/master/docs/Templates.md) for more information.
+        '';
+      };
+      urlPrefix = mkOption {
+        type = types.str;
+        default = "hooks";
+        description = mdDoc ''
+          The URL path prefix to use for served hooks (`protocol://yourserver:port/''${prefix}/hook-id`).
+        '';
+      };
+      hooks = mkOption {
+        type = types.attrsOf hookType;
+        default = {};
+        example = {
+          echo = {
+            execute-command = "echo";
+            response-message = "Webhook is reachable!";
+          };
+          redeploy-webhook = {
+            execute-command = "/var/scripts/redeploy.sh";
+            command-working-directory = "/var/webhook";
+          };
+        };
+        description = mdDoc ''
+          The actual configuration of which hooks will be served.
+
+          Read more on the [project homepage] and on the [hook definition] page.
+          At least one hook needs to be configured.
+
+          [hook definition]: https://github.com/adnanh/webhook/blob/master/docs/Hook-Definition.md
+          [project homepage]: https://github.com/adnanh/webhook#configuration
+        '';
+      };
+      hooksTemplated = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = {
+          echo-template = ''
+            {
+              "id": "echo-template",
+              "execute-command": "echo",
+              "response-message": "{{ getenv "MESSAGE" }}"
+            }
+          '';
+        };
+        description = mdDoc ''
+          Same as {option}`hooks`, but these hooks are specified as literal strings instead of Nix values,
+          and hence can include [template syntax](https://github.com/adnanh/webhook/blob/master/docs/Templates.md)
+          which might not be representable as JSON.
+
+          Template syntax requires the {option}`enableTemplates` option to be set to `true`, which is
+          done by default if this option is set.
+        '';
+      };
+      verbose = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc "Whether to show verbose output.";
+      };
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-secure" ];
+        description = mdDoc ''
+          These are arguments passed to the webhook command in the systemd service.
+          You can find the available arguments and options in the [documentation][parameters].
+
+          [parameters]: https://github.com/adnanh/webhook/blob/master/docs/Webhook-Parameters.md
+        '';
+      };
+      environment = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = mdDoc "Extra environment variables passed to webhook.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = let
+      overlappingHooks = builtins.intersectAttrs cfg.hooks cfg.hooksTemplated;
+    in [
+      {
+        assertion = hookFiles != [];
+        message = "At least one hook needs to be configured for webhook to run.";
+      }
+      {
+        assertion = overlappingHooks == {};
+        message = "`services.webhook.hooks` and `services.webhook.hooksTemplated` have overlapping attribute(s): ${concatStringsSep ", " (builtins.attrNames overlappingHooks)}";
+      }
+    ];
+
+    users.users = mkIf (cfg.user == defaultUser) {
+      ${defaultUser} =
+        {
+          isSystemUser = true;
+          group = cfg.group;
+          description = "Webhook daemon user";
+        };
+    };
+
+    users.groups = mkIf (cfg.user == defaultUser && cfg.group == defaultUser) {
+      ${defaultUser} = {};
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+    systemd.services.webhook = {
+      description = "Webhook service";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = config.networking.proxy.envVars // cfg.environment;
+      script = let
+        args = [ "-ip" cfg.ip "-port" (toString cfg.port) "-urlprefix" cfg.urlPrefix ]
+            ++ concatMap (hook: [ "-hooks" hook ]) hookFiles
+            ++ optional cfg.enableTemplates "-template"
+            ++ optional cfg.verbose "-verbose"
+            ++ cfg.extraArgs;
+      in ''
+        ${cfg.package}/bin/webhook ${escapeShellArgs args}
+      '';
+      serviceConfig = {
+        Restart = "on-failure";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wg-netmanager.nix b/nixpkgs/nixos/modules/services/networking/wg-netmanager.nix
index 493ff7ceba9f..b260c573726b 100644
--- a/nixpkgs/nixos/modules/services/networking/wg-netmanager.nix
+++ b/nixpkgs/nixos/modules/services/networking/wg-netmanager.nix
@@ -9,7 +9,7 @@ in
 
   options = {
     services.wg-netmanager = {
-      enable = mkEnableOption "Wireguard network manager";
+      enable = mkEnableOption (lib.mdDoc "Wireguard network manager");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/networking/wg-quick.nix b/nixpkgs/nixos/modules/services/networking/wg-quick.nix
index b43c3e851324..34210580f538 100644
--- a/nixpkgs/nixos/modules/services/networking/wg-quick.nix
+++ b/nixpkgs/nixos/modules/services/networking/wg-quick.nix
@@ -273,7 +273,11 @@ let
         after = [ "network.target" "network-online.target" ];
         wantedBy = optional values.autostart "multi-user.target";
         environment.DEVICE = name;
-        path = [ pkgs.kmod pkgs.wireguard-tools config.networking.resolvconf.package ];
+        path = [
+          pkgs.wireguard-tools
+          config.networking.firewall.package   # iptables or nftables
+          config.networking.resolvconf.package # openresolv or systemd
+        ];
 
         serviceConfig = {
           Type = "oneshot";
@@ -281,7 +285,7 @@ let
         };
 
         script = ''
-          ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+          ${optionalString (!config.boot.isContainer) "${pkgs.kmod}/bin/modprobe wireguard"}
           ${optionalString (values.configFile != null) ''
             cp ${values.configFile} ${configPath}
           ''}
@@ -328,9 +332,6 @@ in {
   config = mkIf (cfg.interfaces != {}) {
     boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
     environment.systemPackages = [ pkgs.wireguard-tools ];
-    # This is forced to false for now because the default "--validmark" rpfilter we apply on reverse path filtering
-    # breaks the wg-quick routing because wireguard packets leave with a fwmark from wireguard.
-    networking.firewall.checkReversePath = false;
     systemd.services = mapAttrs' generateUnit cfg.interfaces;
 
     # Prevent networkd from clearing the rules set by wg-quick when restarted (e.g. when waking up from suspend).
diff --git a/nixpkgs/nixos/modules/services/networking/wgautomesh.nix b/nixpkgs/nixos/modules/services/networking/wgautomesh.nix
new file mode 100644
index 000000000000..7549d82eae0b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wgautomesh.nix
@@ -0,0 +1,161 @@
+{ lib, config, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.wgautomesh;
+  settingsFormat = pkgs.formats.toml { };
+  configFile =
+    # Have to remove nulls manually as TOML generator will not just skip key
+    # if value is null
+    settingsFormat.generate "wgautomesh-config.toml"
+      (filterAttrs (k: v: v != null)
+        (mapAttrs
+          (k: v:
+            if k == "peers"
+            then map (e: filterAttrs (k: v: v != null) e) v
+            else v)
+          cfg.settings));
+  runtimeConfigFile =
+    if cfg.enableGossipEncryption
+    then "/run/wgautomesh/wgautomesh.toml"
+    else configFile;
+in
+{
+  options.services.wgautomesh = {
+    enable = mkEnableOption (mdDoc "the wgautomesh daemon");
+    logLevel = mkOption {
+      type = types.enum [ "trace" "debug" "info" "warn" "error" ];
+      default = "info";
+      description = mdDoc "wgautomesh log level.";
+    };
+    enableGossipEncryption = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc "Enable encryption of gossip traffic.";
+    };
+    gossipSecretFile = mkOption {
+      type = types.path;
+      description = mdDoc ''
+        File containing the shared secret key to use for gossip encryption.
+        Required if `enableGossipEncryption` is set.
+      '';
+    };
+    enablePersistence = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc "Enable persistence of Wireguard peer info between restarts.";
+    };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc "Automatically open gossip port in firewall (recommended).";
+    };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+
+          interface = mkOption {
+            type = types.str;
+            description = mdDoc ''
+              Wireguard interface to manage (it is NOT created by wgautomesh, you
+              should use another NixOS option to create it such as
+              `networking.wireguard.interfaces.wg0 = {...};`).
+            '';
+            example = "wg0";
+          };
+          gossip_port = mkOption {
+            type = types.port;
+            description = mdDoc ''
+              wgautomesh gossip port, this MUST be the same number on all nodes in
+              the wgautomesh network.
+            '';
+            default = 1666;
+          };
+          lan_discovery = mkOption {
+            type = types.bool;
+            default = true;
+            description = mdDoc "Enable discovery of peers on the same LAN using UDP broadcast.";
+          };
+          upnp_forward_external_port = mkOption {
+            type = types.nullOr types.port;
+            default = null;
+            description = mdDoc ''
+              Public port number to try to redirect to this machine's Wireguard
+              daemon using UPnP IGD.
+            '';
+          };
+          peers = mkOption {
+            type = types.listOf (types.submodule {
+              options = {
+                pubkey = mkOption {
+                  type = types.str;
+                  description = mdDoc "Wireguard public key of this peer.";
+                };
+                address = mkOption {
+                  type = types.str;
+                  description = mdDoc ''
+                    Wireguard address of this peer (a single IP address, multiple
+                    addresses or address ranges are not supported).
+                  '';
+                  example = "10.0.0.42";
+                };
+                endpoint = mkOption {
+                  type = types.nullOr types.str;
+                  description = mdDoc ''
+                    Bootstrap endpoint for connecting to this Wireguard peer if no
+                    other address is known or none are working.
+                  '';
+                  default = null;
+                  example = "wgnode.mydomain.example:51820";
+                };
+              };
+            });
+            default = [ ];
+            description = mdDoc "wgautomesh peer list.";
+          };
+        };
+
+      };
+      default = { };
+      description = mdDoc "Configuration for wgautomesh.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.wgautomesh.settings = {
+      gossip_secret_file = mkIf cfg.enableGossipEncryption "$CREDENTIALS_DIRECTORY/gossip_secret";
+      persist_file = mkIf cfg.enablePersistence "/var/lib/wgautomesh/state";
+    };
+
+    systemd.services.wgautomesh = {
+      path = [ pkgs.wireguard-tools ];
+      environment = { RUST_LOG = "wgautomesh=${cfg.logLevel}"; };
+      description = "wgautomesh";
+      serviceConfig = {
+        Type = "simple";
+
+        ExecStart = "${getExe pkgs.wgautomesh} ${runtimeConfigFile}";
+        Restart = "always";
+        RestartSec = "30";
+        LoadCredential = mkIf cfg.enableGossipEncryption [ "gossip_secret:${cfg.gossipSecretFile}" ];
+
+        ExecStartPre = mkIf cfg.enableGossipEncryption [
+          ''${pkgs.envsubst}/bin/envsubst \
+              -i ${configFile} \
+              -o ${runtimeConfigFile}''
+        ];
+
+        DynamicUser = true;
+        StateDirectory = "wgautomesh";
+        StateDirectoryMode = "0700";
+        RuntimeDirectory = "wgautomesh";
+        AmbientCapabilities = "CAP_NET_ADMIN";
+        CapabilityBoundingSet = "CAP_NET_ADMIN";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+    networking.firewall.allowedUDPPorts =
+      mkIf cfg.openFirewall [ cfg.settings.gossip_port ];
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/wireguard.nix b/nixpkgs/nixos/modules/services/networking/wireguard.nix
index 9017c53f4e56..21473388d76e 100644
--- a/nixpkgs/nixos/modules/services/networking/wireguard.nix
+++ b/nixpkgs/nixos/modules/services/networking/wireguard.nix
@@ -137,19 +137,58 @@ let
         See [documentation](https://www.wireguard.com/netns/).
         '';
       };
+
+      fwMark = mkOption {
+        default = null;
+        type = with types; nullOr str;
+        example = "0x6e6978";
+        description = lib.mdDoc ''
+          Mark all wireguard packets originating from
+          this interface with the given firewall mark. The firewall mark can be
+          used in firewalls or policy routing to filter the wireguard packets.
+          This can be useful for setup where all traffic goes through the
+          wireguard tunnel, because the wireguard packets need to be routed
+          differently.
+        '';
+      };
+
+      mtu = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 1280;
+        description = lib.mdDoc ''
+          Set the maximum transmission unit in bytes for the wireguard
+          interface. Beware that the wireguard packets have a header that may
+          add up to 80 bytes to the mtu. By default, the MTU is (1500 - 80) =
+          1420. However, if the MTU of the upstream network is lower, the MTU
+          of the wireguard network has to be adjusted as well.
+        '';
+      };
     };
 
   };
 
   # peer options
 
-  peerOpts = {
+  peerOpts = self: {
 
     options = {
 
+      name = mkOption {
+        default =
+          replaceStrings
+            [ "/" "-"     " "     "+"     "="     ]
+            [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ]
+            self.config.publicKey;
+        defaultText = literalExpression "publicKey";
+        example = "bernd";
+        type = types.str;
+        description = lib.mdDoc "Name used to derive peer unit name.";
+      };
+
       publicKey = mkOption {
         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
-        type = types.str;
+        type = types.singleLineStr;
         description = lib.mdDoc "The base64 public key of the peer.";
       };
 
@@ -194,19 +233,20 @@ let
         default = null;
         example = "demo.wireguard.io:12913";
         type = with types; nullOr str;
-        description = ''Endpoint IP or hostname of the peer, followed by a colon,
-        and then a port number of the peer.
-
-        Warning for endpoints with changing IPs:
-        The WireGuard kernel side cannot perform DNS resolution.
-        Thus DNS resolution is done once by the <literal>wg</literal> userspace
-        utility, when setting up WireGuard. Consequently, if the IP address
-        behind the name changes, WireGuard will not notice.
-        This is especially common for dynamic-DNS setups, but also applies to
-        any other DNS-based setup.
-        If you do not use IP endpoints, you likely want to set
-        <option>networking.wireguard.dynamicEndpointRefreshSeconds</option>
-        to refresh the IPs periodically.
+        description = lib.mdDoc ''
+          Endpoint IP or hostname of the peer, followed by a colon,
+          and then a port number of the peer.
+
+          Warning for endpoints with changing IPs:
+          The WireGuard kernel side cannot perform DNS resolution.
+          Thus DNS resolution is done once by the `wg` userspace
+          utility, when setting up WireGuard. Consequently, if the IP address
+          behind the name changes, WireGuard will not notice.
+          This is especially common for dynamic-DNS setups, but also applies to
+          any other DNS-based setup.
+          If you do not use IP endpoints, you likely want to set
+          {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
+          to refresh the IPs periodically.
         '';
       };
 
@@ -223,6 +263,21 @@ let
         '';
       };
 
+      dynamicEndpointRefreshRestartSeconds = mkOption {
+        default = null;
+        example = 5;
+        type = with types; nullOr ints.unsigned;
+        description = lib.mdDoc ''
+          When the dynamic endpoint refresh that is configured via
+          dynamicEndpointRefreshSeconds exits (likely due to a failure),
+          restart that service after this many seconds.
+
+          If set to `null` the value of
+          {option}`networking.wireguard.dynamicEndpointRefreshSeconds`
+          will be used as the default.
+        '';
+      };
+
       persistentKeepalive = mkOption {
         default = null;
         type = with types; nullOr int;
@@ -260,7 +315,7 @@ let
           set -e
 
           # If the parent dir does not already exist, create it.
-          # Otherwise, does nothing, keeping existing permisions intact.
+          # Otherwise, does nothing, keeping existing permissions intact.
           mkdir -p --mode 0755 "${dirOf values.privateKeyFile}"
 
           if [ ! -f "${values.privateKeyFile}" ]; then
@@ -270,15 +325,11 @@ let
         '';
       };
 
-  peerUnitServiceName = interfaceName: publicKey: dynamicRefreshEnabled:
+  peerUnitServiceName = interfaceName: peerName: dynamicRefreshEnabled:
     let
-      keyToUnitName = replaceChars
-        [ "/" "-"    " "     "+"     "="      ]
-        [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
-      unitName = keyToUnitName publicKey;
       refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
     in
-      "wireguard-${interfaceName}-peer-${unitName}${refreshSuffix}";
+      "wireguard-${interfaceName}-peer-${peerName}${refreshSuffix}";
 
   generatePeerUnit = { interfaceName, interfaceCfg, peer }:
     let
@@ -294,10 +345,11 @@ let
       # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds`
       # to avoid that the same service switches `Type` (`oneshot` vs `simple`),
       # with the intent to make scripting more obvious.
-      serviceName = peerUnitServiceName interfaceName peer.publicKey dynamicRefreshEnabled;
+      serviceName = peerUnitServiceName interfaceName peer.name dynamicRefreshEnabled;
     in nameValuePair serviceName
       {
-        description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
+        description = "WireGuard Peer - ${interfaceName} - ${peer.name}"
+          + optionalString (peer.name != peer.publicKey) " (${peer.publicKey})";
         requires = [ "wireguard-${interfaceName}.service" ];
         wants = [ "network-online.target" ];
         after = [ "wireguard-${interfaceName}.service" "network-online.target" ];
@@ -320,7 +372,16 @@ let
                 # cannot be used with systemd timers (see `man systemd.timer`),
                 # which is why `simple` with a loop is the best choice here.
                 # It also makes starting and stopping easiest.
+                #
+                # Restart if the service exits (e.g. when wireguard gives up after "Name or service not known" dns failures):
+                Restart = "always";
+                RestartSec = if null != peer.dynamicEndpointRefreshRestartSeconds
+                             then peer.dynamicEndpointRefreshRestartSeconds
+                             else peer.dynamicEndpointRefreshSeconds;
               };
+        unitConfig = lib.optionalAttrs dynamicRefreshEnabled {
+          StartLimitIntervalSec = 0;
+        };
 
         script = let
           wg_setup = concatStringsSep " " (
@@ -363,6 +424,19 @@ let
         '';
       };
 
+  # the target is required to start new peer units when they are added
+  generateInterfaceTarget = name: values:
+    let
+      mkPeerUnit = peer: (peerUnitServiceName name peer.name (peer.dynamicEndpointRefreshSeconds != 0)) + ".service";
+    in
+    nameValuePair "wireguard-${name}"
+      rec {
+        description = "WireGuard Tunnel - ${name}";
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "wireguard-${name}.service" ] ++ map mkPeerUnit values.peers;
+        after = wants;
+      };
+
   generateInterfaceUnit = name: values:
     # exactly one way to specify the private key must be set
     #assert (values.privateKey != null) != (values.privateKeyFile != null);
@@ -381,7 +455,6 @@ let
         after = [ "network-pre.target" ];
         wants = [ "network.target" ];
         before = [ "network.target" ];
-        wantedBy = [ "multi-user.target" ];
         environment.DEVICE = name;
         path = with pkgs; [ kmod iproute2 wireguard-tools ];
 
@@ -397,6 +470,7 @@ let
 
           ${ipPreMove} link add dev "${name}" type wireguard
           ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''}
+          ${optionalString (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''}
 
           ${concatMapStringsSep "\n" (ip:
             ''${ipPostMove} address add "${ip}" dev "${name}"''
@@ -405,6 +479,7 @@ let
           ${concatStringsSep " " (
             [ ''${wg} set "${name}" private-key "${privKey}"'' ]
             ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
+            ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
           )}
 
           ${ipPostMove} link set up dev "${name}"
@@ -435,7 +510,13 @@ in
     networking.wireguard = {
 
       enable = mkOption {
-        description = lib.mdDoc "Whether to enable WireGuard.";
+        description = lib.mdDoc ''
+          Whether to enable WireGuard.
+
+          Please note that {option}`systemd.network.netdevs` has more features
+          and is better maintained. When building new things, it is advised to
+          use that instead.
+        '';
         type = types.bool;
         # 2019-05-25: Backwards compatibility.
         default = cfg.interfaces != {};
@@ -444,7 +525,13 @@ in
       };
 
       interfaces = mkOption {
-        description = lib.mdDoc "WireGuard interfaces.";
+        description = lib.mdDoc ''
+          WireGuard interfaces.
+
+          Please note that {option}`systemd.network.netdevs` has more features
+          and is better maintained. When building new things, it is advised to
+          use that instead.
+        '';
         default = {};
         example = {
           wg0 = {
@@ -498,6 +585,8 @@ in
       // (mapAttrs' generateKeyServiceUnit
       (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
 
-  });
+      systemd.targets = mapAttrs' generateInterfaceTarget cfg.interfaces;
+    }
+  );
 
 }
diff --git a/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
index 59e408f63199..0595e9e6df23 100644
--- a/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
@@ -121,11 +121,15 @@ let
         ''}
 
         # substitute environment variables
-        ${pkgs.gawk}/bin/awk '{
-          for(varname in ENVIRON)
-            gsub("@"varname"@", ENVIRON[varname])
-          print
-        }' "${configFile}" > "${finalConfig}"
+        if [ -f "${configFile}" ]; then
+          ${pkgs.gawk}/bin/awk '{
+            for(varname in ENVIRON)
+              gsub("@"varname"@", ENVIRON[varname])
+            print
+          }' "${configFile}" > "${finalConfig}"
+        else
+          touch "${finalConfig}"
+        fi
 
         iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
 
@@ -164,19 +168,19 @@ let
 in {
   options = {
     networking.wireless = {
-      enable = mkEnableOption "wpa_supplicant";
+      enable = mkEnableOption (lib.mdDoc "wpa_supplicant");
 
       interfaces = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "wlan0" "wlan1" ];
-        description = ''
-          The interfaces <command>wpa_supplicant</command> will use. If empty, it will
+        description = lib.mdDoc ''
+          The interfaces {command}`wpa_supplicant` will use. If empty, it will
           automatically use all wireless interfaces.
 
-          <note><para>
-            A separate wpa_supplicant instance will be started for each interface.
-          </para></note>
+          ::: {.note}
+          A separate wpa_supplicant instance will be started for each interface.
+          :::
         '';
       };
 
@@ -186,13 +190,13 @@ in {
         description = lib.mdDoc "Force a specific wpa_supplicant driver.";
       };
 
-      allowAuxiliaryImperativeNetworks = mkEnableOption "support for imperative & declarative networks" // {
-        description = ''
+      allowAuxiliaryImperativeNetworks = mkEnableOption (lib.mdDoc "support for imperative & declarative networks") // {
+        description = lib.mdDoc ''
           Whether to allow configuring networks "imperatively" (e.g. via
-          <package>wpa_supplicant_gui</package>) and declaratively via
-          <xref linkend="opt-networking.wireless.networks"/>.
+          `wpa_supplicant_gui`) and declaratively via
+          [](#opt-networking.wireless.networks).
 
-          Please note that this adds a custom patch to <package>wpa_supplicant</package>.
+          Please note that this adds a custom patch to `wpa_supplicant`.
         '';
       };
 
@@ -222,24 +226,24 @@ in {
         type = types.nullOr types.path;
         default = null;
         example = "/run/secrets/wireless.env";
-        description = ''
-          File consisting of lines of the form <literal>varname=value</literal>
+        description = lib.mdDoc ''
+          File consisting of lines of the form `varname=value`
           to define variables for the wireless configuration.
 
-          See section "EnvironmentFile=" in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for a syntax reference.
+          See section "EnvironmentFile=" in {manpage}`systemd.exec(5)` for a syntax reference.
 
           Secrets (PSKs, passwords, etc.) can be provided without adding them to
           the world-readable Nix store by defining them in the environment file and
-          referring to them in option <option>networking.wireless.networks</option>
-          with the syntax <literal>@varname@</literal>. Example:
+          referring to them in option {option}`networking.wireless.networks`
+          with the syntax `@varname@`. Example:
 
-          <programlisting>
+          ```
           # content of /run/secrets/wireless.env
           PSK_HOME=mypassword
           PASS_WORK=myworkpassword
-          </programlisting>
+          ```
 
-          <programlisting>
+          ```
           # wireless-related configuration
           networking.wireless.environmentFile = "/run/secrets/wireless.env";
           networking.wireless.networks = {
@@ -250,7 +254,7 @@ in {
               password="@PASS_WORK@"
             ''';
           };
-          </programlisting>
+          ```
         '';
       };
 
@@ -260,36 +264,36 @@ in {
             psk = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The network's pre-shared key in plaintext defaulting
                 to being a network without any authentication.
 
-                <warning><para>
-                  Be aware that this will be written to the nix store
-                  in plaintext! Use an environment variable instead.
-                </para></warning>
+                ::: {.warning}
+                Be aware that this will be written to the nix store
+                in plaintext! Use an environment variable instead.
+                :::
 
-                <note><para>
-                  Mutually exclusive with <varname>pskRaw</varname>.
-                </para></note>
+                ::: {.note}
+                Mutually exclusive with {var}`pskRaw`.
+                :::
               '';
             };
 
             pskRaw = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The network's pre-shared key in hex defaulting
                 to being a network without any authentication.
 
-                <warning><para>
-                  Be aware that this will be written to the nix store
-                  in plaintext! Use an environment variable instead.
-                </para></warning>
+                ::: {.warning}
+                Be aware that this will be written to the nix store
+                in plaintext! Use an environment variable instead.
+                :::
 
-                <note><para>
-                  Mutually exclusive with <varname>psk</varname>.
-                </para></note>
+                ::: {.note}
+                Mutually exclusive with {var}`psk`.
+                :::
               '';
             };
 
@@ -343,21 +347,21 @@ in {
                 identity="user@example.com"
                 password="@EXAMPLE_PASSWORD@"
               '';
-              description = ''
+              description = lib.mdDoc ''
                 Use this option to configure advanced authentication methods like EAP.
                 See
-                <citerefentry><refentrytitle>wpa_supplicant.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+                {manpage}`wpa_supplicant.conf(5)`
                 for example configurations.
 
-                <warning><para>
-                  Be aware that this will be written to the nix store
-                  in plaintext! Use an environment variable for secrets.
-                </para></warning>
+                ::: {.warning}
+                Be aware that this will be written to the nix store
+                in plaintext! Use an environment variable for secrets.
+                :::
 
-                <note><para>
-                  Mutually exclusive with <varname>psk</varname> and
-                  <varname>pskRaw</varname>.
-                </para></note>
+                ::: {.note}
+                Mutually exclusive with {var}`psk` and
+                {var}`pskRaw`.
+                :::
               '';
             };
 
diff --git a/nixpkgs/nixos/modules/services/networking/wstunnel.nix b/nixpkgs/nixos/modules/services/networking/wstunnel.nix
new file mode 100644
index 000000000000..067d5df48725
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wstunnel.nix
@@ -0,0 +1,429 @@
+{ config, lib, options, pkgs, utils, ... }:
+with lib;
+let
+  cfg = config.services.wstunnel;
+  attrsToArgs = attrs: utils.escapeSystemdExecArgs (
+    mapAttrsToList
+    (name: value: if value == true then "--${name}" else "--${name}=${value}")
+    attrs
+  );
+  hostPortSubmodule = {
+    options = {
+      host = mkOption {
+        description = mdDoc "The hostname.";
+        type = types.str;
+      };
+      port = mkOption {
+        description = mdDoc "The port.";
+        type = types.port;
+      };
+    };
+  };
+  localRemoteSubmodule = {
+    options = {
+      local = mkOption {
+        description = mdDoc "Local address and port to listen on.";
+        type = types.submodule hostPortSubmodule;
+        example = {
+          host = "127.0.0.1";
+          port = 51820;
+        };
+      };
+      remote = mkOption {
+        description = mdDoc "Address and port on remote to forward traffic to.";
+        type = types.submodule hostPortSubmodule;
+        example = {
+          host = "127.0.0.1";
+          port = 51820;
+        };
+      };
+    };
+  };
+  hostPortToString = { host, port }: "${host}:${builtins.toString port}";
+  localRemoteToString = { local, remote }: utils.escapeSystemdExecArg "${hostPortToString local}:${hostPortToString remote}";
+  commonOptions = {
+    enable = mkOption {
+      description = mdDoc "Whether to enable this `wstunnel` instance.";
+      type = types.bool;
+      default = true;
+    };
+
+    package = mkPackageOptionMD pkgs "wstunnel" {};
+
+    autoStart = mkOption {
+      description = mdDoc "Whether this tunnel server should be started automatically.";
+      type = types.bool;
+      default = true;
+    };
+
+    extraArgs = mkOption {
+      description = mdDoc "Extra command line arguments to pass to `wstunnel`. Attributes of the form `argName = true;` will be translated to `--argName`, and `argName = \"value\"` to `--argName=value`.";
+      type = with types; attrsOf (either str bool);
+      default = {};
+      example = {
+        "someNewOption" = true;
+        "someNewOptionWithValue" = "someValue";
+      };
+    };
+
+    verboseLogging = mkOption {
+      description = mdDoc "Enable verbose logging.";
+      type = types.bool;
+      default = false;
+    };
+
+    environmentFile = mkOption {
+      description = mdDoc "Environment file to be passed to the systemd service. Useful for passing secrets to the service to prevent them from being world-readable in the Nix store. Note however that the secrets are passed to `wstunnel` through the command line, which makes them locally readable for all users of the system at runtime.";
+      type = types.nullOr types.path;
+      default = null;
+      example = "/var/lib/secrets/wstunnelSecrets";
+    };
+  };
+
+  serverSubmodule = { config, ...}: {
+    options = commonOptions // {
+      listen = mkOption {
+        description = mdDoc "Address and port to listen on. Setting the port to a value below 1024 will also give the process the required `CAP_NET_BIND_SERVICE` capability.";
+        type = types.submodule hostPortSubmodule;
+        default = {
+          address = "0.0.0.0";
+          port = if config.enableHTTPS then 443 else 80;
+        };
+        defaultText = literalExpression ''
+          {
+            address = "0.0.0.0";
+            port = if enableHTTPS then 443 else 80;
+          }
+        '';
+      };
+
+      restrictTo = mkOption {
+        description = mdDoc "Accepted traffic will be forwarded only to this service. Set to `null` to allow forwarding to arbitrary addresses.";
+        type = types.nullOr (types.submodule hostPortSubmodule);
+        example = {
+          host = "127.0.0.1";
+          port = 51820;
+        };
+      };
+
+      enableHTTPS = mkOption {
+        description = mdDoc "Use HTTPS for the tunnel server.";
+        type = types.bool;
+        default = true;
+      };
+
+      tlsCertificate = mkOption {
+        description = mdDoc "TLS certificate to use instead of the hardcoded one in case of HTTPS connections. Use together with `tlsKey`.";
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/secrets/cert.pem";
+      };
+
+      tlsKey = mkOption {
+        description = mdDoc "TLS key to use instead of the hardcoded on in case of HTTPS connections. Use together with `tlsCertificate`.";
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/secrets/key.pem";
+      };
+
+      useACMEHost = mkOption {
+        description = mdDoc "Use a certificate generated by the NixOS ACME module for the given host. Note that this will not generate a new certificate - you will need to do so with `security.acme.certs`.";
+        type = types.nullOr types.str;
+        default = null;
+        example = "example.com";
+      };
+    };
+  };
+  clientSubmodule = { config, ... }: {
+    options = commonOptions // {
+      connectTo = mkOption {
+        description = mdDoc "Server address and port to connect to.";
+        type = types.submodule hostPortSubmodule;
+        example = {
+          host = "example.com";
+        };
+      };
+
+      enableHTTPS = mkOption {
+        description = mdDoc "Enable HTTPS when connecting to the server.";
+        type = types.bool;
+        default = true;
+      };
+
+      localToRemote = mkOption {
+        description = mdDoc "Local hosts and ports to listen on, plus the hosts and ports on remote to forward traffic to. Setting a local port to a value less than 1024 will additionally give the process the required CAP_NET_BIND_SERVICE capability.";
+        type = types.listOf (types.submodule localRemoteSubmodule);
+        default = [];
+        example = [ {
+          local = {
+            host = "127.0.0.1";
+            port = 8080;
+          };
+          remote = {
+            host = "127.0.0.1";
+            port = 8080;
+          };
+        } ];
+      };
+
+      dynamicToRemote = mkOption {
+        description = mdDoc "Host and port for the SOCKS5 proxy to dynamically forward traffic to. Leave this at `null` to disable the SOCKS5 proxy. Setting the port to a value less than 1024 will additionally give the service the required CAP_NET_BIND_SERVICE capability.";
+        type = types.nullOr (types.submodule hostPortSubmodule);
+        default = null;
+        example = {
+          host = "127.0.0.1";
+          port = 1080;
+        };
+      };
+
+      udp = mkOption {
+        description = mdDoc "Whether to forward UDP instead of TCP traffic.";
+        type = types.bool;
+        default = false;
+      };
+
+      udpTimeout = mkOption {
+        description = mdDoc "When using UDP forwarding, timeout in seconds after which the tunnel connection is closed. `-1` means no timeout.";
+        type = types.int;
+        default = 30;
+      };
+
+      httpProxy = mkOption {
+        description = mdDoc ''
+          Proxy to use to connect to the wstunnel server (`USER:PASS@HOST:PORT`).
+
+          ::: {.warning}
+          Passwords specified here will be world-readable in the Nix store! To pass a password to the service, point the `environmentFile` option to a file containing `PROXY_PASSWORD=<your-password-here>` and set this option to `<user>:$PROXY_PASSWORD@<host>:<port>`. Note however that this will also locally leak the passwords at runtime via e.g. /proc/<pid>/cmdline.
+
+          :::
+        '';
+        type = types.nullOr types.str;
+        default = null;
+      };
+
+      soMark = mkOption {
+        description = mdDoc "Mark network packets with the SO_MARK sockoption with the specified value. Setting this option will also enable the required `CAP_NET_ADMIN` capability for the systemd service.";
+        type = types.nullOr types.int;
+        default = null;
+      };
+
+      upgradePathPrefix = mkOption {
+        description = mdDoc "Use a specific HTTP path prefix that will show up in the upgrade request to the `wstunnel` server. Useful when running `wstunnel` behind a reverse proxy.";
+        type = types.nullOr types.str;
+        default = null;
+        example = "wstunnel";
+      };
+
+      hostHeader = mkOption {
+        description = mdDoc "Use this as the HTTP host header instead of the real hostname. Useful for circumventing hostname-based firewalls.";
+        type = types.nullOr types.str;
+        default = null;
+      };
+
+      tlsSNI = mkOption {
+        description = mdDoc "Use this as the SNI while connecting via TLS. Useful for circumventing hostname-based firewalls.";
+        type = types.nullOr types.str;
+        default = null;
+      };
+
+      tlsVerifyCertificate = mkOption {
+        description = mdDoc "Whether to verify the TLS certificate of the server. It might be useful to set this to `false` when working with the `tlsSNI` option.";
+        type = types.bool;
+        default = true;
+      };
+
+      # The original argument name `websocketPingFrequency` is a misnomer, as the frequency is the inverse of the interval.
+      websocketPingInterval = mkOption {
+        description = mdDoc "Do a heartbeat ping every N seconds to keep up the websocket connection.";
+        type = types.nullOr types.ints.unsigned;
+        default = null;
+      };
+
+      upgradeCredentials = mkOption {
+        description = mdDoc ''
+          Use these credentials to authenticate during the HTTP upgrade request (Basic authorization type, `USER:[PASS]`).
+
+          ::: {.warning}
+          Passwords specified here will be world-readable in the Nix store! To pass a password to the service, point the `environmentFile` option to a file containing `HTTP_PASSWORD=<your-password-here>` and set this option to `<user>:$HTTP_PASSWORD`. Note however that this will also locally leak the passwords at runtime via e.g. /proc/<pid>/cmdline.
+          :::
+        '';
+        type = types.nullOr types.str;
+        default = null;
+      };
+
+      customHeaders = mkOption {
+        description = mdDoc "Custom HTTP headers to send during the upgrade request.";
+        type = types.attrsOf types.str;
+        default = {};
+        example = {
+          "X-Some-Header" = "some-value";
+        };
+      };
+    };
+  };
+  generateServerUnit = name: serverCfg: {
+    name = "wstunnel-server-${name}";
+    value = {
+      description = "wstunnel server - ${name}";
+      requires = [ "network.target" "network-online.target" ];
+      after = [ "network.target" "network-online.target" ];
+      wantedBy = optional serverCfg.autoStart "multi-user.target";
+
+      serviceConfig = let
+        certConfig = config.security.acme.certs."${serverCfg.useACMEHost}";
+      in {
+        Type = "simple";
+        ExecStart = with serverCfg; let
+          resolvedTlsCertificate = if useACMEHost != null
+            then "${certConfig.directory}/fullchain.pem"
+            else tlsCertificate;
+          resolvedTlsKey = if useACMEHost != null
+            then "${certConfig.directory}/key.pem"
+            else tlsKey;
+        in ''
+          ${package}/bin/wstunnel \
+            --server \
+            ${optionalString (restrictTo != null)     "--restrictTo=${utils.escapeSystemdExecArg (hostPortToString restrictTo)}"} \
+            ${optionalString (resolvedTlsCertificate != null) "--tlsCertificate=${utils.escapeSystemdExecArg resolvedTlsCertificate}"} \
+            ${optionalString (resolvedTlsKey != null)         "--tlsKey=${utils.escapeSystemdExecArg resolvedTlsKey}"} \
+            ${optionalString verboseLogging "--verbose"} \
+            ${attrsToArgs extraArgs} \
+            ${utils.escapeSystemdExecArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"}
+        '';
+        EnvironmentFile = optional (serverCfg.environmentFile != null) serverCfg.environmentFile;
+        DynamicUser = true;
+        SupplementaryGroups = optional (serverCfg.useACMEHost != null) certConfig.group;
+        PrivateTmp = true;
+        AmbientCapabilities = optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        NoNewPrivileges = true;
+        RestrictNamespaces = "uts ipc pid user cgroup";
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        PrivateDevices = true;
+        RestrictSUIDSGID = true;
+
+      };
+    };
+  };
+  generateClientUnit = name: clientCfg: {
+    name = "wstunnel-client-${name}";
+    value = {
+      description = "wstunnel client - ${name}";
+      requires = [ "network.target" "network-online.target" ];
+      after = [ "network.target" "network-online.target" ];
+      wantedBy = optional clientCfg.autoStart "multi-user.target";
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = with clientCfg; ''
+          ${package}/bin/wstunnel \
+            ${concatStringsSep " " (builtins.map (x:          "--localToRemote=${localRemoteToString x}") localToRemote)} \
+            ${concatStringsSep " " (mapAttrsToList (n: v:     "--customHeaders=\"${n}: ${v}\"") customHeaders)} \
+            ${optionalString (dynamicToRemote != null)        "--dynamicToRemote=${utils.escapeSystemdExecArg (hostPortToString dynamicToRemote)}"} \
+            ${optionalString udp                              "--udp"} \
+            ${optionalString (httpProxy != null)              "--httpProxy=${httpProxy}"} \
+            ${optionalString (soMark != null)                 "--soMark=${toString soMark}"} \
+            ${optionalString (upgradePathPrefix != null)      "--upgradePathPrefix=${upgradePathPrefix}"} \
+            ${optionalString (hostHeader != null)             "--hostHeader=${hostHeader}"} \
+            ${optionalString (tlsSNI != null)                 "--tlsSNI=${tlsSNI}"} \
+            ${optionalString tlsVerifyCertificate             "--tlsVerifyCertificate"} \
+            ${optionalString (websocketPingInterval != null)  "--websocketPingFrequency=${toString websocketPingInterval}"} \
+            ${optionalString (upgradeCredentials != null)     "--upgradeCredentials=${upgradeCredentials}"} \
+            --udpTimeoutSec=${toString udpTimeout} \
+            ${optionalString verboseLogging "--verbose"} \
+            ${attrsToArgs extraArgs} \
+            ${utils.escapeSystemdExecArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString connectTo}"}
+        '';
+        EnvironmentFile = optional (clientCfg.environmentFile != null) clientCfg.environmentFile;
+        DynamicUser = true;
+        PrivateTmp = true;
+        AmbientCapabilities = (optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optionals ((clientCfg.dynamicToRemote.port or 1024) < 1024 || (any (x: x.local.port < 1024) clientCfg.localToRemote)) [ "CAP_NET_BIND_SERVICE" ]);
+        NoNewPrivileges = true;
+        RestrictNamespaces = "uts ipc pid user cgroup";
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        PrivateDevices = true;
+        RestrictSUIDSGID = true;
+      };
+    };
+  };
+in {
+  options.services.wstunnel = {
+    enable = mkEnableOption (mdDoc "wstunnel");
+
+    servers = mkOption {
+      description = mdDoc "`wstunnel` servers to set up.";
+      type = types.attrsOf (types.submodule serverSubmodule);
+      default = {};
+      example = {
+        "wg-tunnel" = {
+          listen.port = 8080;
+          enableHTTPS = true;
+          tlsCertificate = "/var/lib/secrets/fullchain.pem";
+          tlsKey = "/var/lib/secrets/key.pem";
+          restrictTo = {
+            host = "127.0.0.1";
+            port = 51820;
+          };
+        };
+      };
+    };
+
+    clients = mkOption {
+      description = mdDoc "`wstunnel` clients to set up.";
+      type = types.attrsOf (types.submodule clientSubmodule);
+      default = {};
+      example = {
+        "wg-tunnel" = {
+          connectTo = {
+            host = "example.com";
+            port = 8080;
+          };
+          enableHTTPS = true;
+          localToRemote = {
+            local = {
+              host = "127.0.0.1";
+              port = 51820;
+            };
+            remote = {
+              host = "127.0.0.1";
+              port = 51820;
+            };
+          };
+          udp = true;
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services = (mapAttrs' generateServerUnit (filterAttrs (n: v: v.enable) cfg.servers)) // (mapAttrs' generateClientUnit (filterAttrs (n: v: v.enable) cfg.clients));
+
+    assertions = (mapAttrsToList (name: serverCfg: {
+      assertion = !(serverCfg.useACMEHost != null && (serverCfg.tlsCertificate != null || serverCfg.tlsKey != null));
+      message = ''
+        Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive.
+      '';
+    }) cfg.servers) ++
+    (mapAttrsToList (name: serverCfg: {
+      assertion = !((serverCfg.tlsCertificate != null || serverCfg.tlsKey != null) && !(serverCfg.tlsCertificate != null && serverCfg.tlsKey != null));
+      message = ''
+        services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together.
+      '';
+    }) cfg.servers) ++
+    (mapAttrsToList (name: clientCfg: {
+      assertion = !(clientCfg.localToRemote == [] && clientCfg.dynamicToRemote == null);
+      message = ''
+        Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".dynamicToRemote must be set.
+      '';
+    }) cfg.clients);
+  };
+
+  meta.maintainers = with maintainers; [ alyaeanyx ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/x2goserver.nix b/nixpkgs/nixos/modules/services/networking/x2goserver.nix
index 3c2424b6f4f9..1242229a0b60 100644
--- a/nixpkgs/nixos/modules/services/networking/x2goserver.nix
+++ b/nixpkgs/nixos/modules/services/networking/x2goserver.nix
@@ -22,16 +22,16 @@ in {
   ];
 
   options.services.x2goserver = {
-    enable = mkEnableOption "x2goserver" // {
-      description = ''
+    enable = mkEnableOption (lib.mdDoc "x2goserver") // {
+      description = lib.mdDoc ''
         Enables the x2goserver module.
         NOTE: This will create a good amount of symlinks in `/usr/local/bin`
       '';
     };
 
     superenicer = {
-      enable = mkEnableOption "superenicer" // {
-        description = ''
+      enable = mkEnableOption (lib.mdDoc "superenicer") // {
+        description = lib.mdDoc ''
           Enables the SupeReNicer code in x2gocleansessions, this will renice
           suspended sessions to nice level 19 and renice them to level 0 if the
           session becomes marked as running again
@@ -50,7 +50,7 @@ in {
     settings = mkOption {
       type = types.attrsOf types.attrs;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         x2goserver.conf ini configuration as nix attributes. See
         `x2goserver.conf(5)` for details
       '';
diff --git a/nixpkgs/nixos/modules/services/networking/xandikos.nix b/nixpkgs/nixos/modules/services/networking/xandikos.nix
index 649e9c7a668e..6d1ddc74c719 100644
--- a/nixpkgs/nixos/modules/services/networking/xandikos.nix
+++ b/nixpkgs/nixos/modules/services/networking/xandikos.nix
@@ -9,7 +9,7 @@ in
 
   options = {
     services.xandikos = {
-      enable = mkEnableOption "Xandikos CalDAV and CardDAV server";
+      enable = mkEnableOption (lib.mdDoc "Xandikos CalDAV and CardDAV server");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/xinetd.nix b/nixpkgs/nixos/modules/services/networking/xinetd.nix
index 6c633d4ead16..fb3de7077e31 100644
--- a/nixpkgs/nixos/modules/services/networking/xinetd.nix
+++ b/nixpkgs/nixos/modules/services/networking/xinetd.nix
@@ -27,7 +27,7 @@ let
         ${optionalString srv.unlisted "type        = UNLISTED"}
         ${optionalString (srv.flags != "") "flags = ${srv.flags}"}
         socket_type = ${if srv.protocol == "udp" then "dgram" else "stream"}
-        ${if srv.port != 0 then "port        = ${toString srv.port}" else ""}
+        ${optionalString (srv.port != 0) "port        = ${toString srv.port}"}
         wait        = ${if srv.protocol == "udp" then "yes" else "no"}
         user        = ${srv.user}
         server      = ${srv.server}
@@ -44,7 +44,7 @@ in
 
   options = {
 
-    services.xinetd.enable = mkEnableOption "the xinetd super-server daemon";
+    services.xinetd.enable = mkEnableOption (lib.mdDoc "the xinetd super-server daemon");
 
     services.xinetd.extraDefaults = mkOption {
       default = "";
@@ -78,7 +78,7 @@ in
           };
 
           port = mkOption {
-            type = types.int;
+            type = types.port;
             default = 0;
             example = 123;
             description = lib.mdDoc "Port number of the service.";
@@ -105,7 +105,7 @@ in
           flags = mkOption {
             type = types.str;
             default = "";
-            description = "";
+            description = lib.mdDoc "";
           };
 
           unlisted = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/xl2tpd.nix b/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
index c30a541d30ef..7d2595707612 100644
--- a/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.xl2tpd = {
-      enable = mkEnableOption "xl2tpd, the Layer 2 Tunnelling Protocol Daemon";
+      enable = mkEnableOption (lib.mdDoc "xl2tpd, the Layer 2 Tunnelling Protocol Daemon");
 
       serverIp = mkOption {
         type        = types.str;
@@ -84,7 +84,7 @@ with lib;
       xl2tpd-ppp-wrapped = pkgs.stdenv.mkDerivation {
         name         = "xl2tpd-ppp-wrapped";
         phases       = [ "installPhase" ];
-        buildInputs  = with pkgs; [ makeWrapper ];
+        nativeBuildInputs  = with pkgs; [ makeWrapper ];
         installPhase = ''
           mkdir -p $out/bin
 
diff --git a/nixpkgs/nixos/modules/services/networking/xray.nix b/nixpkgs/nixos/modules/services/networking/xray.nix
new file mode 100644
index 000000000000..83655a2f88ef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xray.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  options = {
+
+    services.xray = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to run xray server.
+
+          Either `settingsFile` or `settings` must be specified.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.xray;
+        defaultText = literalExpression "pkgs.xray";
+        description = lib.mdDoc ''
+          Which xray package to use.
+        '';
+      };
+
+      settingsFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/etc/xray/config.json";
+        description = lib.mdDoc ''
+          The absolute path to the configuration file.
+
+          Either `settingsFile` or `settings` must be specified.
+
+          See <https://www.v2fly.org/en_US/config/overview.html>.
+        '';
+      };
+
+      settings = mkOption {
+        type = types.nullOr (types.attrsOf types.unspecified);
+        default = null;
+        example = {
+          inbounds = [{
+            port = 1080;
+            listen = "127.0.0.1";
+            protocol = "http";
+          }];
+          outbounds = [{
+            protocol = "freedom";
+          }];
+        };
+        description = lib.mdDoc ''
+          The configuration object.
+
+          Either `settingsFile` or `settings` must be specified.
+
+          See <https://www.v2fly.org/en_US/config/overview.html>.
+        '';
+      };
+    };
+
+  };
+
+  config = let
+    cfg = config.services.xray;
+    settingsFile = if cfg.settingsFile != null
+      then cfg.settingsFile
+      else pkgs.writeTextFile {
+        name = "xray.json";
+        text = builtins.toJSON cfg.settings;
+        checkPhase = ''
+          ${cfg.package}/bin/xray -test -config $out
+        '';
+      };
+
+  in mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.settingsFile == null) != (cfg.settings == null);
+        message = "Either but not both `settingsFile` and `settings` should be specified for xray.";
+      }
+    ];
+
+    systemd.services.xray = {
+      description = "xray Daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/xray -config ${settingsFile}";
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+        NoNewPrivileges = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xrdp.nix b/nixpkgs/nixos/modules/services/networking/xrdp.nix
index 17caeab27264..ed7f1dadd370 100644
--- a/nixpkgs/nixos/modules/services/networking/xrdp.nix
+++ b/nixpkgs/nixos/modules/services/networking/xrdp.nix
@@ -42,7 +42,7 @@ in
 
     services.xrdp = {
 
-      enable = mkEnableOption "xrdp, the Remote Desktop Protocol server";
+      enable = mkEnableOption (lib.mdDoc "xrdp, the Remote Desktop Protocol server");
 
       package = mkOption {
         type = types.package;
@@ -54,7 +54,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3389;
         description = lib.mdDoc ''
           Specifies on which port the xrdp daemon listens.
@@ -100,7 +100,7 @@ in
       confDir = mkOption {
         type = types.path;
         default = confDir;
-        defaultText = literalDocBook "generated from configuration";
+        defaultText = literalMD "generated from configuration";
         description = lib.mdDoc "The location of the config files for xrdp.";
       };
     };
diff --git a/nixpkgs/nixos/modules/services/networking/yggdrasil.xml b/nixpkgs/nixos/modules/services/networking/yggdrasil.md
index a341d5d8153b..bbaea5bc74aa 100644
--- a/nixpkgs/nixos/modules/services/networking/yggdrasil.xml
+++ b/nixpkgs/nixos/modules/services/networking/yggdrasil.md
@@ -1,25 +1,18 @@
-<?xml version="1.0"?>
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-networking-yggdrasil">
-  <title>Yggdrasil</title>
-  <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/networking/yggdrasil/default.nix</filename>
-  </para>
-  <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://yggdrasil-network.github.io/"/>
-  </para>
-  <para>
+# Yggdrasil {#module-services-networking-yggdrasil}
+
+*Source:* {file}`modules/services/networking/yggdrasil/default.nix`
+
+*Upstream documentation:* <https://yggdrasil-network.github.io/>
+
 Yggdrasil is an early-stage implementation of a fully end-to-end encrypted,
 self-arranging IPv6 network.
-</para>
-  <section xml:id="module-services-networking-yggdrasil-configuration">
-    <title>Configuration</title>
-    <section xml:id="module-services-networking-yggdrasil-configuration-simple">
-      <title>Simple ephemeral node</title>
-      <para>
+
+## Configuration {#module-services-networking-yggdrasil-configuration}
+
+### Simple ephemeral node {#module-services-networking-yggdrasil-configuration-simple}
+
 An annotated example of a simple configuration:
-<programlisting>
+```
 {
   services.yggdrasil = {
     enable = true;
@@ -27,10 +20,10 @@ An annotated example of a simple configuration:
       # The NixOS module will generate new keys and a new IPv6 address each time
       # it is started if persistentKeys is not enabled.
 
-    config = {
+    settings = {
       Peers = [
         # Yggdrasil will automatically connect and "peer" with other nodes it
-        # discovers via link-local multicast annoucements. Unless this is the
+        # discovers via link-local multicast announcements. Unless this is the
         # case (it probably isn't) a node needs peers within the existing
         # network that it can tunnel to.
         "tcp://1.2.3.4:1024"
@@ -41,14 +34,12 @@ An annotated example of a simple configuration:
     };
   };
 }
-</programlisting>
-   </para>
-    </section>
-    <section xml:id="module-services-networking-yggdrasil-configuration-prefix">
-      <title>Persistent node with prefix</title>
-      <para>
+```
+
+### Persistent node with prefix {#module-services-networking-yggdrasil-configuration-prefix}
+
 A node with a fixed address that announces a prefix:
-<programlisting>
+```
 let
   address = "210:5217:69c0:9afc:1b95:b9f:8718:c3d2";
   prefix = "310:5217:69c0:9afc";
@@ -58,7 +49,7 @@ in {
   services.yggdrasil = {
     enable = true;
     persistentKeys = true; # Maintain a fixed public key and IPv6 address.
-    config = {
+    settings = {
       Peers = [ "tcp://1.2.3.4:1024" "tcp://1.2.3.5:1024" ];
       NodeInfo = {
         # This information is visible to the network.
@@ -78,7 +69,7 @@ in {
   }];
 
   services.radvd = {
-    # Annouce the 300::/8 prefix to eth0.
+    # Announce the 300::/8 prefix to eth0.
     enable = true;
     config = ''
       interface eth0
@@ -93,15 +84,13 @@ in {
     '';
   };
 }
-</programlisting>
-  </para>
-    </section>
-    <section xml:id="module-services-networking-yggdrasil-configuration-container">
-      <title>Yggdrasil attached Container</title>
-      <para>
+```
+
+### Yggdrasil attached Container {#module-services-networking-yggdrasil-configuration-container}
+
 A NixOS container attached to the Yggdrasil network via a node running on the
 host:
-        <programlisting>
+```
 let
   yggPrefix64 = "310:5217:69c0:9afc";
     # Again, taken from the output of "yggdrasilctl getself".
@@ -112,10 +101,10 @@ in
 
   networking = {
     bridges.br0.interfaces = [ ];
-    # A bridge only to containers&#x2026;
+    # A bridge only to containers…
 
     interfaces.br0 = {
-      # &#x2026; configured with a prefix address.
+      # … configured with a prefix address.
       ipv6.addresses = [{
         address = "${yggPrefix64}::1";
         prefixLength = 64;
@@ -149,8 +138,4 @@ in
   };
 
 }
-</programlisting>
-      </para>
-    </section>
-  </section>
-</chapter>
+```
diff --git a/nixpkgs/nixos/modules/services/networking/yggdrasil.nix b/nixpkgs/nixos/modules/services/networking/yggdrasil.nix
index 81ed6d1dd566..55a6002d61af 100644
--- a/nixpkgs/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixpkgs/nixos/modules/services/networking/yggdrasil.nix
@@ -8,7 +8,8 @@ let
   configFileProvided = cfg.configFile != null;
 
   format = pkgs.formats.json { };
-in {
+in
+{
   imports = [
     (mkRenamedOptionModule
       [ "services" "yggdrasil" "config" ]
@@ -17,11 +18,11 @@ in {
 
   options = with types; {
     services.yggdrasil = {
-      enable = mkEnableOption "the yggdrasil system service";
+      enable = mkEnableOption (lib.mdDoc "the yggdrasil system service");
 
       settings = mkOption {
         type = format.type;
-        default = {};
+        default = { };
         example = {
           Peers = [
             "tcp://aa.bb.cc.dd:eeeee"
@@ -31,29 +32,28 @@ in {
             "tcp://0.0.0.0:xxxxx"
           ];
         };
-        description = ''
+        description = lib.mdDoc ''
           Configuration for yggdrasil, as a Nix attribute set.
 
           Warning: this is stored in the WORLD-READABLE Nix store!
           Therefore, it is not appropriate for private keys. If you
-          wish to specify the keys, use <option>configFile</option>.
+          wish to specify the keys, use {option}`configFile`.
 
-          If the <option>persistentKeys</option> is enabled then the
+          If the {option}`persistentKeys` is enabled then the
           keys that are generated during activation will override
-          those in <option>config</option> or
-          <option>configFile</option>.
+          those in {option}`settings` or
+          {option}`configFile`.
 
           If no keys are specified then ephemeral keys are generated
           and the Yggdrasil interface will have a random IPv6 address
-          each time the service is started, this is the default.
+          each time the service is started. This is the default.
 
-          If both <option>configFile</option> and <option>config</option>
+          If both {option}`configFile` and {option}`settings`
           are supplied, they will be combined, with values from
-          <option>configFile</option> taking precedence.
+          {option}`configFile` taking precedence.
 
-          You can use the command <literal>nix-shell -p yggdrasil --run
-          "yggdrasil -genconf"</literal> to generate default
-          configuration values with documentation.
+          You can use the command `nix-shell -p yggdrasil --run "yggdrasil -genconf"`
+          to generate default configuration values with documentation.
         '';
       };
 
@@ -62,8 +62,13 @@ in {
         default = null;
         example = "/run/keys/yggdrasil.conf";
         description = lib.mdDoc ''
-          A file which contains JSON configuration for yggdrasil.
-          See the {option}`config` option for more information.
+          A file which contains JSON or HJSON configuration for yggdrasil. See
+          the {option}`settings` option for more information.
+
+          Note: This file must not be larger than 1 MB because it is passed to
+          the yggdrasil process via systemd‘s LoadCredential mechanism. For
+          details, see <https://systemd.io/CREDENTIALS/> and `man 5
+          systemd.exec`.
         '';
       };
 
@@ -78,20 +83,20 @@ in {
         type = bool;
         default = false;
         description = lib.mdDoc ''
-          Whether to open the UDP port used for multicast peer
-          discovery. The NixOS firewall blocks link-local
-          communication, so in order to make local peering work you
-          will also need to set `LinkLocalTCPPort` in your
-          yggdrasil configuration ({option}`config` or
-          {option}`configFile`) to a port number other than 0,
-          and then add that port to
-          {option}`networking.firewall.allowedTCPPorts`.
+          Whether to open the UDP port used for multicast peer discovery. The
+          NixOS firewall blocks link-local communication, so in order to make
+          incoming local peering work you will also need to configure
+          `MulticastInterfaces` in your Yggdrasil configuration
+          ({option}`settings` or {option}`configFile`). You will then have to
+          add the ports that you configure there to your firewall configuration
+          ({option}`networking.firewall.allowedTCPPorts` or
+          {option}`networking.firewall.interfaces.<name>.allowedTCPPorts`).
         '';
       };
 
       denyDhcpcdInterfaces = mkOption {
         type = listOf str;
-        default = [];
+        default = [ ];
         example = [ "tap*" ];
         description = lib.mdDoc ''
           Disable the DHCP client for any interface whose name matches
@@ -110,91 +115,113 @@ in {
         description = lib.mdDoc "Yggdrasil package to use.";
       };
 
-      persistentKeys = mkEnableOption ''
+      persistentKeys = mkEnableOption (lib.mdDoc ''
         If enabled then keys will be generated once and Yggdrasil
         will retain the same IPv6 address when the service is
         restarted. Keys are stored at ${keysPath}.
-      '';
+      '');
 
     };
   };
 
-  config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil";
-  in {
-    assertions = [{
-      assertion = config.networking.enableIPv6;
-      message = "networking.enableIPv6 must be true for yggdrasil to work";
-    }];
-
-    system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
-      if [ ! -e ${keysPath} ]
-      then
-        mkdir --mode=700 -p ${builtins.dirOf keysPath}
-        ${binYggdrasil} -genconf -json \
-          | ${pkgs.jq}/bin/jq \
-              'to_entries|map(select(.key|endswith("Key")))|from_entries' \
-          > ${keysPath}
-      fi
-    '';
-
-    systemd.services.yggdrasil = {
-      description = "Yggdrasil Network Service";
-      after = [ "network-pre.target" ];
-      wants = [ "network.target" ];
-      before = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart =
-        (if settingsProvided || configFileProvided || cfg.persistentKeys then
-          "echo "
-
-          + (lib.optionalString settingsProvided
-            "'${builtins.toJSON cfg.settings}'")
-          + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})")
-          + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
-          + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
-        else
-          "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf";
-
-      serviceConfig = {
-        ExecStart =
-          "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        Restart = "always";
-
-        DynamicUser = true;
-        StateDirectory = "yggdrasil";
-        RuntimeDirectory = "yggdrasil";
-        RuntimeDirectoryMode = "0750";
-        BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile
-          ++ lib.optional cfg.persistentKeys keysPath;
-        ReadWritePaths = "/run/yggdrasil";
-
-        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
-        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
-        MemoryDenyWriteExecute = true;
-        ProtectControlGroups = true;
-        ProtectHome = "tmpfs";
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources";
-      } // (if (cfg.group != null) then {
-        Group = cfg.group;
-      } else {});
-    };
+  config = mkIf cfg.enable (
+    let
+      binYggdrasil = "${cfg.package}/bin/yggdrasil";
+      binHjson = "${pkgs.hjson-go}/bin/hjson-cli";
+    in
+    {
+      assertions = [{
+        assertion = config.networking.enableIPv6;
+        message = "networking.enableIPv6 must be true for yggdrasil to work";
+      }];
+
+      system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
+        if [ ! -e ${keysPath} ]
+        then
+          mkdir --mode=700 -p ${builtins.dirOf keysPath}
+          ${binYggdrasil} -genconf -json \
+            | ${pkgs.jq}/bin/jq \
+                'to_entries|map(select(.key|endswith("Key")))|from_entries' \
+            > ${keysPath}
+        fi
+      '';
+
+      systemd.services.yggdrasil = {
+        description = "Yggdrasil Network Service";
+        after = [ "network-pre.target" ];
+        wants = [ "network.target" ];
+        before = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        # This script first prepares the config file, then it starts Yggdrasil.
+        # The preparation could also be done in ExecStartPre/preStart but only
+        # systemd versions >= v252 support reading credentials in ExecStartPre. As
+        # of February 2023, systemd v252 is not yet in the stable branch of NixOS.
+        #
+        # This could be changed in the future once systemd version v252 has
+        # reached NixOS but it does not have to be. Config file preparation is
+        # fast enough, it does not need elevated privileges, and `set -euo
+        # pipefail` should make sure that the service is not started if the
+        # preparation fails. Therefore, it is not necessary to move the
+        # preparation to ExecStartPre.
+        script = ''
+          set -euo pipefail
+
+          # prepare config file
+          ${(if settingsProvided || configFileProvided || cfg.persistentKeys then
+            "echo "
+
+            + (lib.optionalString settingsProvided
+              "'${builtins.toJSON cfg.settings}'")
+            + (lib.optionalString configFileProvided
+              "$(${binHjson} -c \"$CREDENTIALS_DIRECTORY/yggdrasil.conf\")")
+            + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
+            + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
+          else
+            "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
+
+          # start yggdrasil
+          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf
+        '';
+
+        serviceConfig = {
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          Restart = "always";
+
+          DynamicUser = true;
+          StateDirectory = "yggdrasil";
+          RuntimeDirectory = "yggdrasil";
+          RuntimeDirectoryMode = "0750";
+          BindReadOnlyPaths = lib.optional cfg.persistentKeys keysPath;
+          LoadCredential =
+            mkIf configFileProvided "yggdrasil.conf:${cfg.configFile}";
+
+          AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+          MemoryDenyWriteExecute = true;
+          ProtectControlGroups = true;
+          ProtectHome = "tmpfs";
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
+        } // (if (cfg.group != null) then {
+          Group = cfg.group;
+        } else { });
+      };
 
-    networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
-    networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
+      networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
+      networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
 
-    # Make yggdrasilctl available on the command line.
-    environment.systemPackages = [ cfg.package ];
-  });
+      # Make yggdrasilctl available on the command line.
+      environment.systemPackages = [ cfg.package ];
+    }
+  );
   meta = {
-    doc = ./yggdrasil.xml;
+    doc = ./yggdrasil.md;
     maintainers = with lib.maintainers; [ gazally ehmry ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/networking/zerobin.nix b/nixpkgs/nixos/modules/services/networking/zerobin.nix
index 0be694915cb1..9e07666f3e14 100644
--- a/nixpkgs/nixos/modules/services/networking/zerobin.nix
+++ b/nixpkgs/nixos/modules/services/networking/zerobin.nix
@@ -12,7 +12,7 @@ in
   {
     options = {
       services.zerobin = {
-        enable = mkEnableOption "0bin";
+        enable = mkEnableOption (lib.mdDoc "0bin");
 
         dataDir = mkOption {
           type = types.str;
diff --git a/nixpkgs/nixos/modules/services/networking/zeronet.nix b/nixpkgs/nixos/modules/services/networking/zeronet.nix
index 2245204d4557..1f3711bd0d72 100644
--- a/nixpkgs/nixos/modules/services/networking/zeronet.nix
+++ b/nixpkgs/nixos/modules/services/networking/zeronet.nix
@@ -17,7 +17,7 @@ let
   };
 in with lib; {
   options.services.zeronet = {
-    enable = mkEnableOption "zeronet";
+    enable = mkEnableOption (lib.mdDoc "zeronet");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/networking/zerotierone.nix b/nixpkgs/nixos/modules/services/networking/zerotierone.nix
index 572ae2e929d3..0d9e25cfc52c 100644
--- a/nixpkgs/nixos/modules/services/networking/zerotierone.nix
+++ b/nixpkgs/nixos/modules/services/networking/zerotierone.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.zerotierone;
 in
 {
-  options.services.zerotierone.enable = mkEnableOption "ZeroTierOne";
+  options.services.zerotierone.enable = mkEnableOption (lib.mdDoc "ZeroTierOne");
 
   options.services.zerotierone.joinNetworks = mkOption {
     default = [];
@@ -19,7 +19,7 @@ in
 
   options.services.zerotierone.port = mkOption {
     default = 9993;
-    type = types.int;
+    type = types.port;
     description = lib.mdDoc ''
       Network port used by ZeroTier.
     '';
diff --git a/nixpkgs/nixos/modules/services/networking/znc/default.nix b/nixpkgs/nixos/modules/services/networking/znc/default.nix
index 7ca28700daec..d3ba4a524197 100644
--- a/nixpkgs/nixos/modules/services/networking/znc/default.nix
+++ b/nixpkgs/nixos/modules/services/networking/znc/default.nix
@@ -81,7 +81,7 @@ in
 
   options = {
     services.znc = {
-      enable = mkEnableOption "ZNC";
+      enable = mkEnableOption (lib.mdDoc "ZNC");
 
       user = mkOption {
         default = "znc";
@@ -149,27 +149,27 @@ in
             };
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Configuration for ZNC, see
-          <link xlink:href="https://wiki.znc.in/Configuration"/> for details. The
+          <https://wiki.znc.in/Configuration> for details. The
           Nix value declared here will be translated directly to the xml-like
           format ZNC expects. This is much more flexible than the legacy options
-          under <option>services.znc.confOptions.*</option>, but also can't do
+          under {option}`services.znc.confOptions.*`, but also can't do
           any type checking.
 
-          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          You can use {command}`nix-instantiate --eval --strict '<nixpkgs/nixos>' -A config.services.znc.config`
           to view the current value. By default it contains a listener for port
           5000 with SSL enabled.
 
-          Nix attributes called <literal>extraConfig</literal> will be inserted
+          Nix attributes called `extraConfig` will be inserted
           verbatim into the resulting config file.
 
-          If <option>services.znc.useLegacyConfig</option> is turned on, the
-          option values in <option>services.znc.confOptions.*</option> will be
+          If {option}`services.znc.useLegacyConfig` is turned on, the
+          option values in {option}`services.znc.confOptions.*` will be
           gracefully be applied to this option.
 
           If you intend to update the configuration through this option, be sure
-          to enable <option>services.znc.mutable</option>, otherwise none of the
+          to disable {option}`services.znc.mutable`, otherwise none of the
           changes here will be applied after the initial deploy.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/networking/znc/options.nix b/nixpkgs/nixos/modules/services/networking/znc/options.nix
index 021fea9819a7..bd67ec86d513 100644
--- a/nixpkgs/nixos/modules/services/networking/znc/options.nix
+++ b/nixpkgs/nixos/modules/services/networking/znc/options.nix
@@ -18,7 +18,7 @@ let
       };
 
       port = mkOption {
-        type = types.ints.u16;
+        type = types.port;
         default = 6697;
         description = lib.mdDoc ''
           IRC server port.
@@ -97,18 +97,18 @@ in
       useLegacyConfig = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to propagate the legacy options under
-          <option>services.znc.confOptions.*</option> to the znc config. If this
+          {option}`services.znc.confOptions.*` to the znc config. If this
           is turned on, the znc config will contain a user with the default name
           "znc", global modules "webadmin" and "adminlog" will be enabled by
           default, and more, all controlled through the
-          <option>services.znc.confOptions.*</option> options.
-          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          {option}`services.znc.confOptions.*` options.
+          You can use {command}`nix-instantiate --eval --strict '<nixpkgs/nixos>' -A config.services.znc.config`
           to view the current value of the config.
 
           In any case, if you need more flexibility,
-          <option>services.znc.config</option> can be used to override/add to
+          {option}`services.znc.config` can be used to override/add to
           all of the legacy options.
         '';
       };
@@ -118,7 +118,7 @@ in
           type = types.listOf types.str;
           default = [ "webadmin" "adminlog" ];
           example = [ "partyline" "webadmin" "adminlog" "log" ];
-          description = ''
+          description = lib.mdDoc ''
             A list of modules to include in the `znc.conf` file.
           '';
         };
@@ -127,7 +127,7 @@ in
           type = types.listOf types.str;
           default = [ "chansaver" "controlpanel" ];
           example = [ "chansaver" "controlpanel" "fish" "push" ];
-          description = ''
+          description = lib.mdDoc ''
             A list of user modules to include in the `znc.conf` file.
           '';
         };
@@ -177,18 +177,18 @@ in
             &lt;/Pass&gt;
           '';
           type = types.str;
-          description = ''
-            Generate with `nix-shell -p znc --command "znc --makepass"`.
+          description = lib.mdDoc ''
+            Generate with {command}`nix-shell -p znc --command "znc --makepass"`.
             This is the password used to log in to the ZNC web admin interface.
             You can also set this through
-            <option>services.znc.config.User.&lt;username&gt;.Pass.Method</option>
+            {option}`services.znc.config.User.<username>.Pass.Method`
             and co.
           '';
         };
 
         port = mkOption {
           default = 5000;
-          type = types.int;
+          type = types.port;
           description = lib.mdDoc ''
             Specifies the port on which to listen.
           '';
@@ -216,7 +216,7 @@ in
         extraZncConf = mkOption {
           default = "";
           type = types.lines;
-          description = ''
+          description = lib.mdDoc ''
             Extra config to `znc.conf` file.
           '';
         };
diff --git a/nixpkgs/nixos/modules/services/printing/cups-pdf.nix b/nixpkgs/nixos/modules/services/printing/cups-pdf.nix
new file mode 100644
index 000000000000..07f24367132f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/printing/cups-pdf.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  # cups calls its backends as user `lp` (which is good!),
+  # but cups-pdf wants to be called as `root`, so it can change ownership of files.
+  # We add a suid wrapper and a wrapper script to trick cups into calling the suid wrapper.
+  # Note that a symlink to the suid wrapper alone wouldn't suffice, cups would complain
+  # > File "/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-cups-progs/lib/cups/backend/cups-pdf" has insecure permissions (0104554/uid=0/gid=20)
+
+  # wrapper script that redirects calls to the suid wrapper
+  cups-pdf-wrapper = pkgs.writeTextFile {
+    name = "${pkgs.cups-pdf-to-pdf.name}-wrapper.sh";
+    executable = true;
+    destination = "/lib/cups/backend/cups-pdf";
+    checkPhase = ''
+      ${pkgs.stdenv.shellDryRun} "$target"
+      ${lib.getExe pkgs.shellcheck} "$target"
+    '';
+    text = ''
+      #! ${pkgs.runtimeShell}
+      exec "${config.security.wrapperDir}/cups-pdf" "$@"
+    '';
+  };
+
+  # wrapped cups-pdf package that uses the suid wrapper
+  cups-pdf-wrapped = pkgs.buildEnv {
+    name = "${pkgs.cups-pdf-to-pdf.name}-wrapped";
+    # using the wrapper as first path ensures it is used
+    paths = [ cups-pdf-wrapper pkgs.cups-pdf-to-pdf ];
+    ignoreCollisions = true;
+  };
+
+  instanceSettings = name: {
+    freeformType = with lib.types; nullOr (oneOf [ int str path package ]);
+    # override defaults:
+    # inject instance name into paths,
+    # also avoid conflicts between user names and special dirs
+    options.Out = lib.mkOption {
+      type = with lib.types; nullOr singleLineStr;
+      default = "/var/spool/cups-pdf-${name}/users/\${USER}";
+      defaultText = "/var/spool/cups-pdf-{instance-name}/users/\${USER}";
+      example = "\${HOME}/cups-pdf";
+      description = lib.mdDoc ''
+        output directory;
+        `''${HOME}` will be expanded to the user's home directory,
+        `''${USER}` will be expanded to the user name.
+      '';
+    };
+    options.AnonDirName = lib.mkOption {
+      type = with lib.types; nullOr singleLineStr;
+      default = "/var/spool/cups-pdf-${name}/anonymous";
+      defaultText = "/var/spool/cups-pdf-{instance-name}/anonymous";
+      example = "/var/lib/cups-pdf";
+      description = lib.mdDoc "path for anonymously created PDF files";
+    };
+    options.Spool = lib.mkOption {
+      type = with lib.types; nullOr singleLineStr;
+      default = "/var/spool/cups-pdf-${name}/spool";
+      defaultText = "/var/spool/cups-pdf-{instance-name}/spool";
+      example = "/var/lib/cups-pdf";
+      description = lib.mdDoc "spool directory";
+    };
+    options.Anonuser = lib.mkOption {
+      type = lib.types.singleLineStr;
+      default = "root";
+      description = lib.mdDoc ''
+        User for anonymous PDF creation.
+        An empty string disables this feature.
+      '';
+    };
+    options.GhostScript = lib.mkOption {
+      type = with lib.types; nullOr path;
+      default = lib.getExe pkgs.ghostscript;
+      defaultText = lib.literalExpression "lib.getExe pkgs.ghostscript";
+      example = lib.literalExpression ''''${pkgs.ghostscript}/bin/ps2pdf'';
+      description = lib.mdDoc "location of GhostScript binary";
+    };
+  };
+
+  instanceConfig = { name, config, ... }: {
+    options = {
+      enable = (lib.mkEnableOption (lib.mdDoc "this cups-pdf instance")) // { default = true; };
+      installPrinter = (lib.mkEnableOption (lib.mdDoc ''
+        a CUPS printer queue for this instance.
+        The queue will be named after the instance and will use the {file}`CUPS-PDF_opt.ppd` ppd file.
+        If this is disabled, you need to add the queue yourself to use the instance
+      '')) // { default = true; };
+      confFileText = lib.mkOption {
+        type = lib.types.lines;
+        description = lib.mdDoc ''
+          This will contain the contents of {file}`cups-pdf.conf` for this instance, derived from {option}`settings`.
+          You can use this option to append text to the file.
+        '';
+      };
+      settings = lib.mkOption {
+        type = lib.types.submodule (instanceSettings name);
+        default = {};
+        example = {
+          Out = "\${HOME}/cups-pdf";
+          UserUMask = "0033";
+        };
+        description = lib.mdDoc ''
+          Settings for a cups-pdf instance, see the descriptions in the template config file in the cups-pdf package.
+          The key value pairs declared here will be translated into proper key value pairs for {file}`cups-pdf.conf`.
+          Setting a value to `null` disables the option and removes it from the file.
+        '';
+      };
+    };
+    config.confFileText = lib.pipe config.settings [
+      (lib.filterAttrs (key: value: value != null))
+      (lib.mapAttrs (key: builtins.toString))
+      (lib.mapAttrsToList (key: value: "${key} ${value}\n"))
+      lib.concatStrings
+    ];
+  };
+
+  cupsPdfCfg = config.services.printing.cups-pdf;
+
+  copyConfigFileCmds = lib.pipe cupsPdfCfg.instances [
+    (lib.filterAttrs (name: lib.getAttr "enable"))
+    (lib.mapAttrs (name: lib.getAttr "confFileText"))
+    (lib.mapAttrs (name: pkgs.writeText "cups-pdf-${name}.conf"))
+    (lib.mapAttrsToList (name: confFile: "ln --symbolic --no-target-directory ${confFile} /var/lib/cups/cups-pdf-${name}.conf\n"))
+    lib.concatStrings
+  ];
+
+  printerSettings = lib.pipe cupsPdfCfg.instances [
+    (lib.filterAttrs (name: lib.getAttr "enable"))
+    (lib.filterAttrs (name: lib.getAttr "installPrinter"))
+    (lib.mapAttrsToList (name: instance: (lib.mapAttrs (key: lib.mkDefault) {
+      inherit name;
+      model = "CUPS-PDF_opt.ppd";
+      deviceUri = "cups-pdf:/${name}";
+      description = "virtual printer for cups-pdf instance ${name}";
+      location = instance.settings.Out;
+    })))
+  ];
+
+in
+
+{
+
+  options.services.printing.cups-pdf = {
+    enable = lib.mkEnableOption (lib.mdDoc ''
+      the cups-pdf virtual pdf printer backend.
+      By default, this will install a single printer `pdf`.
+      but this can be changed/extended with {option}`services.printing.cups-pdf.instances`
+    '');
+    instances = lib.mkOption {
+      type = lib.types.attrsOf (lib.types.submodule instanceConfig);
+      default.pdf = {};
+      example.pdf.settings = {
+        Out = "\${HOME}/cups-pdf";
+        UserUMask = "0033";
+      };
+      description = lib.mdDoc ''
+        Permits to raise one or more cups-pdf instances.
+        Each instance is named by an attribute name, and the attribute's values control the instance' configuration.
+      '';
+    };
+  };
+
+  config = lib.mkIf cupsPdfCfg.enable {
+    services.printing.enable = true;
+    services.printing.drivers = [ cups-pdf-wrapped ];
+    hardware.printers.ensurePrinters = printerSettings;
+    # the cups module will install the default config file,
+    # but we don't need it and it would confuse cups-pdf
+    systemd.services.cups.preStart = lib.mkAfter ''
+      rm -f /var/lib/cups/cups-pdf.conf
+      ${copyConfigFileCmds}
+    '';
+    security.wrappers.cups-pdf = {
+      group = "lp";
+      owner = "root";
+      permissions = "+r,ug+x";
+      setuid = true;
+      source = "${pkgs.cups-pdf-to-pdf}/lib/cups/backend/cups-pdf";
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/printing/cupsd.nix b/nixpkgs/nixos/modules/services/printing/cupsd.nix
index 8f1c3d9c525a..f6a23fb900f0 100644
--- a/nixpkgs/nixos/modules/services/printing/cupsd.nix
+++ b/nixpkgs/nixos/modules/services/printing/cupsd.nix
@@ -134,6 +134,15 @@ in
         '';
       };
 
+      stateless = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          If set, all state directories relating to CUPS will be removed on
+          startup of the service.
+        '';
+      };
+
       startWhenNeeded = mkOption {
         type = types.bool;
         default = true;
@@ -167,7 +176,7 @@ in
         type = types.lines;
         internal = true;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional commands executed while creating the directory
           containing the CUPS server binaries.
         '';
@@ -308,6 +317,7 @@ in
     environment.etc.cups.source = "/var/lib/cups";
 
     services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
+    services.udev.packages = cfg.drivers;
 
     # Allow asswordless printer admin for members of wheel group
     security.polkit.extraConfig = mkIf polkitEnabled ''
@@ -332,7 +342,7 @@ in
 
     systemd.sockets.cups = mkIf cfg.startWhenNeeded {
       wantedBy = [ "sockets.target" ];
-      listenStreams = [ "/run/cups/cups.sock" ]
+      listenStreams = [ "" "/run/cups/cups.sock" ]
         ++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
     };
 
@@ -343,8 +353,9 @@ in
 
         path = [ cups.out ];
 
-        preStart =
-          ''
+        preStart = lib.optionalString cfg.stateless ''
+          rm -rf /var/cache/cups /var/lib/cups /var/spool/cups
+        '' + ''
             mkdir -m 0700 -p /var/cache/cups
             mkdir -m 0700 -p /var/spool/cups
             mkdir -m 0755 -p ${cfg.tempDir}
@@ -385,10 +396,7 @@ in
             ''}
           '';
 
-          serviceConfig = {
-            PrivateTmp = true;
-            RuntimeDirectory = [ "cups" ];
-          };
+          serviceConfig.PrivateTmp = true;
       };
 
     systemd.services.cups-browsed = mkIf avahiEnabled
diff --git a/nixpkgs/nixos/modules/services/printing/ipp-usb.nix b/nixpkgs/nixos/modules/services/printing/ipp-usb.nix
new file mode 100644
index 000000000000..8ed2ff826871
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/printing/ipp-usb.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }: {
+  options = {
+    services.ipp-usb = {
+      enable = lib.mkEnableOption (lib.mdDoc "ipp-usb, a daemon to turn an USB printer/scanner supporting IPP everywhere (aka AirPrint, WSD, AirScan) into a locally accessible network printer/scanner");
+    };
+  };
+  config = lib.mkIf config.services.ipp-usb.enable {
+    systemd.services.ipp-usb = {
+      description = "Daemon for IPP over USB printer support";
+      after = [ "cups.service" "avahi-daemon.service" ];
+      wants = [ "avahi-daemon.service" ];
+      serviceConfig = {
+        ExecStart = [ "${pkgs.ipp-usb}/bin/ipp-usb" ];
+        Type = "simple";
+        Restart = "on-failure";
+        StateDirectory = "ipp-usb";
+        LogsDirectory = "ipp-usb";
+
+        # hardening.
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectControlGroups = true;
+        MemoryDenyWriteExecute = true;
+        # breaks the daemon, presumably because it messes with DeviceAllow
+        ProtectClock = false;
+        ProtectKernelTunables = true;
+        ProtectKernelLogs = true;
+        ProtectSystem = "strict";
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        PrivateMounts = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        RemoveIPC = true;
+        RestrictNamespaces = true;
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_NETLINK" "AF_INET" "AF_INET6" ];
+        ProtectProc = "noaccess";
+      };
+    };
+
+    # starts the systemd service
+    services.udev.packages = [ pkgs.ipp-usb ];
+    services.avahi = {
+      enable = true;
+      publish = {
+        enable = true;
+        userServices = true;
+      };
+    };
+    # enable printing and scanning by default, but not required.
+    services.printing.enable = lib.mkDefault true;
+    hardware.sane.enable = lib.mkDefault true;
+    # so that sane discovers scanners
+    hardware.sane.extraBackends = [ pkgs.sane-airscan ];
+  };
+}
+
+
diff --git a/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix b/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
index da3b0dc9d713..0a21d705ef87 100644
--- a/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
+++ b/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
@@ -37,7 +37,7 @@ in {
 
   options.services.elasticsearch-curator = {
 
-    enable = mkEnableOption "elasticsearch curator";
+    enable = mkEnableOption (lib.mdDoc "elasticsearch curator");
     interval = mkOption {
       description = lib.mdDoc "The frequency to run curator, a systemd.time such as 'hourly'";
       default = "hourly";
@@ -50,7 +50,7 @@ in {
     };
     port = mkOption {
       description = lib.mdDoc "the port that elasticsearch is listening on";
-      type = types.int;
+      type = types.port;
       default = 9200;
     };
     actionYAML = mkOption {
diff --git a/nixpkgs/nixos/modules/services/search/elasticsearch.nix b/nixpkgs/nixos/modules/services/search/elasticsearch.nix
index 4a9dd50310e2..fa1627566ebe 100644
--- a/nixpkgs/nixos/modules/services/search/elasticsearch.nix
+++ b/nixpkgs/nixos/modules/services/search/elasticsearch.nix
@@ -66,7 +66,7 @@ in
     port = mkOption {
       description = lib.mdDoc "Elasticsearch port to listen for HTTP traffic.";
       default = 9200;
-      type = types.int;
+      type = types.port;
     };
 
     tcp_port = mkOption {
diff --git a/nixpkgs/nixos/modules/services/search/hound.nix b/nixpkgs/nixos/modules/services/search/hound.nix
index c81ceee54696..b41a2e2bae1f 100644
--- a/nixpkgs/nixos/modules/services/search/hound.nix
+++ b/nixpkgs/nixos/modules/services/search/hound.nix
@@ -120,7 +120,6 @@ in {
                     " -conf ${pkgs.writeText "hound.json" cfg.config}";
 
       };
-      path = [ pkgs.git pkgs.mercurial pkgs.openssh ];
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/search/kibana.nix b/nixpkgs/nixos/modules/services/search/kibana.nix
index c945ef4c89d0..5eb2381d5d39 100644
--- a/nixpkgs/nixos/modules/services/search/kibana.nix
+++ b/nixpkgs/nixos/modules/services/search/kibana.nix
@@ -32,7 +32,7 @@ let
 
 in {
   options.services.kibana = {
-    enable = mkEnableOption "kibana service";
+    enable = mkEnableOption (lib.mdDoc "kibana service");
 
     listenAddress = mkOption {
       description = lib.mdDoc "Kibana listening host";
@@ -43,7 +43,7 @@ in {
     port = mkOption {
       description = lib.mdDoc "Kibana listening port";
       default = 5601;
-      type = types.int;
+      type = types.port;
     };
 
     cert = mkOption {
@@ -122,13 +122,13 @@ in {
       };
 
       certificateAuthorities = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           CA files to auth against elasticsearch.
 
-          Please use the <option>ca</option> option when using kibana &lt; 5.4
+          Please use the {option}`ca` option when using kibana \< 5.4
           because those old versions don't support setting multiple CA's.
 
-          This defaults to the singleton list [ca] when the <option>ca</option> option is defined.
+          This defaults to the singleton list [ca] when the {option}`ca` option is defined.
         '';
         default = if cfg.elasticsearch.ca == null then [] else [ca];
         defaultText = literalExpression ''
diff --git a/nixpkgs/nixos/modules/services/search/meilisearch.md b/nixpkgs/nixos/modules/services/search/meilisearch.md
index 98e7c542cb9a..299f56bf8293 100644
--- a/nixpkgs/nixos/modules/services/search/meilisearch.md
+++ b/nixpkgs/nixos/modules/services/search/meilisearch.md
@@ -2,7 +2,7 @@
 
 Meilisearch is a lightweight, fast and powerful search engine. Think elastic search with a much smaller footprint.
 
-## Quickstart
+## Quickstart {#module-services-meilisearch-quickstart}
 
 the minimum to start meilisearch is
 
@@ -14,26 +14,26 @@ this will start the http server included with meilisearch on port 7700.
 
 test with `curl -X GET 'http://localhost:7700/health'`
 
-## Usage
+## Usage {#module-services-meilisearch-usage}
 
 you first need to add documents to an index before you can search for documents.
 
-### Add a documents to the `movies` index
+### Add a documents to the `movies` index {#module-services-meilisearch-quickstart-add}
 
 `curl -X POST 'http://127.0.0.1:7700/indexes/movies/documents' --data '[{"id": "123", "title": "Superman"}, {"id": 234, "title": "Batman"}]'`
 
-### Search documents in the `movies` index
+### Search documents in the `movies` index {#module-services-meilisearch-quickstart-search}
 
 `curl 'http://127.0.0.1:7700/indexes/movies/search' --data '{ "q": "botman" }'` (note the typo is intentional and there to demonstrate the typo tolerant capabilities)
 
-## Defaults
+## Defaults {#module-services-meilisearch-defaults}
 
 - The default nixos package doesn't come with the [dashboard](https://docs.meilisearch.com/learn/getting_started/quick_start.html#search), since the dashboard features makes some assets downloads at compile time.
 
-- Anonimized Analytics sent to meilisearch are disabled by default.
+- Anonymized Analytics sent to meilisearch are disabled by default.
 
 - Default deployment is development mode. It doesn't require a secret master key. All routes are not protected and accessible.
 
-## Missing
+## Missing {#module-services-meilisearch-missing}
 
 - the snapshot feature is not yet configurable from the module, it's just a matter of adding the relevant environment variables.
diff --git a/nixpkgs/nixos/modules/services/search/meilisearch.nix b/nixpkgs/nixos/modules/services/search/meilisearch.nix
index 9a03fc1f715b..7c9fa62ae954 100644
--- a/nixpkgs/nixos/modules/services/search/meilisearch.nix
+++ b/nixpkgs/nixos/modules/services/search/meilisearch.nix
@@ -9,19 +9,17 @@ in
 {
 
   meta.maintainers = with maintainers; [ Br1ght0ne happysalada ];
-  # Don't edit the docbook xml directly, edit the md and generate it:
-  # `pandoc meilisearch.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > meilisearch.xml`
-  meta.doc = ./meilisearch.xml;
+  meta.doc = ./meilisearch.md;
 
   ###### interface
 
   options.services.meilisearch = {
-    enable = mkEnableOption "MeiliSearch - a RESTful search API";
+    enable = mkEnableOption (lib.mdDoc "MeiliSearch - a RESTful search API");
 
     package = mkOption {
       description = lib.mdDoc "The package to use for meilisearch. Use this if you require specific features to be enabled. The default package has no features.";
       default = pkgs.meilisearch;
-      defaultText = "pkgs.meilisearch";
+      defaultText = lib.literalExpression "pkgs.meilisearch";
       type = types.package;
     };
 
@@ -68,7 +66,7 @@ in
     };
 
     logLevel = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Defines how much detail should be present in MeiliSearch's logs.
         MeiliSearch currently supports four log levels, listed in order of increasing verbosity:
         - 'ERROR': only log unexpected events indicating MeiliSearch is not functioning as expected
@@ -117,7 +115,7 @@ in
         MEILI_HTTP_ADDR = "${cfg.listenAddress}:${toString cfg.listenPort}";
         MEILI_NO_ANALYTICS = toString cfg.noAnalytics;
         MEILI_ENV = cfg.environment;
-        MEILI_DUMPS_DIR = "/var/lib/meilisearch/dumps";
+        MEILI_DUMP_DIR = "/var/lib/meilisearch/dumps";
         MEILI_LOG_LEVEL = cfg.logLevel;
         MEILI_MAX_INDEX_SIZE = cfg.maxIndexSize;
       };
diff --git a/nixpkgs/nixos/modules/services/search/meilisearch.xml b/nixpkgs/nixos/modules/services/search/meilisearch.xml
deleted file mode 100644
index c1a73f358c28..000000000000
--- a/nixpkgs/nixos/modules/services/search/meilisearch.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-meilisearch">
-  <title>Meilisearch</title>
-  <para>
-    Meilisearch is a lightweight, fast and powerful search engine. Think
-    elastic search with a much smaller footprint.
-  </para>
-  <section xml:id="quickstart">
-    <title>Quickstart</title>
-    <para>
-      the minimum to start meilisearch is
-    </para>
-    <programlisting language="bash">
-services.meilisearch.enable = true;
-</programlisting>
-    <para>
-      this will start the http server included with meilisearch on port
-      7700.
-    </para>
-    <para>
-      test with
-      <literal>curl -X GET 'http://localhost:7700/health'</literal>
-    </para>
-  </section>
-  <section xml:id="usage">
-    <title>Usage</title>
-    <para>
-      you first need to add documents to an index before you can search
-      for documents.
-    </para>
-    <section xml:id="add-a-documents-to-the-movies-index">
-      <title>Add a documents to the <literal>movies</literal>
-      index</title>
-      <para>
-        <literal>curl -X POST 'http://127.0.0.1:7700/indexes/movies/documents' --data '[{&quot;id&quot;: &quot;123&quot;, &quot;title&quot;: &quot;Superman&quot;}, {&quot;id&quot;: 234, &quot;title&quot;: &quot;Batman&quot;}]'</literal>
-      </para>
-    </section>
-    <section xml:id="search-documents-in-the-movies-index">
-      <title>Search documents in the <literal>movies</literal>
-      index</title>
-      <para>
-        <literal>curl 'http://127.0.0.1:7700/indexes/movies/search' --data '{ &quot;q&quot;: &quot;botman&quot; }'</literal>
-        (note the typo is intentional and there to demonstrate the typo
-        tolerant capabilities)
-      </para>
-    </section>
-  </section>
-  <section xml:id="defaults">
-    <title>Defaults</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          The default nixos package doesn’t come with the
-          <link xlink:href="https://docs.meilisearch.com/learn/getting_started/quick_start.html#search">dashboard</link>,
-          since the dashboard features makes some assets downloads at
-          compile time.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Anonimized Analytics sent to meilisearch are disabled by
-          default.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          Default deployment is development mode. It doesn’t require a
-          secret master key. All routes are not protected and
-          accessible.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="missing">
-    <title>Missing</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          the snapshot feature is not yet configurable from the module,
-          it’s just a matter of adding the relevant environment
-          variables.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/search/opensearch.nix b/nixpkgs/nixos/modules/services/search/opensearch.nix
new file mode 100644
index 000000000000..9a50e7963138
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/opensearch.nix
@@ -0,0 +1,248 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.opensearch;
+
+  settingsFormat = pkgs.formats.yaml {};
+
+  configDir = cfg.dataDir + "/config";
+
+  usingDefaultDataDir = cfg.dataDir == "/var/lib/opensearch";
+  usingDefaultUserAndGroup = cfg.user == "opensearch" && cfg.group == "opensearch";
+
+  opensearchYml = settingsFormat.generate "opensearch.yml" cfg.settings;
+
+  loggingConfigFilename = "log4j2.properties";
+  loggingConfigFile = pkgs.writeTextFile {
+    name = loggingConfigFilename;
+    text = cfg.logging;
+  };
+in
+{
+
+  options.services.opensearch = {
+    enable = mkEnableOption (lib.mdDoc "OpenSearch");
+
+    package = lib.mkPackageOptionMD pkgs "OpenSearch" {
+      default = [ "opensearch" ];
+    };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options."network.host" = lib.mkOption {
+          type = lib.types.str;
+          default = "127.0.0.1";
+          description = lib.mdDoc ''
+            Which port this service should listen on.
+          '';
+        };
+
+        options."cluster.name" = lib.mkOption {
+          type = lib.types.str;
+          default = "opensearch";
+          description = lib.mdDoc ''
+            The name of the cluster.
+          '';
+        };
+
+        options."discovery.type" = lib.mkOption {
+          type = lib.types.str;
+          default = "single-node";
+          description = lib.mdDoc ''
+            The type of discovery to use.
+          '';
+        };
+
+        options."http.port" = lib.mkOption {
+          type = lib.types.port;
+          default = 9200;
+          description = lib.mdDoc ''
+            The port to listen on for HTTP traffic.
+          '';
+        };
+
+        options."transport.port" = lib.mkOption {
+          type = lib.types.port;
+          default = 9300;
+          description = lib.mdDoc ''
+            The port to listen on for transport traffic.
+          '';
+        };
+      };
+
+      default = {};
+
+      description = lib.mdDoc ''
+        OpenSearch configuration.
+      '';
+    };
+
+    logging = lib.mkOption {
+      description = lib.mdDoc "opensearch logging configuration.";
+
+      default = ''
+        logger.action.name = org.opensearch.action
+        logger.action.level = info
+
+        appender.console.type = Console
+        appender.console.name = console
+        appender.console.layout.type = PatternLayout
+        appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
+
+        rootLogger.level = info
+        rootLogger.appenderRef.console.ref = console
+      '';
+      type = types.str;
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/opensearch";
+      apply = converge (removeSuffix "/");
+      description = lib.mdDoc ''
+        Data directory for OpenSearch. If you change this, you need to
+        manually create the directory. You also need to create the
+        `opensearch` user and group, or change
+        [](#opt-services.opensearch.user) and
+        [](#opt-services.opensearch.group) to existing ones with
+        access to the directory.
+      '';
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "opensearch";
+      description = lib.mdDoc ''
+        The user OpenSearch runs as. Should be left at default unless
+        you have very specific needs.
+      '';
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "opensearch";
+      description = lib.mdDoc ''
+        The group OpenSearch runs as. Should be left at default unless
+        you have very specific needs.
+      '';
+    };
+
+    extraCmdLineOptions = lib.mkOption {
+      description = lib.mdDoc "Extra command line options for the OpenSearch launcher.";
+      default = [ ];
+      type = lib.types.listOf lib.types.str;
+    };
+
+    extraJavaOptions = lib.mkOption {
+      description = lib.mdDoc "Extra command line options for Java.";
+      default = [ ];
+      type = lib.types.listOf lib.types.str;
+      example = [ "-Djava.net.preferIPv4Stack=true" ];
+    };
+
+    restartIfChanged = lib.mkOption {
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Automatically restart the service on config change.
+        This can be set to false to defer restarts on a server or cluster.
+        Please consider the security implications of inadvertently running an older version,
+        and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+      '';
+      default = true;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.opensearch = {
+      description = "OpenSearch Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.inetutils ];
+      inherit (cfg) restartIfChanged;
+      environment = {
+        OPENSEARCH_HOME = cfg.dataDir;
+        OPENSEARCH_JAVA_OPTS = toString cfg.extraJavaOptions;
+        OPENSEARCH_PATH_CONF = configDir;
+      };
+      serviceConfig = {
+        ExecStartPre =
+          let
+            startPreFullPrivileges = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+            '' + (optionalString (!config.boot.isContainer) ''
+              # Only set vm.max_map_count if lower than ES required minimum
+              # This avoids conflict if configured via boot.kernel.sysctl
+              if [ $(${pkgs.procps}/bin/sysctl -n vm.max_map_count) -lt 262144 ]; then
+                ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
+              fi
+            '');
+            startPreUnprivileged = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+
+              # Install plugins
+              ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
+              ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
+
+              # opensearch needs to create the opensearch.keystore in the config directory
+              # so this directory needs to be writable.
+              mkdir -p ${configDir}
+              chmod 0700 ${configDir}
+
+              # Note that we copy config files from the nix store instead of symbolically linking them
+              # because otherwise X-Pack Security will raise the following exception:
+              # java.security.AccessControlException:
+              # access denied ("java.io.FilePermission" "/var/lib/opensearch/config/opensearch.yml" "read")
+
+              rm -f ${configDir}/opensearch.yml
+              cp ${opensearchYml} ${configDir}/opensearch.yml
+
+              # Make sure the logging configuration for old OpenSearch versions is removed:
+              rm -f "${configDir}/logging.yml"
+              rm -f ${configDir}/${loggingConfigFilename}
+              cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
+              mkdir -p ${configDir}/scripts
+
+              rm -f ${configDir}/jvm.options
+              cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options
+
+              # redirect jvm logs to the data directory
+              mkdir -p ${cfg.dataDir}/logs
+              chmod 0700 ${cfg.dataDir}/logs
+              sed -e '#logs/gc.log#${cfg.dataDir}/logs/gc.log#' -i ${configDir}/jvm.options
+            '';
+          in [
+            "+${pkgs.writeShellScript "opensearch-start-pre-full-privileges" startPreFullPrivileges}"
+            "${pkgs.writeShellScript "opensearch-start-pre-unprivileged" startPreUnprivileged}"
+          ];
+        ExecStartPost = pkgs.writeShellScript "opensearch-start-post" ''
+          set -o errexit -o pipefail -o nounset -o errtrace
+          shopt -s inherit_errexit
+
+          # Make sure opensearch is up and running before dependents
+          # are started
+          while ! ${pkgs.curl}/bin/curl -sS -f http://${cfg.settings."network.host"}:${toString cfg.settings."http.port"} 2>/dev/null; do
+            sleep 1
+          done
+        '';
+        ExecStart = "${cfg.package}/bin/opensearch ${toString cfg.extraCmdLineOptions}";
+        User = cfg.user;
+        Group = cfg.group;
+        LimitNOFILE = "1024000";
+        Restart = "always";
+        TimeoutStartSec = "infinity";
+        DynamicUser = usingDefaultUserAndGroup && usingDefaultDataDir;
+      } // (optionalAttrs (usingDefaultDataDir) {
+        StateDirectory = "opensearch";
+        StateDirectoryMode = "0700";
+      });
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/search/qdrant.nix b/nixpkgs/nixos/modules/services/search/qdrant.nix
new file mode 100644
index 000000000000..e1f7365d951a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/qdrant.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.qdrant;
+
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "config.yaml" cfg.settings;
+in {
+
+  options = {
+    services.qdrant = {
+      enable = mkEnableOption (lib.mdDoc "Vector Search Engine for the next generation of AI applications");
+
+      settings = mkOption {
+        description = lib.mdDoc ''
+          Configuration for Qdrant
+          Refer to <https://github.com/qdrant/qdrant/blob/master/config/config.yaml> for details on supported values.
+        '';
+
+        type = settingsFormat.type;
+
+        example = {
+          storage = {
+            storage_path = "/var/lib/qdrant/storage";
+            snapshots_path = "/var/lib/qdrant/snapshots";
+          };
+          hsnw_index = {
+            on_disk = true;
+          };
+          service = {
+            host = "127.0.0.1";
+            http_port = 6333;
+            grpc_port = 6334;
+          };
+          telemetry_disabled = true;
+        };
+
+        defaultText = literalExpression ''
+          {
+            storage = {
+              storage_path = "/var/lib/qdrant/storage";
+              snapshots_path = "/var/lib/qdrant/snapshots";
+            };
+            hsnw_index = {
+              on_disk = true;
+            };
+            service = {
+              host = "127.0.0.1";
+              http_port = 6333;
+              grpc_port = 6334;
+            };
+            telemetry_disabled = true;
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.qdrant.settings = {
+      storage.storage_path = mkDefault "/var/lib/qdrant/storage";
+      storage.snapshots_path = mkDefault "/var/lib/qdrant/snapshots";
+      # The following default values are the same as in the default config,
+      # they are just written here for convenience.
+      storage.on_disk_payload = mkDefault true;
+      storage.wal.wal_capacity_mb = mkDefault 32;
+      storage.wal.wal_segments_ahead = mkDefault 0;
+      storage.performance.max_search_threads = mkDefault 0;
+      storage.performance.max_optimization_threads = mkDefault 1;
+      storage.optimizers.deleted_threshold = mkDefault 0.2;
+      storage.optimizers.vacuum_min_vector_number = mkDefault 1000;
+      storage.optimizers.default_segment_number = mkDefault 0;
+      storage.optimizers.max_segment_size_kb = mkDefault null;
+      storage.optimizers.memmap_threshold_kb = mkDefault null;
+      storage.optimizers.indexing_threshold_kb = mkDefault 20000;
+      storage.optimizers.flush_interval_sec = mkDefault 5;
+      storage.optimizers.max_optimization_threads = mkDefault 1;
+      storage.hnsw_index.m = mkDefault 16;
+      storage.hnsw_index.ef_construct = mkDefault 100;
+      storage.hnsw_index.full_scan_threshold_kb = mkDefault 10000;
+      storage.hnsw_index.max_indexing_threads = mkDefault 0;
+      storage.hnsw_index.on_disk = mkDefault false;
+      storage.hnsw_index.payload_m = mkDefault null;
+      service.max_request_size_mb = mkDefault 32;
+      service.max_workers = mkDefault 0;
+      service.http_port = mkDefault 6333;
+      service.grpc_port = mkDefault 6334;
+      service.enable_cors = mkDefault true;
+      cluster.enabled = mkDefault false;
+      # the following have been altered for security
+      service.host = mkDefault "127.0.0.1";
+      telemetry_disabled = mkDefault true;
+    };
+
+    systemd.services.qdrant = {
+      description = "Vector Search Engine for the next generation of AI applications";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        LimitNOFILE=65536;
+        ExecStart = "${pkgs.qdrant}/bin/qdrant --config-path ${configFile}";
+        DynamicUser = true;
+        Restart = "on-failure";
+        StateDirectory = "qdrant";
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectClock = true;
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        RestrictSUIDSGID = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/search/solr.nix b/nixpkgs/nixos/modules/services/search/solr.nix
deleted file mode 100644
index ea8a2d6f9277..000000000000
--- a/nixpkgs/nixos/modules/services/search/solr.nix
+++ /dev/null
@@ -1,110 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.solr;
-
-in
-
-{
-  options = {
-    services.solr = {
-      enable = mkEnableOption "Solr";
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.solr;
-        defaultText = literalExpression "pkgs.solr";
-        description = lib.mdDoc "Which Solr package to use.";
-      };
-
-      port = mkOption {
-        type = types.int;
-        default = 8983;
-        description = lib.mdDoc "Port on which Solr is ran.";
-      };
-
-      stateDir = mkOption {
-        type = types.path;
-        default = "/var/lib/solr";
-        description = lib.mdDoc "The solr home directory containing config, data, and logging files.";
-      };
-
-      extraJavaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = lib.mdDoc "Extra command line options given to the java process running Solr.";
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "solr";
-        description = lib.mdDoc "User under which Solr is ran.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "solr";
-        description = lib.mdDoc "Group under which Solr is ran.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ cfg.package ];
-
-    systemd.services.solr = {
-      after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ];
-      wantedBy = [ "multi-user.target" ];
-
-      environment = {
-        SOLR_HOME = "${cfg.stateDir}/data";
-        LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml";
-        SOLR_LOGS_DIR = "${cfg.stateDir}/logs";
-        SOLR_PORT = "${toString cfg.port}";
-      };
-      path = with pkgs; [
-        gawk
-        procps
-      ];
-      preStart = ''
-        mkdir -p "${cfg.stateDir}/data";
-        mkdir -p "${cfg.stateDir}/logs";
-
-        if ! test -e "${cfg.stateDir}/data/solr.xml"; then
-          install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml"
-          install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg"
-        fi
-
-        if ! test -e "${cfg.stateDir}/log4j2.xml"; then
-          install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml"
-        fi
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\"";
-        ExecStop="${cfg.package}/bin/solr stop";
-      };
-    };
-
-    users.users = optionalAttrs (cfg.user == "solr") {
-      solr = {
-        group = cfg.group;
-        home = cfg.stateDir;
-        createHome = true;
-        uid = config.ids.uids.solr;
-      };
-    };
-
-    users.groups = optionalAttrs (cfg.group == "solr") {
-      solr.gid = config.ids.gids.solr;
-    };
-
-  };
-
-}
diff --git a/nixpkgs/nixos/modules/services/security/aesmd.nix b/nixpkgs/nixos/modules/services/security/aesmd.nix
index 2f7deb7c8491..8b3f010d7c4d 100644
--- a/nixpkgs/nixos/modules/services/security/aesmd.nix
+++ b/nixpkgs/nixos/modules/services/security/aesmd.nix
@@ -19,12 +19,28 @@ let
 in
 {
   options.services.aesmd = {
-    enable = mkEnableOption "Intel's Architectural Enclave Service Manager (AESM) for Intel SGX";
+    enable = mkEnableOption (lib.mdDoc "Intel's Architectural Enclave Service Manager (AESM) for Intel SGX");
     debug = mkOption {
       type = types.bool;
       default = false;
       description = lib.mdDoc "Whether to build the PSW package in debug mode.";
     };
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      description = mdDoc "Additional environment variables to pass to the AESM service.";
+      # Example environment variable for `sgx-azure-dcap-client` provider library
+      example = {
+        AZDCAP_COLLATERAL_VERSION = "v2";
+        AZDCAP_DEBUG_LOG_LEVEL = "INFO";
+      };
+    };
+    quoteProviderLibrary = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      example = literalExpression "pkgs.sgx-azure-dcap-client";
+      description = lib.mdDoc "Custom quote provider library to use.";
+    };
     settings = mkOption {
       description = lib.mdDoc "AESM configuration";
       default = { };
@@ -83,7 +99,6 @@ in
         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";
@@ -98,8 +113,8 @@ in
         environment = {
           NAME = "aesm_service";
           AESM_PATH = storeAesmFolder;
-          LD_LIBRARY_PATH = storeAesmFolder;
-        };
+          LD_LIBRARY_PATH = makeLibraryPath [ cfg.quoteProviderLibrary ];
+        } // cfg.environment;
 
         # Make sure any of the SGX application enclave devices is available
         unitConfig.AssertPathExists = [
diff --git a/nixpkgs/nixos/modules/services/security/authelia.nix b/nixpkgs/nixos/modules/services/security/authelia.nix
new file mode 100644
index 000000000000..cc55260e20f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/authelia.nix
@@ -0,0 +1,401 @@
+{ lib
+, pkgs
+, config
+, ...
+}:
+
+let
+  cfg = config.services.authelia;
+
+  format = pkgs.formats.yaml { };
+  configFile = format.generate "config.yml" cfg.settings;
+
+  autheliaOpts = with lib; { name, ... }: {
+    options = {
+      enable = mkEnableOption (mdDoc "Authelia instance");
+
+      name = mkOption {
+        type = types.str;
+        default = name;
+        description = mdDoc ''
+          Name is used as a suffix for the service name, user, and group.
+          By default it takes the value you use for `<instance>` in:
+          {option}`services.authelia.<instance>`
+        '';
+      };
+
+      package = mkOption {
+        default = pkgs.authelia;
+        type = types.package;
+        defaultText = literalExpression "pkgs.authelia";
+        description = mdDoc "Authelia derivation to use.";
+      };
+
+      user = mkOption {
+        default = "authelia-${name}";
+        type = types.str;
+        description = mdDoc "The name of the user for this authelia instance.";
+      };
+
+      group = mkOption {
+        default = "authelia-${name}";
+        type = types.str;
+        description = mdDoc "The name of the group for this authelia instance.";
+      };
+
+      secrets = mkOption {
+        description = mdDoc ''
+          It is recommended you keep your secrets separate from the configuration.
+          It's especially important to keep the raw secrets out of your nix configuration,
+          as the values will be preserved in your nix store.
+          This attribute allows you to configure the location of secret files to be loaded at runtime.
+
+          https://www.authelia.com/configuration/methods/secrets/
+        '';
+        default = { };
+        type = types.submodule {
+          options = {
+            manual = mkOption {
+              default = false;
+              example = true;
+              description = mdDoc ''
+                Configuring authelia's secret files via the secrets attribute set
+                is intended to be convenient and help catch cases where values are required
+                to run at all.
+                If a user wants to set these values themselves and bypass the validation they can set this value to true.
+              '';
+              type = types.bool;
+            };
+
+            # required
+            jwtSecretFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your JWT secret used during identity verificaton.
+              '';
+            };
+
+            oidcIssuerPrivateKeyFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your private key file used to encrypt OIDC JWTs.
+              '';
+            };
+
+            oidcHmacSecretFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your HMAC secret used to sign OIDC JWTs.
+              '';
+            };
+
+            sessionSecretFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your session secret. Only used when redis is used as session storage.
+              '';
+            };
+
+            # required
+            storageEncryptionKeyFile = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = mdDoc ''
+                Path to your storage encryption key.
+              '';
+            };
+          };
+        };
+      };
+
+      environmentVariables = mkOption {
+        type = types.attrsOf types.str;
+        description = mdDoc ''
+          Additional environment variables to provide to authelia.
+          If you are providing secrets please consider the options under {option}`services.authelia.<instance>.secrets`
+          or make sure you use the `_FILE` suffix.
+          If you provide the raw secret rather than the location of a secret file that secret will be preserved in the nix store.
+          For more details: https://www.authelia.com/configuration/methods/secrets/
+        '';
+        default = { };
+      };
+
+      settings = mkOption {
+        description = mdDoc ''
+          Your Authelia config.yml as a Nix attribute set.
+          There are several values that are defined and documented in nix such as `default_2fa_method`,
+          but additional items can also be included.
+
+          https://github.com/authelia/authelia/blob/master/config.template.yml
+        '';
+        default = { };
+        example = ''
+          {
+            theme = "light";
+            default_2fa_method = "totp";
+            log.level = "debug";
+            server.disable_healthcheck = true;
+          }
+        '';
+        type = types.submodule {
+          freeformType = format.type;
+          options = {
+            theme = mkOption {
+              type = types.enum [ "light" "dark" "grey" "auto" ];
+              default = "light";
+              example = "dark";
+              description = mdDoc "The theme to display.";
+            };
+
+            default_2fa_method = mkOption {
+              type = types.enum [ "" "totp" "webauthn" "mobile_push" ];
+              default = "";
+              example = "webauthn";
+              description = mdDoc ''
+                Default 2FA method for new users and fallback for preferred but disabled methods.
+              '';
+            };
+
+            server = {
+              host = mkOption {
+                type = types.str;
+                default = "localhost";
+                example = "0.0.0.0";
+                description = mdDoc "The address to listen on.";
+              };
+
+              port = mkOption {
+                type = types.port;
+                default = 9091;
+                description = mdDoc "The port to listen on.";
+              };
+            };
+
+            log = {
+              level = mkOption {
+                type = types.enum [ "info" "debug" "trace" ];
+                default = "debug";
+                example = "info";
+                description = mdDoc "Level of verbosity for logs: info, debug, trace.";
+              };
+
+              format = mkOption {
+                type = types.enum [ "json" "text" ];
+                default = "json";
+                example = "text";
+                description = mdDoc "Format the logs are written as.";
+              };
+
+              file_path = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                example = "/var/log/authelia/authelia.log";
+                description = mdDoc "File path where the logs will be written. If not set logs are written to stdout.";
+              };
+
+              keep_stdout = mkOption {
+                type = types.bool;
+                default = false;
+                example = true;
+                description = mdDoc "Whether to also log to stdout when a `file_path` is defined.";
+              };
+            };
+
+            telemetry = {
+              metrics = {
+                enabled = mkOption {
+                  type = types.bool;
+                  default = false;
+                  example = true;
+                  description = mdDoc "Enable Metrics.";
+                };
+
+                address = mkOption {
+                  type = types.str;
+                  default = "tcp://127.0.0.1:9959";
+                  example = "tcp://0.0.0.0:8888";
+                  description = mdDoc "The address to listen on for metrics. This should be on a different port to the main `server.port` value.";
+                };
+              };
+            };
+          };
+        };
+      };
+
+      settingsFiles = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        example = [ "/etc/authelia/config.yml" "/etc/authelia/access-control.yml" "/etc/authelia/config/" ];
+        description = mdDoc ''
+          Here you can provide authelia with configuration files or directories.
+          It is possible to give authelia multiple files and use the nix generated configuration
+          file set via {option}`services.authelia.<instance>.settings`.
+        '';
+      };
+    };
+  };
+in
+{
+  options.services.authelia.instances = with lib; mkOption {
+    default = { };
+    type = types.attrsOf (types.submodule autheliaOpts);
+    description = mdDoc ''
+      Multi-domain protection currently requires multiple instances of Authelia.
+      If you don't require multiple instances of Authelia you can define just the one.
+
+      https://www.authelia.com/roadmap/active/multi-domain-protection/
+    '';
+    example = ''
+      {
+        main = {
+          enable = true;
+          secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile";
+          secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile";
+          settings = {
+            theme = "light";
+            default_2fa_method = "totp";
+            log.level = "debug";
+            server.disable_healthcheck = true;
+          };
+        };
+        preprod = {
+          enable = false;
+          secrets.storageEncryptionKeyFile = "/mnt/pre-prod/authelia/storageEncryptionKeyFile";
+          secrets.jwtSecretFile = "/mnt/pre-prod/jwtSecretFile";
+          settings = {
+            theme = "dark";
+            default_2fa_method = "webauthn";
+            server.host = "0.0.0.0";
+          };
+        };
+        test.enable = true;
+        test.secrets.manual = true;
+        test.settings.theme = "grey";
+        test.settings.server.disable_healthcheck = true;
+        test.settingsFiles = [ "/mnt/test/authelia" "/mnt/test-authelia.conf" ];
+        };
+      }
+    '';
+  };
+
+  config =
+    let
+      mkInstanceServiceConfig = instance:
+        let
+          execCommand = "${instance.package}/bin/authelia";
+          configFile = format.generate "config.yml" instance.settings;
+          configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}";
+        in
+        {
+          description = "Authelia authentication and authorization server";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          environment =
+            (lib.filterAttrs (_: v: v != null) {
+              AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
+              AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile;
+              AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile;
+              AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile;
+              AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile;
+            })
+            // instance.environmentVariables;
+
+          preStart = "${execCommand} ${configArg} validate-config";
+          serviceConfig = {
+            User = instance.user;
+            Group = instance.group;
+            ExecStart = "${execCommand} ${configArg}";
+            Restart = "always";
+            RestartSec = "5s";
+            StateDirectory = "authelia-${instance.name}";
+            StateDirectoryMode = "0700";
+
+            # Security options:
+            AmbientCapabilities = "";
+            CapabilityBoundingSet = "";
+            DeviceAllow = "";
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            NoNewPrivileges = true;
+
+            PrivateTmp = true;
+            PrivateDevices = true;
+            PrivateUsers = true;
+
+            ProtectClock = true;
+            ProtectControlGroups = true;
+            ProtectHome = "read-only";
+            ProtectHostname = true;
+            ProtectKernelLogs = true;
+            ProtectKernelModules = true;
+            ProtectKernelTunables = true;
+            ProtectProc = "noaccess";
+            ProtectSystem = "strict";
+
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+            RestrictNamespaces = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+
+            SystemCallArchitectures = "native";
+            SystemCallErrorNumber = "EPERM";
+            SystemCallFilter = [
+              "@system-service"
+              "~@cpu-emulation"
+              "~@debug"
+              "~@keyring"
+              "~@memlock"
+              "~@obsolete"
+              "~@privileged"
+              "~@setuid"
+            ];
+          };
+        };
+      mkInstanceUsersConfig = instance: {
+        groups."authelia-${instance.name}" =
+          lib.mkIf (instance.group == "authelia-${instance.name}") {
+            name = "authelia-${instance.name}";
+          };
+        users."authelia-${instance.name}" =
+          lib.mkIf (instance.user == "authelia-${instance.name}") {
+            name = "authelia-${instance.name}";
+            isSystemUser = true;
+            group = instance.group;
+          };
+      };
+      instances = lib.attrValues cfg.instances;
+    in
+    {
+      assertions = lib.flatten (lib.flip lib.mapAttrsToList cfg.instances (name: instance:
+        [
+          {
+            assertion = instance.secrets.manual || (instance.secrets.jwtSecretFile != null && instance.secrets.storageEncryptionKeyFile != null);
+            message = ''
+              Authelia requires a JWT Secret and a Storage Encryption Key to work.
+              Either set them like so:
+              services.authelia.${name}.secrets.jwtSecretFile = /my/path/to/jwtsecret;
+              services.authelia.${name}.secrets.storageEncryptionKeyFile = /my/path/to/encryptionkey;
+              Or set services.authelia.${name}.secrets.manual = true and provide them yourself via
+              environmentVariables or settingsFiles.
+              Do not include raw secrets in nix settings.
+            '';
+          }
+        ]
+      ));
+
+      systemd.services = lib.mkMerge
+        (map
+          (instance: lib.mkIf instance.enable {
+            "authelia-${instance.name}" = mkInstanceServiceConfig instance;
+          })
+          instances);
+      users = lib.mkMerge
+        (map
+          (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance))
+          instances);
+    };
+}
diff --git a/nixpkgs/nixos/modules/services/security/certmgr.nix b/nixpkgs/nixos/modules/services/security/certmgr.nix
index 40a566bc960d..ca4cf5084722 100644
--- a/nixpkgs/nixos/modules/services/security/certmgr.nix
+++ b/nixpkgs/nixos/modules/services/security/certmgr.nix
@@ -35,7 +35,7 @@ let
 in
 {
   options.services.certmgr = {
-    enable = mkEnableOption "certmgr";
+    enable = mkEnableOption (lib.mdDoc "certmgr");
 
     package = mkOption {
       type = types.package;
@@ -118,34 +118,34 @@ in
           service = mkOption {
             type = nullOr str;
             default = null;
-            description = "The service on which to perform &lt;action&gt; after fetching.";
+            description = lib.mdDoc "The service on which to perform \<action\> after fetching.";
           };
 
           action = mkOption {
             type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]);
             default = "nop";
-            description = "The action to take after fetching.";
+            description = lib.mdDoc "The action to take after fetching.";
           };
 
           # These ought all to be specified according to certmgr spec def.
           authority = mkOption {
             type = attrs;
-            description = "certmgr spec authority object.";
+            description = lib.mdDoc "certmgr spec authority object.";
           };
 
           certificate = mkOption {
             type = nullOr attrs;
-            description = "certmgr spec certificate object.";
+            description = lib.mdDoc "certmgr spec certificate object.";
           };
 
           private_key = mkOption {
             type = nullOr attrs;
-            description = "certmgr spec private_key object.";
+            description = lib.mdDoc "certmgr spec private_key object.";
           };
 
           request = mkOption {
             type = nullOr attrs;
-            description = "certmgr spec request object.";
+            description = lib.mdDoc "certmgr spec request object.";
           };
         };
     }));
diff --git a/nixpkgs/nixos/modules/services/security/cfssl.nix b/nixpkgs/nixos/modules/services/security/cfssl.nix
index 9408a602f137..202db98e222c 100644
--- a/nixpkgs/nixos/modules/services/security/cfssl.nix
+++ b/nixpkgs/nixos/modules/services/security/cfssl.nix
@@ -6,20 +6,20 @@ let
   cfg = config.services.cfssl;
 in {
   options.services.cfssl = {
-    enable = mkEnableOption "the CFSSL CA api-server";
+    enable = mkEnableOption (lib.mdDoc "the CFSSL CA api-server");
 
     dataDir = mkOption {
       default = "/var/lib/cfssl";
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         The work directory for CFSSL.
 
-        <note><para>
-          If left as the default value this directory will automatically be
-          created before the CFSSL server starts, otherwise you are
-          responsible for ensuring the directory exists with appropriate
-          ownership and permissions.
-        </para></note>
+        ::: {.note}
+        If left as the default value this directory will automatically be
+        created before the CFSSL server starts, otherwise you are
+        responsible for ensuring the directory exists with appropriate
+        ownership and permissions.
+        :::
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/security/clamav.nix b/nixpkgs/nixos/modules/services/security/clamav.nix
index 1b1194d31135..34897a9ac7db 100644
--- a/nixpkgs/nixos/modules/services/security/clamav.nix
+++ b/nixpkgs/nixos/modules/services/security/clamav.nix
@@ -26,7 +26,7 @@ in
   options = {
     services.clamav = {
       daemon = {
-        enable = mkEnableOption "ClamAV clamd daemon";
+        enable = mkEnableOption (lib.mdDoc "ClamAV clamd daemon");
 
         settings = mkOption {
           type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
@@ -38,7 +38,7 @@ in
         };
       };
       updater = {
-        enable = mkEnableOption "ClamAV freshclam updater";
+        enable = mkEnableOption (lib.mdDoc "ClamAV freshclam updater");
 
         frequency = mkOption {
           type = types.int;
diff --git a/nixpkgs/nixos/modules/services/security/endlessh-go.nix b/nixpkgs/nixos/modules/services/security/endlessh-go.nix
new file mode 100644
index 000000000000..6557ec953cd8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/endlessh-go.nix
@@ -0,0 +1,138 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.endlessh-go;
+in
+{
+  options.services.endlessh-go = {
+    enable = mkEnableOption (mdDoc "endlessh-go service");
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      example = "[::]";
+      description = mdDoc ''
+        Interface address to bind the endlessh-go daemon to SSH connections.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 2222;
+      example = 22;
+      description = mdDoc ''
+        Specifies on which port the endlessh-go daemon listens for SSH
+        connections.
+
+        Setting this to `22` may conflict with {option}`services.openssh`.
+      '';
+    };
+
+    prometheus = {
+      enable = mkEnableOption (mdDoc "Prometheus integration");
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        example = "[::]";
+        description = mdDoc ''
+          Interface address to bind the endlessh-go daemon to answer Prometheus
+          queries.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 2112;
+        example = 9119;
+        description = mdDoc ''
+          Specifies on which port the endlessh-go daemon listens for Prometheus
+          queries.
+        '';
+      };
+    };
+
+    extraOptions = mkOption {
+      type = with types; listOf str;
+      default = [ ];
+      example = [ "-conn_type=tcp4" "-max_clients=8192" ];
+      description = mdDoc ''
+        Additional command line options to pass to the endlessh-go daemon.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to open a firewall port for the SSH listener.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.endlessh-go = {
+      description = "SSH tarpit";
+      requires = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig =
+        let
+          needsPrivileges = cfg.port < 1024 || cfg.prometheus.port < 1024;
+          capabilities = [ "" ] ++ optionals needsPrivileges [ "CAP_NET_BIND_SERVICE" ];
+          rootDirectory = "/run/endlessh-go";
+        in
+        {
+          Restart = "always";
+          ExecStart = with cfg; concatStringsSep " " ([
+            "${pkgs.endlessh-go}/bin/endlessh-go"
+            "-logtostderr"
+            "-host=${listenAddress}"
+            "-port=${toString port}"
+          ] ++ optionals prometheus.enable [
+            "-enable_prometheus"
+            "-prometheus_host=${prometheus.listenAddress}"
+            "-prometheus_port=${toString prometheus.port}"
+          ] ++ extraOptions);
+          DynamicUser = true;
+          RootDirectory = rootDirectory;
+          BindReadOnlyPaths = [ builtins.storeDir ];
+          InaccessiblePaths = [ "-+${rootDirectory}" ];
+          RuntimeDirectory = baseNameOf rootDirectory;
+          RuntimeDirectoryMode = "700";
+          AmbientCapabilities = capabilities;
+          CapabilityBoundingSet = capabilities;
+          UMask = "0077";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateTmp = true;
+          PrivateUsers = !needsPrivileges;
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectSystem = "strict";
+          ProtectProc = "noaccess";
+          ProcSubset = "pid";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
+        };
+    };
+
+    networking.firewall.allowedTCPPorts = with cfg;
+      optionals openFirewall [ port prometheus.port ];
+  };
+
+  meta.maintainers = with maintainers; [ azahi ];
+}
diff --git a/nixpkgs/nixos/modules/services/security/endlessh.nix b/nixpkgs/nixos/modules/services/security/endlessh.nix
new file mode 100644
index 000000000000..e99b4dadcd58
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/endlessh.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.endlessh;
+in
+{
+  options.services.endlessh = {
+    enable = mkEnableOption (mdDoc "endlessh service");
+
+    port = mkOption {
+      type = types.port;
+      default = 2222;
+      example = 22;
+      description = mdDoc ''
+        Specifies on which port the endlessh daemon listens for SSH
+        connections.
+
+        Setting this to `22` may conflict with {option}`services.openssh`.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = with types; listOf str;
+      default = [ ];
+      example = [ "-6" "-d 9000" "-v" ];
+      description = mdDoc ''
+        Additional command line options to pass to the endlessh daemon.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to open a firewall port for the SSH listener.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.endlessh = {
+      description = "SSH tarpit";
+      requires = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig =
+        let
+          needsPrivileges = cfg.port < 1024;
+          capabilities = [ "" ] ++ optionals needsPrivileges [ "CAP_NET_BIND_SERVICE" ];
+          rootDirectory = "/run/endlessh";
+        in
+        {
+          Restart = "always";
+          ExecStart = with cfg; concatStringsSep " " ([
+            "${pkgs.endlessh}/bin/endlessh"
+            "-p ${toString port}"
+          ] ++ extraOptions);
+          DynamicUser = true;
+          RootDirectory = rootDirectory;
+          BindReadOnlyPaths = [ builtins.storeDir ];
+          InaccessiblePaths = [ "-+${rootDirectory}" ];
+          RuntimeDirectory = baseNameOf rootDirectory;
+          RuntimeDirectoryMode = "700";
+          AmbientCapabilities = capabilities;
+          CapabilityBoundingSet = capabilities;
+          UMask = "0077";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateTmp = true;
+          PrivateUsers = !needsPrivileges;
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectSystem = "strict";
+          ProtectProc = "noaccess";
+          ProcSubset = "pid";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
+        };
+    };
+
+    networking.firewall.allowedTCPPorts = with cfg;
+      optionals openFirewall [ port ];
+  };
+
+  meta.maintainers = with maintainers; [ azahi ];
+}
diff --git a/nixpkgs/nixos/modules/services/security/fail2ban.nix b/nixpkgs/nixos/modules/services/security/fail2ban.nix
index 24c84151bc78..eebf2a2f7b55 100644
--- a/nixpkgs/nixos/modules/services/security/fail2ban.nix
+++ b/nixpkgs/nixos/modules/services/security/fail2ban.nix
@@ -62,23 +62,29 @@ in
       };
 
       packageFirewall = mkOption {
-        default = pkgs.iptables;
-        defaultText = literalExpression "pkgs.iptables";
+        default = config.networking.firewall.package;
+        defaultText = literalExpression "config.networking.firewall.package";
         type = types.package;
-        example = literalExpression "pkgs.nftables";
-        description = lib.mdDoc "The firewall package used by fail2ban service.";
+        description = lib.mdDoc "The firewall package used by fail2ban service. Defaults to the package for your firewall (iptables or nftables).";
       };
 
       extraPackages = mkOption {
         default = [];
         type = types.listOf types.package;
         example = lib.literalExpression "[ pkgs.ipset ]";
-        description = ''
+        description = lib.mdDoc ''
           Extra packages to be made available to the fail2ban service. The example contains
           the packages needed by the `iptables-ipset-proto6` action.
         '';
       };
 
+      bantime = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        example = "10m";
+        description = lib.mdDoc "Number of seconds that a host is banned.";
+      };
+
       maxretry = mkOption {
         default = 3;
         type = types.ints.unsigned;
@@ -86,23 +92,24 @@ in
       };
 
       banaction = mkOption {
-        default = "iptables-multiport";
+        default = if config.networking.nftables.enable then "nftables-multiport" else "iptables-multiport";
+        defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-multiport" else "iptables-multiport"'';
         type = types.str;
-        example = "nftables-multiport";
         description = lib.mdDoc ''
           Default banning action (e.g. iptables, iptables-new, iptables-multiport,
-          shorewall, etc) It is used to define action_* variables. Can be overridden
-          globally or per section within jail.local file
+          iptables-ipset-proto6-allports, shorewall, etc). It is used to
+          define action_* variables. Can be overridden globally or per
+          section within jail.local file
         '';
       };
 
       banaction-allports = mkOption {
-        default = "iptables-allport";
+        default = if config.networking.nftables.enable then "nftables-allport" else "iptables-allport";
+        defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-allport" else "iptables-allport"'';
         type = types.str;
-        example = "nftables-allport";
         description = lib.mdDoc ''
           Default banning action (e.g. iptables, iptables-new, iptables-multiport,
-          shorewall, etc) It is used to define action_* variables. Can be overridden
+          shorewall, etc) for "allports" jails. It is used to define action_* variables. Can be overridden
           globally or per section within jail.local file
         '';
       };
@@ -111,56 +118,56 @@ in
         default = false;
         type = types.bool;
         description = lib.mdDoc ''
-          Allows to use database for searching of previously banned ip's to increase
-          a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
+          "bantime.increment" allows to use database for searching of previously banned ip's to increase
+          a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32 ...
         '';
       };
 
       bantime-increment.rndtime = mkOption {
-        default = "4m";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "8m";
         description = lib.mdDoc ''
-          "bantime-increment.rndtime" is the max number of seconds using for mixing with random time
+          "bantime.rndtime" is the max number of seconds using for mixing with random time
           to prevent "clever" botnets calculate exact time IP can be unbanned again
         '';
       };
 
       bantime-increment.maxtime = mkOption {
-        default = "10h";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "48h";
         description = lib.mdDoc ''
-          "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
+          "bantime.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
         '';
       };
 
       bantime-increment.factor = mkOption {
-        default = "1";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "4";
         description = lib.mdDoc ''
-          "bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
+          "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
           default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
         '';
       };
 
       bantime-increment.formula = mkOption {
-        default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
         description = lib.mdDoc ''
-          "bantime-increment.formula" used by default to calculate next value of ban time, default value bellow,
-          the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
+          "bantime.formula" used by default to calculate next value of ban time, default value bellow,
+          the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32 ...
         '';
       };
 
       bantime-increment.multipliers = mkOption {
-        default = "1 2 4 8 16 32 64";
-        type = types.str;
-        example = "2 4 16 128";
+        default = null;
+        type = types.nullOr types.str;
+        example = "1 2 4 8 16 32 64";
         description = lib.mdDoc ''
-          "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding
+          "bantime.multipliers" used to calculate next value of ban time instead of formula, corresponding
           previously ban count and given "bantime.factor" (for multipliers default is 1);
           following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
           always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
@@ -168,12 +175,12 @@ in
       };
 
       bantime-increment.overalljails = mkOption {
-        default = false;
-        type = types.bool;
+        default = null;
+        type = types.nullOr types.bool;
         example = true;
         description = lib.mdDoc ''
-          "bantime-increment.overalljails"  (if true) specifies the search of IP in the database will be executed
-          cross over all jails, if false (dafault), only current jail of the ban IP will be searched
+          "bantime.overalljails" (if true) specifies the search of IP in the database will be executed
+          cross over all jails, if false (default), only current jail of the ban IP will be searched
         '';
       };
 
@@ -202,6 +209,20 @@ in
        '';
       };
 
+      extraSettings = mkOption {
+        type = with types; attrsOf (oneOf [ bool ints.positive str ]);
+        default = {};
+        description = lib.mdDoc ''
+          Extra default configuration for all jails (i.e. `[DEFAULT]`). See
+          <https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf> for an overview.
+        '';
+        example = literalExpression ''
+          {
+            findtime = "15m";
+          }
+        '';
+      };
+
       jails = mkOption {
         default = { };
         example = literalExpression ''
@@ -212,10 +233,18 @@ in
               filter   = apache-nohome
               action   = iptables-multiport[name=HTTP, port="http,https"]
               logpath  = /var/log/httpd/error_log*
+              backend = auto
               findtime = 600
               bantime  = 600
               maxretry = 5
             ''';
+           dovecot = '''
+             # block IPs which failed to log-in
+             # aggressive mode add blocking for aborted connections
+             enabled = true
+             filter = dovecot[mode=aggressive]
+             maxretry = 3
+           ''';
           }
         '';
         type = types.attrsOf types.lines;
@@ -247,8 +276,16 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null);
+        message = ''
+          Options `services.fail2ban.bantime-increment.formula` and `services.fail2ban.bantime-increment.multipliers` cannot be both specified.
+        '';
+      }
+    ];
 
-    warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [
+    warnings = mkIf (!config.networking.firewall.enable && !config.networking.nftables.enable) [
       "fail2ban can not be used without a firewall"
     ];
 
@@ -265,26 +302,16 @@ in
       "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
     };
 
+    systemd.packages = [ cfg.package ];
     systemd.services.fail2ban = {
-      description = "Fail2ban Intrusion Prevention System";
-
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
       partOf = optional config.networking.firewall.enable "firewall.service";
 
       restartTriggers = [ fail2banConf jailConf pathsConf ];
 
       path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages;
 
-      unitConfig.Documentation = "man:fail2ban(1)";
-
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/fail2ban-server -xf start";
-        ExecStop = "${cfg.package}/bin/fail2ban-server stop";
-        ExecReload = "${cfg.package}/bin/fail2ban-server reload";
-        Type = "simple";
-        Restart = "on-failure";
-        PIDFile = "/run/fail2ban/fail2ban.pid";
         # Capabilities
         CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
         # Security
@@ -311,27 +338,33 @@ in
     # Add some reasonable default jails.  The special "DEFAULT" jail
     # sets default values for all other jails.
     services.fail2ban.jails.DEFAULT = ''
-      ${optionalString cfg.bantime-increment.enable ''
-        # Bantime incremental
-        bantime.increment    = ${boolToString cfg.bantime-increment.enable}
-        bantime.maxtime      = ${cfg.bantime-increment.maxtime}
-        bantime.factor       = ${cfg.bantime-increment.factor}
-        bantime.formula      = ${cfg.bantime-increment.formula}
-        bantime.multipliers  = ${cfg.bantime-increment.multipliers}
-        bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}
-      ''}
+      # Bantime increment options
+      bantime.increment = ${boolToString cfg.bantime-increment.enable}
+      ${optionalString (cfg.bantime-increment.rndtime != null) "bantime.rndtime = ${cfg.bantime-increment.rndtime}"}
+      ${optionalString (cfg.bantime-increment.maxtime != null) "bantime.maxtime = ${cfg.bantime-increment.maxtime}"}
+      ${optionalString (cfg.bantime-increment.factor != null) "bantime.factor = ${cfg.bantime-increment.factor}"}
+      ${optionalString (cfg.bantime-increment.formula != null) "bantime.formula = ${cfg.bantime-increment.formula}"}
+      ${optionalString (cfg.bantime-increment.multipliers != null) "bantime.multipliers = ${cfg.bantime-increment.multipliers}"}
+      ${optionalString (cfg.bantime-increment.overalljails != null) "bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}"}
       # Miscellaneous options
       ignoreip    = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
+      ${optionalString (cfg.bantime != null) ''
+        bantime     = ${cfg.bantime}
+      ''}
       maxretry    = ${toString cfg.maxretry}
       backend     = systemd
       # Actions
       banaction   = ${cfg.banaction}
       banaction_allports = ${cfg.banaction-allports}
+      ${optionalString (cfg.extraSettings != {}) ''
+        # Extra settings
+        ${generators.toKeyValue {} cfg.extraSettings}
+      ''}
     '';
     # Block SSH if there are too many failing connection attempts.
     # Benefits from verbose sshd logging to observe failed login attempts,
     # so we set that here unless the user overrode it.
-    services.openssh.logLevel = lib.mkDefault "VERBOSE";
+    services.openssh.settings.LogLevel = lib.mkDefault "VERBOSE";
     services.fail2ban.jails.sshd = mkDefault ''
       enabled = true
       port    = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}
diff --git a/nixpkgs/nixos/modules/services/security/fprintd.nix b/nixpkgs/nixos/modules/services/security/fprintd.nix
index 45b370009c38..28f9b5908b53 100644
--- a/nixpkgs/nixos/modules/services/security/fprintd.nix
+++ b/nixpkgs/nixos/modules/services/security/fprintd.nix
@@ -18,7 +18,7 @@ in
 
     services.fprintd = {
 
-      enable = mkEnableOption "fprintd daemon and PAM module for fingerprint readers handling";
+      enable = mkEnableOption (lib.mdDoc "fprintd daemon and PAM module for fingerprint readers handling");
 
       package = mkOption {
         type = types.package;
@@ -31,7 +31,7 @@ in
 
       tod = {
 
-        enable = mkEnableOption "Touch OEM Drivers library support";
+        enable = mkEnableOption (lib.mdDoc "Touch OEM Drivers library support");
 
         driver = mkOption {
           type = types.package;
diff --git a/nixpkgs/nixos/modules/services/security/haka.nix b/nixpkgs/nixos/modules/services/security/haka.nix
index 10b7cef54d33..c93638f44d60 100644
--- a/nixpkgs/nixos/modules/services/security/haka.nix
+++ b/nixpkgs/nixos/modules/services/security/haka.nix
@@ -55,15 +55,15 @@ in
 
     services.haka = {
 
-      enable = mkEnableOption "Haka";
+      enable = mkEnableOption (lib.mdDoc "Haka");
 
       package = mkOption {
         default = pkgs.haka;
         defaultText = literalExpression "pkgs.haka";
         type = types.package;
-        description = "
+        description = lib.mdDoc ''
           Which Haka derivation to use.
-        ";
+        '';
       };
 
       configFile = mkOption {
@@ -103,9 +103,9 @@ in
         description = lib.mdDoc "Whether to enable pcap";
       };
 
-      nfqueue = mkEnableOption "nfqueue";
+      nfqueue = mkEnableOption (lib.mdDoc "nfqueue");
 
-      dump.enable = mkEnableOption "dump";
+      dump.enable = mkEnableOption (lib.mdDoc "dump");
       dump.input  = mkOption {
         default = "/tmp/input.pcap";
         example = "/path/to/file.pcap";
diff --git a/nixpkgs/nixos/modules/services/security/haveged.nix b/nixpkgs/nixos/modules/services/security/haveged.nix
index c65d5ab2923e..db12a28a7d0b 100644
--- a/nixpkgs/nixos/modules/services/security/haveged.nix
+++ b/nixpkgs/nixos/modules/services/security/haveged.nix
@@ -15,10 +15,10 @@ in
 
     services.haveged = {
 
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         haveged entropy daemon, which refills /dev/random when low.
         NOTE: does nothing on kernels newer than 5.6.
-      '';
+      '');
       # source for the note https://github.com/jirka-h/haveged/issues/57
 
       refill_threshold = mkOption {
diff --git a/nixpkgs/nixos/modules/services/security/hockeypuck.nix b/nixpkgs/nixos/modules/services/security/hockeypuck.nix
index 6fdad13f2556..127134bc5dba 100644
--- a/nixpkgs/nixos/modules/services/security/hockeypuck.nix
+++ b/nixpkgs/nixos/modules/services/security/hockeypuck.nix
@@ -7,7 +7,7 @@ in {
   meta.maintainers = with lib.maintainers; [ etu ];
 
   options.services.hockeypuck = {
-    enable = lib.mkEnableOption "Hockeypuck OpenPGP Key Server";
+    enable = lib.mkEnableOption (lib.mdDoc "Hockeypuck OpenPGP Key Server");
 
     port = lib.mkOption {
       default = 11371;
@@ -37,10 +37,10 @@ in {
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configuration file for hockeypuck, here you can override
-        certain settings (<literal>loglevel</literal> and
-        <literal>openpgp.db.dsn</literal>) by just setting those values.
+        certain settings (`loglevel` and
+        `openpgp.db.dsn`) by just setting those values.
 
         For other settings you need to use lib.mkForce to override them.
 
@@ -49,7 +49,7 @@ in {
         the database yourself.
 
         Example:
-        <literal>
+        ```
           services.postgresql = {
             enable = true;
             ensureDatabases = [ "hockeypuck" ];
@@ -58,7 +58,7 @@ in {
               ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
             }];
           };
-        </literal>
+        ```
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/services/security/infnoise.nix b/nixpkgs/nixos/modules/services/security/infnoise.nix
index 4fb8adaf33f8..739a0a84d90b 100644
--- a/nixpkgs/nixos/modules/services/security/infnoise.nix
+++ b/nixpkgs/nixos/modules/services/security/infnoise.nix
@@ -7,10 +7,10 @@ let
 in {
   options = {
     services.infnoise = {
-      enable = mkEnableOption "the Infinite Noise TRNG driver";
+      enable = mkEnableOption (lib.mdDoc "the Infinite Noise TRNG driver");
 
       fillDevRandom = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Whether to run the infnoise driver as a daemon to refill /dev/random.
 
           If disabled, you can use the `infnoise` command-line tool to
diff --git a/nixpkgs/nixos/modules/services/security/kanidm.nix b/nixpkgs/nixos/modules/services/security/kanidm.nix
index 6429273705da..7ec7a1598735 100644
--- a/nixpkgs/nixos/modules/services/security/kanidm.nix
+++ b/nixpkgs/nixos/modules/services/security/kanidm.nix
@@ -7,6 +7,18 @@ let
   serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings);
   clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings);
   unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings);
+  certPaths = builtins.map builtins.dirOf [ cfg.serverSettings.tls_chain cfg.serverSettings.tls_key ];
+
+  # Merge bind mount paths and remove paths where a prefix is already mounted.
+  # This makes sure that if e.g. the tls_chain is in the nix store and /nix/store is already in the mount
+  # paths, no new bind mount is added. Adding subpaths caused problems on ofborg.
+  hasPrefixInList = list: newPath: lib.any (path: lib.hasPrefix (builtins.toString path) (builtins.toString newPath)) list;
+  mergePaths = lib.foldl' (merged: newPath: let
+      # If the new path is a prefix to some existing path, we need to filter it out
+      filteredPaths = lib.filter (p: !lib.hasPrefix (builtins.toString newPath) (builtins.toString p)) merged;
+      # If a prefix of the new path is already in the list, do not add it
+      filteredNew = if hasPrefixInList filteredPaths newPath then [] else [ newPath ];
+    in filteredPaths ++ filteredNew) [];
 
   defaultServiceConfig = {
     BindReadOnlyPaths = [
@@ -16,7 +28,7 @@ let
       "-/etc/hosts"
       "-/etc/localtime"
     ];
-    CapabilityBoundingSet = "";
+    CapabilityBoundingSet = [];
     # ProtectClock= adds DeviceAllow=char-rtc r
     DeviceAllow = "";
     # Implies ProtectSystem=strict, which re-mounts all paths
@@ -53,9 +65,9 @@ let
 in
 {
   options.services.kanidm = {
-    enableClient = lib.mkEnableOption "the Kanidm client";
-    enableServer = lib.mkEnableOption "the Kanidm server";
-    enablePam = lib.mkEnableOption "the Kanidm PAM and NSS integration.";
+    enableClient = lib.mkEnableOption (lib.mdDoc "the Kanidm client");
+    enableServer = lib.mkEnableOption (lib.mdDoc "the Kanidm server");
+    enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration");
 
     serverSettings = lib.mkOption {
       type = lib.types.submodule {
@@ -100,6 +112,14 @@ in
             readOnly = true;
             type = lib.types.path;
           };
+          tls_chain = lib.mkOption {
+            description = lib.mdDoc "TLS chain in pem format.";
+            type = lib.types.path;
+          };
+          tls_key = lib.mkOption {
+            description = lib.mdDoc "TLS key in pem format.";
+            type = lib.types.path;
+          };
           log_level = lib.mkOption {
             description = lib.mdDoc "Log level of the server.";
             default = "default";
@@ -208,22 +228,28 @@ in
       description = "kanidm identity management daemon";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      serviceConfig = defaultServiceConfig // {
-        StateDirectory = "kanidm";
-        StateDirectoryMode = "0700";
-        ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
-        User = "kanidm";
-        Group = "kanidm";
+      serviceConfig = lib.mkMerge [
+        # Merge paths and ignore existing prefixes needs to sidestep mkMerge
+        (defaultServiceConfig // {
+          BindReadOnlyPaths = mergePaths (defaultServiceConfig.BindReadOnlyPaths ++ certPaths);
+        })
+        {
+          StateDirectory = "kanidm";
+          StateDirectoryMode = "0700";
+          ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
+          User = "kanidm";
+          Group = "kanidm";
 
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
-        # This would otherwise override the CAP_NET_BIND_SERVICE capability.
-        PrivateUsers = false;
-        # Port needs to be exposed to the host network
-        PrivateNetwork = false;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        TemporaryFileSystem = "/:ro";
-      };
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+          CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+          # This would otherwise override the CAP_NET_BIND_SERVICE capability.
+          PrivateUsers = lib.mkForce false;
+          # Port needs to be exposed to the host network
+          PrivateNetwork = lib.mkForce false;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+          TemporaryFileSystem = "/:ro";
+        }
+      ];
       environment.RUST_LOG = "info";
     };
 
@@ -232,32 +258,32 @@ in
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       restartTriggers = [ unixConfigFile clientConfigFile ];
-      serviceConfig = defaultServiceConfig // {
-        CacheDirectory = "kanidm-unixd";
-        CacheDirectoryMode = "0700";
-        RuntimeDirectory = "kanidm-unixd";
-        ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
-        User = "kanidm-unixd";
-        Group = "kanidm-unixd";
+      serviceConfig = lib.mkMerge [
+        defaultServiceConfig
+        {
+          CacheDirectory = "kanidm-unixd";
+          CacheDirectoryMode = "0700";
+          RuntimeDirectory = "kanidm-unixd";
+          ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
+          User = "kanidm-unixd";
+          Group = "kanidm-unixd";
 
-        BindReadOnlyPaths = [
-          "/nix/store"
-          "-/etc/resolv.conf"
-          "-/etc/nsswitch.conf"
-          "-/etc/hosts"
-          "-/etc/localtime"
-          "-/etc/kanidm"
-          "-/etc/static/kanidm"
-        ];
-        BindPaths = [
-          # To create the socket
-          "/run/kanidm-unixd:/var/run/kanidm-unixd"
-        ];
-        # Needs to connect to kanidmd
-        PrivateNetwork = false;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
-        TemporaryFileSystem = "/:ro";
-      };
+          BindReadOnlyPaths = [
+            "-/etc/kanidm"
+            "-/etc/static/kanidm"
+            "-/etc/ssl"
+            "-/etc/static/ssl"
+          ];
+          BindPaths = [
+            # To create the socket
+            "/run/kanidm-unixd:/var/run/kanidm-unixd"
+          ];
+          # Needs to connect to kanidmd
+          PrivateNetwork = lib.mkForce false;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+          TemporaryFileSystem = "/:ro";
+        }
+      ];
       environment.RUST_LOG = "info";
     };
 
diff --git a/nixpkgs/nixos/modules/services/security/munge.nix b/nixpkgs/nixos/modules/services/security/munge.nix
index e2b0921b4bc0..4d6fe33f697b 100644
--- a/nixpkgs/nixos/modules/services/security/munge.nix
+++ b/nixpkgs/nixos/modules/services/security/munge.nix
@@ -15,7 +15,7 @@ in
   options = {
 
     services.munge = {
-      enable = mkEnableOption "munge service";
+      enable = mkEnableOption (lib.mdDoc "munge service");
 
       password = mkOption {
         default = "/etc/munge/munge.key";
diff --git a/nixpkgs/nixos/modules/services/security/nginx-sso.nix b/nixpkgs/nixos/modules/services/security/nginx-sso.nix
index 1c23c29781c0..971f22ed3476 100644
--- a/nixpkgs/nixos/modules/services/security/nginx-sso.nix
+++ b/nixpkgs/nixos/modules/services/security/nginx-sso.nix
@@ -8,7 +8,7 @@ let
   configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
 in {
   options.services.nginx.sso = {
-    enable = mkEnableOption "nginx-sso service";
+    enable = mkEnableOption (lib.mdDoc "nginx-sso service");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
index 8b2c7fa21402..12547acabfe0 100644
--- a/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
@@ -72,21 +72,20 @@ let
   } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
 
   mapConfig = key: attr:
-  if attr != null && attr != [] then (
+  optionalString (attr != null && attr != []) (
     if isDerivation attr then mapConfig key (toString attr) else
     if (builtins.typeOf attr) == "set" then concatStringsSep " "
       (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
     if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else
     if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else
     if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
-    "--${key}=${toString attr}")
-    else "";
+    "--${key}=${toString attr}");
 
   configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig);
 in
 {
   options.services.oauth2_proxy = {
-    enable = mkEnableOption "oauth2_proxy";
+    enable = mkEnableOption (lib.mdDoc "oauth2_proxy");
 
     package = mkOption {
       type = types.package;
@@ -160,9 +159,9 @@ in
       domains = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Authenticate emails with the specified domains. Use
-          <literal>*</literal> to authenticate any email.
+          `*` to authenticate any email.
         '';
       };
 
@@ -347,7 +346,7 @@ in
       domain = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Optional cookie domains to force cookies to (ie: `.yourcompany.com`).
           The longest domain matching the request's host will be used (or the shortest
           cookie domain if there is no match).
diff --git a/nixpkgs/nixos/modules/services/security/opensnitch.nix b/nixpkgs/nixos/modules/services/security/opensnitch.nix
index 4558236339e5..98695b1ef060 100644
--- a/nixpkgs/nixos/modules/services/security/opensnitch.nix
+++ b/nixpkgs/nixos/modules/services/security/opensnitch.nix
@@ -5,10 +5,47 @@ with lib;
 let
   cfg = config.services.opensnitch;
   format = pkgs.formats.json {};
+
+  predefinedRules = flip mapAttrs cfg.rules (name: cfg: {
+    file = pkgs.writeText "rule" (builtins.toJSON cfg);
+  });
+
 in {
   options = {
     services.opensnitch = {
-      enable = mkEnableOption "Opensnitch application firewall";
+      enable = mkEnableOption (mdDoc "Opensnitch application firewall");
+
+      rules = mkOption {
+        default = {};
+        example = literalExpression ''
+          {
+            "tor" = {
+              "name" = "tor";
+              "enabled" = true;
+              "action" = "allow";
+              "duration" = "always";
+              "operator" = {
+                "type" ="simple";
+                "sensitive" = false;
+                "operand" = "process.path";
+                "data" = "''${lib.getBin pkgs.tor}/bin/tor";
+              };
+            };
+          };
+        '';
+
+        description = mdDoc ''
+          Declarative configuration of firewall rules.
+          All rules will be stored in `/var/lib/opensnitch/rules`.
+          See [upstream documentation](https://github.com/evilsocket/opensnitch/wiki/Rules)
+          for available options.
+        '';
+
+        type = types.submodule {
+          freeformType = format.type;
+        };
+      };
+
       settings = mkOption {
         type = types.submodule {
           freeformType = format.type;
@@ -18,7 +55,7 @@ in {
 
               Address = mkOption {
                 type = types.str;
-                description = lib.mdDoc ''
+                description = mdDoc ''
                   Unix socket path (unix:///tmp/osui.sock, the "unix:///" part is
                   mandatory) or TCP socket (192.168.1.100:50051).
                 '';
@@ -26,7 +63,7 @@ in {
 
               LogFile = mkOption {
                 type = types.path;
-                description = lib.mdDoc ''
+                description = mdDoc ''
                   File to write logs to (use /dev/stdout to write logs to standard
                   output).
                 '';
@@ -36,7 +73,7 @@ in {
 
             DefaultAction = mkOption {
               type = types.enum [ "allow" "deny" ];
-              description = lib.mdDoc ''
+              description = mdDoc ''
                 Default action whether to block or allow application internet
                 access.
               '';
@@ -46,28 +83,28 @@ in {
               type = types.enum [
                 "once" "always" "until restart" "30s" "5m" "15m" "30m" "1h"
               ];
-              description = lib.mdDoc ''
+              description = mdDoc ''
                 Default duration of firewall rule.
               '';
             };
 
             InterceptUnknown = mkOption {
               type = types.bool;
-              description = lib.mdDoc ''
-                Wheter to intercept spare connections.
+              description = mdDoc ''
+                Whether to intercept spare connections.
               '';
             };
 
             ProcMonitorMethod = mkOption {
               type = types.enum [ "ebpf" "proc" "ftrace" "audit" ];
-              description = lib.mdDoc ''
+              description = mdDoc ''
                 Which process monitoring method to use.
               '';
             };
 
             LogLevel = mkOption {
               type = types.enum [ 0 1 2 3 4 ];
-              description = lib.mdDoc ''
+              description = mdDoc ''
                 Default log level from 0 to 4 (debug, info, important, warning,
                 error).
               '';
@@ -75,7 +112,7 @@ in {
 
             Firewall = mkOption {
               type = types.enum [ "iptables" "nftables" ];
-              description = lib.mdDoc ''
+              description = mdDoc ''
                 Which firewall backend to use.
               '';
             };
@@ -84,14 +121,14 @@ in {
 
               MaxEvents = mkOption {
                 type = types.int;
-                description = lib.mdDoc ''
+                description = mdDoc ''
                   Max events to send to the GUI.
                 '';
               };
 
               MaxStats = mkOption {
                 type = types.int;
-                description = lib.mdDoc ''
+                description = mdDoc ''
                   Max stats per item to keep in backlog.
                 '';
               };
@@ -99,9 +136,8 @@ in {
             };
           };
         };
-        description = lib.mdDoc ''
-          opensnitchd configuration. Refer to
-          <https://github.com/evilsocket/opensnitch/wiki/Configurations>
+        description = mdDoc ''
+          opensnitchd configuration. Refer to [upstream documentation](https://github.com/evilsocket/opensnitch/wiki/Configurations)
           for details on supported values.
         '';
       };
@@ -118,6 +154,25 @@ in {
       services.opensnitchd.wantedBy = [ "multi-user.target" ];
     };
 
+    systemd.services.opensnitchd.preStart = mkIf (cfg.rules != {}) (let
+      rules = flip mapAttrsToList predefinedRules (file: content: {
+        inherit (content) file;
+        local = "/var/lib/opensnitch/rules/${file}.json";
+      });
+    in ''
+      # Remove all firewall rules from `/var/lib/opensnitch/rules` that are symlinks to a store-path,
+      # but aren't declared in `cfg.rules` (i.e. all networks that were "removed" from
+      # `cfg.rules`).
+      find /var/lib/opensnitch/rules -type l -lname '${builtins.storeDir}/*' ${optionalString (rules != {}) ''
+        -not \( ${concatMapStringsSep " -o " ({ local, ... }:
+          "-name '${baseNameOf local}*'")
+        rules} \) \
+      ''} -delete
+      ${concatMapStrings ({ file, local }: ''
+        ln -sf '${file}' "${local}"
+      '') rules}
+    '');
+
     environment.etc."opensnitchd/default-config.json".source = format.generate "default-config.json" cfg.settings;
 
   };
diff --git a/nixpkgs/nixos/modules/services/security/pass-secret-service.nix b/nixpkgs/nixos/modules/services/security/pass-secret-service.nix
index 611cea48ee6f..c3c70d97ff59 100644
--- a/nixpkgs/nixos/modules/services/security/pass-secret-service.nix
+++ b/nixpkgs/nixos/modules/services/security/pass-secret-service.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.services.passSecretService = {
-    enable = mkEnableOption "pass secret service";
+    enable = mkEnableOption (lib.mdDoc "pass secret service");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/security/physlock.nix b/nixpkgs/nixos/modules/services/security/physlock.nix
index 3db9e0ac4458..cd7747659152 100644
--- a/nixpkgs/nixos/modules/services/security/physlock.nix
+++ b/nixpkgs/nixos/modules/services/security/physlock.nix
@@ -57,6 +57,14 @@ in
         '';
       };
 
+      muteKernelMessages = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Disable kernel messages on console while physlock is running.
+        '';
+      };
+
       lockOn = {
 
         suspend = mkOption {
@@ -116,7 +124,7 @@ in
                 ++ cfg.lockOn.extraTargets;
         serviceConfig = {
           Type = "forking";
-          ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}${optionalString (cfg.lockMessage != "") " -p \"${cfg.lockMessage}\""}";
+          ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.muteKernelMessages "m"}${optionalString cfg.disableSysRq "s"}${optionalString (cfg.lockMessage != "") " -p \"${cfg.lockMessage}\""}";
         };
       };
 
diff --git a/nixpkgs/nixos/modules/services/security/privacyidea.nix b/nixpkgs/nixos/modules/services/security/privacyidea.nix
index 29c499e33ab5..664335cb58e8 100644
--- a/nixpkgs/nixos/modules/services/security/privacyidea.nix
+++ b/nixpkgs/nixos/modules/services/security/privacyidea.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.privacyidea;
   opt = options.services.privacyidea;
 
-  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python39; };
+  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python310; };
   python = uwsgi.python3;
   penv = python.withPackages (const [ pkgs.privacyidea ]);
   logCfg = pkgs.writeText "privacyidea-log.cfg" ''
@@ -41,7 +41,7 @@ let
 
   piCfgFile = pkgs.writeText "privacyidea.cfg" ''
     SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
-    SQLALCHEMY_DATABASE_URI = 'postgresql:///privacyidea'
+    SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2:///privacyidea'
     SECRET_KEY = '${cfg.secretKey}'
     PI_PEPPER = '${cfg.pepper}'
     PI_ENCFILE = '${cfg.encFile}'
@@ -61,30 +61,36 @@ let
       (flip mapAttrs cfg.ldap-proxy.settings
         (const (mapAttrs (const renderValue)))));
 
+  privacyidea-token-janitor = pkgs.writeShellScriptBin "privacyidea-token-janitor" ''
+    exec -a privacyidea-token-janitor \
+      /run/wrappers/bin/sudo -u ${cfg.user} \
+      env PRIVACYIDEA_CONFIGFILE=${cfg.stateDir}/privacyidea.cfg \
+      ${penv}/bin/privacyidea-token-janitor $@
+  '';
 in
 
 {
   options = {
     services.privacyidea = {
-      enable = mkEnableOption "PrivacyIDEA";
+      enable = mkEnableOption (lib.mdDoc "PrivacyIDEA");
 
       environmentFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/root/privacyidea.env";
-        description = ''
+        description = lib.mdDoc ''
           File to load as environment file. Environment variables
           from this file will be interpolated into the config file
-          using <package>envsubst</package> which is helpful for specifying
+          using `envsubst` which is helpful for specifying
           secrets:
-          <programlisting>
-          { <xref linkend="opt-services.privacyidea.secretKey"/> = "$SECRET"; }
-          </programlisting>
+          ```
+          { services.privacyidea.secretKey = "$SECRET"; }
+          ```
 
           The environment-file can now specify the actual secret key:
-          <programlisting>
+          ```
           SECRET=veryverytopsecret
-          </programlisting>
+          ```
         '';
       };
 
@@ -178,8 +184,44 @@ in
         description = lib.mdDoc "Group account under which PrivacyIDEA runs.";
       };
 
+      tokenjanitor = {
+        enable = mkEnableOption (lib.mdDoc "automatic runs of the token janitor");
+        interval = mkOption {
+          default = "quarterly";
+          type = types.str;
+          description = lib.mdDoc ''
+            Interval in which the cleanup program is supposed to run.
+            See {manpage}`systemd.time(7)` for further information.
+          '';
+        };
+        action = mkOption {
+          type = types.enum [ "delete" "mark" "disable" "unassign" ];
+          description = lib.mdDoc ''
+            Which action to take for matching tokens.
+          '';
+        };
+        unassigned = mkOption {
+          default = false;
+          type = types.bool;
+          description = lib.mdDoc ''
+            Whether to search for **unassigned** tokens
+            and apply [](#opt-services.privacyidea.tokenjanitor.action)
+            onto them.
+          '';
+        };
+        orphaned = mkOption {
+          default = true;
+          type = types.bool;
+          description = lib.mdDoc ''
+            Whether to search for **orphaned** tokens
+            and apply [](#opt-services.privacyidea.tokenjanitor.action)
+            onto them.
+          '';
+        };
+      };
+
       ldap-proxy = {
-        enable = mkEnableOption "PrivacyIDEA LDAP Proxy";
+        enable = mkEnableOption (lib.mdDoc "PrivacyIDEA LDAP Proxy");
 
         configFile = mkOption {
           type = types.nullOr types.path;
@@ -204,11 +246,11 @@ in
         settings = mkOption {
           type = with types; attrsOf (attrsOf (oneOf [ str bool int (listOf str) ]));
           default = {};
-          description = ''
-            Attribute-set containing the settings for <package>privacyidea-ldap-proxy</package>.
+          description = lib.mdDoc ''
+            Attribute-set containing the settings for `privacyidea-ldap-proxy`.
             It's possible to pass secrets using env-vars as substitutes and
-            use the option <xref linkend="opt-services.privacyidea.ldap-proxy.environmentFile"/>
-            to inject them via <package>envsubst</package>.
+            use the option [](#opt-services.privacyidea.ldap-proxy.environmentFile)
+            to inject them via `envsubst`.
           '';
         };
 
@@ -228,10 +270,60 @@ in
 
     (mkIf cfg.enable {
 
-      environment.systemPackages = [ pkgs.privacyidea ];
+      assertions = [
+        {
+          assertion = cfg.tokenjanitor.enable -> (cfg.tokenjanitor.orphaned || cfg.tokenjanitor.unassigned);
+          message = ''
+            privacyidea-token-janitor has no effect if neither orphaned nor unassigned tokens
+            are to be searched.
+          '';
+        }
+      ];
+
+      environment.systemPackages = [ pkgs.privacyidea (hiPrio privacyidea-token-janitor) ];
 
       services.postgresql.enable = mkDefault true;
 
+      systemd.services.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable {
+        environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
+        path = [ penv ];
+        serviceConfig = {
+          CapabilityBoundingSet = [ "" ];
+          ExecStart = "${pkgs.writeShellScript "pi-token-janitor" ''
+            ${optionalString cfg.tokenjanitor.orphaned ''
+              echo >&2 "Removing orphaned tokens..."
+              privacyidea-token-janitor find \
+                --orphaned true \
+                --action ${cfg.tokenjanitor.action}
+            ''}
+            ${optionalString cfg.tokenjanitor.unassigned ''
+              echo >&2 "Removing unassigned tokens..."
+              privacyidea-token-janitor find \
+                --assigned false \
+                --action ${cfg.tokenjanitor.action}
+            ''}
+          ''}";
+          Group = cfg.group;
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectSystem = "strict";
+          ReadWritePaths = cfg.stateDir;
+          Type = "oneshot";
+          User = cfg.user;
+          WorkingDirectory = cfg.stateDir;
+        };
+      };
+      systemd.timers.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable {
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = cfg.tokenjanitor.interval;
+        timerConfig.Persistent = true;
+      };
+
       systemd.services.privacyidea = let
         piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
           uwsgi = {
diff --git a/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix b/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
index 6626ea213625..e7897c3324cf 100644
--- a/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
+++ b/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
@@ -27,13 +27,13 @@ in {
       fastcgi.shibAuthorizerPort = mkOption {
         type = types.int;
         default = 9100;
-        description = lib.mdDoc "Port for shibauthorizer FastCGI proccess to bind to";
+        description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to";
       };
 
       fastcgi.shibResponderPort = mkOption {
         type = types.int;
         default = 9101;
-        description = lib.mdDoc "Port for shibauthorizer FastCGI proccess to bind to";
+        description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/security/sks.nix b/nixpkgs/nixos/modules/services/security/sks.nix
index e9205e4855e5..550b61916a22 100644
--- a/nixpkgs/nixos/modules/services/security/sks.nix
+++ b/nixpkgs/nixos/modules/services/security/sks.nix
@@ -16,10 +16,10 @@ in {
 
     services.sks = {
 
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         SKS (synchronizing key server for OpenPGP) and start the database
         server. You need to create "''${dataDir}/dump/*.gpg" for the initial
-        import'';
+        import'');
 
       package = mkOption {
         default = pkgs.sks;
diff --git a/nixpkgs/nixos/modules/services/security/sslmate-agent.nix b/nixpkgs/nixos/modules/services/security/sslmate-agent.nix
index c850eb22a031..2d72406f0db8 100644
--- a/nixpkgs/nixos/modules/services/security/sslmate-agent.nix
+++ b/nixpkgs/nixos/modules/services/security/sslmate-agent.nix
@@ -10,7 +10,7 @@ in {
 
   options = {
     services.sslmate-agent = {
-      enable = mkEnableOption "sslmate-agent, a daemon for managing SSL/TLS certificates on a server";
+      enable = mkEnableOption (lib.mdDoc "sslmate-agent, a daemon for managing SSL/TLS certificates on a server");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/security/step-ca.nix b/nixpkgs/nixos/modules/services/security/step-ca.nix
index 1afcf659632e..433f162ecb86 100644
--- a/nixpkgs/nixos/modules/services/security/step-ca.nix
+++ b/nixpkgs/nixos/modules/services/security/step-ca.nix
@@ -8,8 +8,8 @@ in
 
   options = {
     services.step-ca = {
-      enable = lib.mkEnableOption "the smallstep certificate authority server";
-      openFirewall = lib.mkEnableOption "opening the certificate authority server port";
+      enable = lib.mkEnableOption (lib.mdDoc "the smallstep certificate authority server");
+      openFirewall = lib.mkEnableOption (lib.mdDoc "opening the certificate authority server port");
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.step-ca;
@@ -34,42 +34,38 @@ in
       };
       settings = lib.mkOption {
         type = with lib.types; attrsOf anything;
-        description = ''
-          Settings that go into <filename>ca.json</filename>. See
-          <link xlink:href="https://smallstep.com/docs/step-ca/configuration">the step-ca manual</link>
+        description = lib.mdDoc ''
+          Settings that go into {file}`ca.json`. See
+          [the step-ca manual](https://smallstep.com/docs/step-ca/configuration)
           for more information. The easiest way to
-          configure this module would be to run <literal>step ca init</literal>
-          to generate <filename>ca.json</filename> and then import it using
-          <literal>builtins.fromJSON</literal>.
-          <link xlink:href="https://smallstep.com/docs/step-cli/basic-crypto-operations#run-an-offline-x509-certificate-authority">This article</link>
+          configure this module would be to run `step ca init`
+          to generate {file}`ca.json` and then import it using
+          `builtins.fromJSON`.
+          [This article](https://smallstep.com/docs/step-cli/basic-crypto-operations#run-an-offline-x509-certificate-authority)
           may also be useful if you want to customize certain aspects of
           certificate generation for your CA.
-          You need to change the database storage path to <filename>/var/lib/step-ca/db</filename>.
+          You need to change the database storage path to {file}`/var/lib/step-ca/db`.
 
-          <warning>
-            <para>
-              The <option>services.step-ca.settings.address</option> option
-              will be ignored and overwritten by
-              <option>services.step-ca.address</option> and
-              <option>services.step-ca.port</option>.
-            </para>
-          </warning>
+          ::: {.warning}
+          The {option}`services.step-ca.settings.address` option
+          will be ignored and overwritten by
+          {option}`services.step-ca.address` and
+          {option}`services.step-ca.port`.
+          :::
         '';
       };
       intermediatePasswordFile = lib.mkOption {
         type = lib.types.path;
         example = "/run/keys/smallstep-password";
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing the password for the intermediate
           certificate private key.
 
-          <warning>
-            <para>
-              Make sure to use a quoted absolute path instead of a path literal
-              to prevent it from being copied to the globally readable Nix
-              store.
-            </para>
-          </warning>
+          ::: {.warning}
+          Make sure to use a quoted absolute path instead of a path literal
+          to prevent it from being copied to the globally readable Nix
+          store.
+          :::
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/security/tor.nix b/nixpkgs/nixos/modules/services/security/tor.nix
index 84f577c3853b..2aa2964f8818 100644
--- a/nixpkgs/nixos/modules/services/security/tor.nix
+++ b/nixpkgs/nixos/modules/services/security/tor.nix
@@ -9,7 +9,7 @@ let
   stateDir = "/var/lib/tor";
   runDir = "/run/tor";
   descriptionGeneric = option: ''
-    See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en#${option}">torrc manual</link>.
+    See [torrc manual](https://2019.www.torproject.org/docs/tor-manual.html.en#${option}).
   '';
   bindsPrivilegedPort =
     any (p0:
@@ -30,35 +30,35 @@ let
   optionBool = optionName: mkOption {
     type = with types; nullOr bool;
     default = null;
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   optionInt = optionName: mkOption {
     type = with types; nullOr int;
     default = null;
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   optionString = optionName: mkOption {
     type = with types; nullOr str;
     default = null;
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   optionStrings = optionName: mkOption {
     type = with types; listOf str;
     default = [];
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   optionAddress = mkOption {
     type = with types; nullOr str;
     default = null;
     example = "0.0.0.0";
-    description = ''
+    description = lib.mdDoc ''
       IPv4 or IPv6 (if between brackets) address.
     '';
   };
   optionUnix = mkOption {
     type = with types; nullOr path;
     default = null;
-    description = ''
+    description = lib.mdDoc ''
       Unix domain socket path to use.
     '';
   };
@@ -69,7 +69,7 @@ let
   optionPorts = optionName: mkOption {
     type = with types; listOf port;
     default = [];
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   optionIsolablePort = with types; oneOf [
     port (enum ["auto"])
@@ -89,7 +89,7 @@ let
   optionIsolablePorts = optionName: mkOption {
     default = [];
     type = with types; either optionIsolablePort (listOf optionIsolablePort);
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   isolateFlags = [
     "IsolateClientAddr"
@@ -144,17 +144,17 @@ let
         };
       }))
     ]))];
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
-  optionBandwith = optionName: mkOption {
+  optionBandwidth = optionName: mkOption {
     type = with types; nullOr (either int str);
     default = null;
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
   optionPath = optionName: mkOption {
     type = with types; nullOr path;
     default = null;
-    description = descriptionGeneric optionName;
+    description = lib.mdDoc (descriptionGeneric optionName);
   };
 
   mkValueString = k: v:
@@ -205,7 +205,7 @@ in
     (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "isolationOptions" ] "Use services.tor.settings.TransPort instead.")
     (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "listenAddress" ] "Use services.tor.settings.TransPort instead.")
     (mkRenamedOptionModule [ "services" "tor" "controlPort" ] [ "services" "tor" "settings" "ControlPort" ])
-    (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Plese use services.tor.settings instead.")
+    (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Please use services.tor.settings instead.")
     (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "onionServices" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "accountingMax" ] [ "services" "tor" "settings" "AccountingMax" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "accountingStart" ] [ "services" "tor" "settings" "AccountingStart" ])
@@ -224,11 +224,11 @@ in
 
   options = {
     services.tor = {
-      enable = mkEnableOption ''Tor daemon.
+      enable = mkEnableOption (lib.mdDoc ''Tor daemon.
         By default, the daemon is run without
-        relay, exit, bridge or client connectivity'';
+        relay, exit, bridge or client connectivity'');
 
-      openFirewall = mkEnableOption "opening of the relay port(s) in the firewall";
+      openFirewall = mkEnableOption (lib.mdDoc "opening of the relay port(s) in the firewall");
 
       package = mkOption {
         type = types.package;
@@ -237,19 +237,19 @@ in
         description = lib.mdDoc "Tor package to use.";
       };
 
-      enableGeoIP = mkEnableOption ''use of GeoIP databases.
+      enableGeoIP = mkEnableOption (lib.mdDoc ''use of GeoIP databases.
         Disabling this will disable by-country statistics for bridges and relays
-        and some client and third-party software functionality'' // { default = true; };
+        and some client and third-party software functionality'') // { default = true; };
 
-      controlSocket.enable = mkEnableOption ''control socket,
-        created in <literal>${runDir}/control</literal>'';
+      controlSocket.enable = mkEnableOption (lib.mdDoc ''control socket,
+        created in `${runDir}/control`'');
 
       client = {
-        enable = mkEnableOption ''the routing of application connections.
-          You might want to disable this if you plan running a dedicated Tor relay'';
+        enable = mkEnableOption (lib.mdDoc ''the routing of application connections.
+          You might want to disable this if you plan running a dedicated Tor relay'');
 
-        transparentProxy.enable = mkEnableOption "transparent proxy";
-        dns.enable = mkEnableOption "DNS resolver";
+        transparentProxy.enable = mkEnableOption (lib.mdDoc "transparent proxy");
+        dns.enable = mkEnableOption (lib.mdDoc "DNS resolver");
 
         socksListenAddress = mkOption {
           type = optionSOCKSPort false;
@@ -262,7 +262,7 @@ in
         };
 
         onionServices = mkOption {
-          description = descriptionGeneric "HiddenServiceDir";
+          description = lib.mdDoc (descriptionGeneric "HiddenServiceDir");
           default = {};
           example = {
             "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" = {
@@ -271,11 +271,14 @@ in
           };
           type = types.attrsOf (types.submodule ({name, config, ...}: {
             options.clientAuthorizations = mkOption {
-              description = ''
+              description = lib.mdDoc ''
                 Clients' authorizations for a v3 onion service,
                 as a list of files containing each one private key, in the format:
-                <screen>descriptor:x25519:&lt;base32-private-key&gt;</screen>
-              '' + descriptionGeneric "_client_authorization";
+                ```
+                descriptor:x25519:<base32-private-key>
+                ```
+                ${descriptionGeneric "_client_authorization"}
+              '';
               type = with types; listOf path;
               default = [];
               example = ["/run/keys/tor/alice.prv.x25519"];
@@ -285,151 +288,109 @@ in
       };
 
       relay = {
-        enable = mkEnableOption ''relaying of Tor traffic for others.
+        enable = mkEnableOption (lib.mdDoc "tor relaying") // {
+          description = lib.mdDoc ''
+            Whether to enable relaying of Tor traffic for others.
 
-          See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay"/>
-          for details.
+            See <https://www.torproject.org/docs/tor-doc-relay>
+            for details.
 
-          Setting this to true requires setting
-          <option>services.tor.relay.role</option>
-          and
-          <option>services.tor.settings.ORPort</option>
-          options'';
+            Setting this to true requires setting
+            {option}`services.tor.relay.role`
+            and
+            {option}`services.tor.settings.ORPort`
+            options.
+          '';
+        };
 
         role = mkOption {
           type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
-          description = ''
+          description = lib.mdDoc ''
             Your role in Tor network. There're several options:
 
-            <variablelist>
-            <varlistentry>
-              <term><literal>exit</literal></term>
-              <listitem>
-                <para>
-                  An exit relay. This allows Tor users to access regular
-                  Internet services through your public IP.
-                </para>
+            - `exit`:
+              An exit relay. This allows Tor users to access regular
+              Internet services through your public IP.
 
-                <important><para>
-                  Running an exit relay may expose you to abuse
-                  complaints. See
-                  <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies"/>
-                  for more info.
-                </para></important>
+              You can specify which services Tor users may access via
+              your exit relay using {option}`settings.ExitPolicy` option.
 
-                <para>
-                  You can specify which services Tor users may access via
-                  your exit relay using <option>settings.ExitPolicy</option> option.
-                </para>
-              </listitem>
-            </varlistentry>
+            - `relay`:
+              Regular relay. This allows Tor users to relay onion
+              traffic to other Tor nodes, but not to public
+              Internet.
 
-            <varlistentry>
-              <term><literal>relay</literal></term>
-              <listitem>
-                <para>
-                  Regular relay. This allows Tor users to relay onion
-                  traffic to other Tor nodes, but not to public
-                  Internet.
-                </para>
+              See
+              <https://www.torproject.org/docs/tor-doc-relay.html.en>
+              for more info.
 
-                <important><para>
-                  Note that some misconfigured and/or disrespectful
-                  towards privacy sites will block you even if your
-                  relay is not an exit relay. That is, just being listed
-                  in a public relay directory can have unwanted
-                  consequences.
+            - `bridge`:
+              Regular bridge. Works like a regular relay, but
+              doesn't list you in the public relay directory and
+              hides your Tor node behind obfs4proxy.
 
-                  Which means you might not want to use
-                  this role if you browse public Internet from the same
-                  network as your relay, unless you want to write
-                  e-mails to those sites (you should!).
-                </para></important>
+              Using this option will make Tor advertise your bridge
+              to users through various mechanisms like
+              <https://bridges.torproject.org/>, though.
 
-                <para>
-                  See
-                  <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en"/>
-                  for more info.
-                </para>
-              </listitem>
-            </varlistentry>
+              See <https://www.torproject.org/docs/bridges.html.en>
+              for more info.
 
-            <varlistentry>
-              <term><literal>bridge</literal></term>
-              <listitem>
-                <para>
-                  Regular bridge. Works like a regular relay, but
-                  doesn't list you in the public relay directory and
-                  hides your Tor node behind obfs4proxy.
-                </para>
+            - `private-bridge`:
+              Private bridge. Works like regular bridge, but does
+              not advertise your node in any way.
 
-                <para>
-                  Using this option will make Tor advertise your bridge
-                  to users through various mechanisms like
-                  <link xlink:href="https://bridges.torproject.org/"/>, though.
-                </para>
+              Using this role means that you won't contribute to Tor
+              network in any way unless you advertise your node
+              yourself in some way.
 
-                <important>
-                  <para>
-                    WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
-                    Consult with your lawyer when in doubt.
-                  </para>
+              Use this if you want to run a private bridge, for
+              example because you'll give out your bridge addr
+              manually to your friends.
 
-                  <para>
-                    This role should be safe to use in most situations
-                    (unless the act of forwarding traffic for others is
-                    a punishable offence under your local laws, which
-                    would be pretty insane as it would make ISP illegal).
-                  </para>
-                </important>
+              Switching to this role after measurable time in
+              "bridge" role is pretty useless as some Tor users
+              would have learned about your node already. In the
+              latter case you can still change
+              {option}`port` option.
 
-                <para>
-                  See <link xlink:href="https://www.torproject.org/docs/bridges.html.en"/>
-                  for more info.
-                </para>
-              </listitem>
-            </varlistentry>
+              See <https://www.torproject.org/docs/bridges.html.en>
+              for more info.
 
-            <varlistentry>
-              <term><literal>private-bridge</literal></term>
-              <listitem>
-                <para>
-                  Private bridge. Works like regular bridge, but does
-                  not advertise your node in any way.
-                </para>
+            ::: {.important}
+            Running an exit relay may expose you to abuse
+            complaints. See
+            <https://www.torproject.org/faq.html.en#ExitPolicies>
+            for more info.
+            :::
 
-                <para>
-                  Using this role means that you won't contribute to Tor
-                  network in any way unless you advertise your node
-                  yourself in some way.
-                </para>
+            ::: {.important}
+            Note that some misconfigured and/or disrespectful
+            towards privacy sites will block you even if your
+            relay is not an exit relay. That is, just being listed
+            in a public relay directory can have unwanted
+            consequences.
 
-                <para>
-                  Use this if you want to run a private bridge, for
-                  example because you'll give out your bridge addr
-                  manually to your friends.
-                </para>
+            Which means you might not want to use
+            this role if you browse public Internet from the same
+            network as your relay, unless you want to write
+            e-mails to those sites (you should!).
+            :::
 
-                <para>
-                  Switching to this role after measurable time in
-                  "bridge" role is pretty useless as some Tor users
-                  would have learned about your node already. In the
-                  latter case you can still change
-                  <option>port</option> option.
-                </para>
+            ::: {.important}
+            WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
+            Consult with your lawyer when in doubt.
 
-                <para>
-                  See <link xlink:href="https://www.torproject.org/docs/bridges.html.en"/>
-                  for more info.
-                </para>
-              </listitem>
-            </varlistentry>
-            </variablelist>
+            The `bridge` role should be safe to use in most situations
+            (unless the act of forwarding traffic for others is
+            a punishable offence under your local laws, which
+            would be pretty insane as it would make ISP illegal).
+            :::
           '';
         };
 
         onionServices = mkOption {
-          description = descriptionGeneric "HiddenServiceDir";
+          description = lib.mdDoc (descriptionGeneric "HiddenServiceDir");
           default = {};
           example = {
             "example.org/www" = {
@@ -462,7 +423,7 @@ in
               '';
             };
             options.authorizeClient = mkOption {
-              description = descriptionGeneric "HiddenServiceAuthorizeClient";
+              description = lib.mdDoc (descriptionGeneric "HiddenServiceAuthorizeClient");
               default = null;
               type = types.nullOr (types.submodule ({...}: {
                 options = {
@@ -487,17 +448,20 @@ in
               }));
             };
             options.authorizedClients = mkOption {
-              description = ''
+              description = lib.mdDoc ''
                 Authorized clients for a v3 onion service,
                 as a list of public key, in the format:
-                <screen>descriptor:x25519:&lt;base32-public-key&gt;</screen>
-              '' + descriptionGeneric "_client_authorization";
+                ```
+                descriptor:x25519:<base32-public-key>
+                ```
+                ${descriptionGeneric "_client_authorization"}
+              '';
               type = with types; listOf str;
               default = [];
               example = ["descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];
             };
             options.map = mkOption {
-              description = descriptionGeneric "HiddenServicePort";
+              description = lib.mdDoc (descriptionGeneric "HiddenServicePort");
               type = with types; listOf (oneOf [
                 port (submodule ({...}: {
                   options = {
@@ -518,14 +482,15 @@ in
               apply = map (v: if isInt v then {port=v; target=null;} else v);
             };
             options.version = mkOption {
-              description = descriptionGeneric "HiddenServiceVersion";
+              description = lib.mdDoc (descriptionGeneric "HiddenServiceVersion");
               type = with types; nullOr (enum [2 3]);
               default = null;
             };
             options.settings = mkOption {
-              description = ''
+              description = lib.mdDoc ''
                 Settings of the onion service.
-              '' + descriptionGeneric "_hidden_service_options";
+                ${descriptionGeneric "_hidden_service_options"}
+              '';
               default = {};
               type = types.submodule {
                 freeformType = with types;
@@ -535,18 +500,18 @@ in
                 options.HiddenServiceAllowUnknownPorts = optionBool "HiddenServiceAllowUnknownPorts";
                 options.HiddenServiceDirGroupReadable = optionBool "HiddenServiceDirGroupReadable";
                 options.HiddenServiceExportCircuitID = mkOption {
-                  description = descriptionGeneric "HiddenServiceExportCircuitID";
+                  description = lib.mdDoc (descriptionGeneric "HiddenServiceExportCircuitID");
                   type = with types; nullOr (enum ["haproxy"]);
                   default = null;
                 };
                 options.HiddenServiceMaxStreams = mkOption {
-                  description = descriptionGeneric "HiddenServiceMaxStreams";
+                  description = lib.mdDoc (descriptionGeneric "HiddenServiceMaxStreams");
                   type = with types; nullOr (ints.between 0 65535);
                   default = null;
                 };
                 options.HiddenServiceMaxStreamsCloseCircuit = optionBool "HiddenServiceMaxStreamsCloseCircuit";
                 options.HiddenServiceNumIntroductionPoints = mkOption {
-                  description = descriptionGeneric "HiddenServiceNumIntroductionPoints";
+                  description = lib.mdDoc (descriptionGeneric "HiddenServiceNumIntroductionPoints");
                   type = with types; nullOr (ints.between 0 20);
                   default = null;
                 };
@@ -581,7 +546,7 @@ in
             };
           options.Address = optionString "Address";
           options.AssumeReachable = optionBool "AssumeReachable";
-          options.AccountingMax = optionBandwith "AccountingMax";
+          options.AccountingMax = optionBandwidth "AccountingMax";
           options.AccountingStart = optionString "AccountingStart";
           options.AuthDirHasIPv6Connectivity = optionBool "AuthDirHasIPv6Connectivity";
           options.AuthDirListBadExits = optionBool "AuthDirListBadExits";
@@ -594,8 +559,8 @@ in
             default = [".onion" ".exit"];
             example = [".onion"];
           };
-          options.BandwidthBurst = optionBandwith "BandwidthBurst";
-          options.BandwidthRate = optionBandwith "BandwidthRate";
+          options.BandwidthBurst = optionBandwidth "BandwidthBurst";
+          options.BandwidthRate = optionBandwidth "BandwidthRate";
           options.BridgeAuthoritativeDir = optionBool "BridgeAuthoritativeDir";
           options.BridgeRecordUsageByCountry = optionBool "BridgeRecordUsageByCountry";
           options.BridgeRelay = optionBool "BridgeRelay" // { default = false; };
@@ -605,7 +570,7 @@ in
           options.ClientAutoIPv6ORPort = optionBool "ClientAutoIPv6ORPort";
           options.ClientDNSRejectInternalAddresses = optionBool "ClientDNSRejectInternalAddresses";
           options.ClientOnionAuthDir = mkOption {
-            description = descriptionGeneric "ClientOnionAuthDir";
+            description = lib.mdDoc (descriptionGeneric "ClientOnionAuthDir");
             default = null;
             type = with types; nullOr path;
           };
@@ -618,7 +583,7 @@ in
           options.ConstrainedSockets = optionBool "ConstrainedSockets";
           options.ContactInfo = optionString "ContactInfo";
           options.ControlPort = mkOption rec {
-            description = descriptionGeneric "ControlPort";
+            description = lib.mdDoc (descriptionGeneric "ControlPort");
             default = [];
             example = [{port = 9051;}];
             type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
@@ -653,7 +618,7 @@ in
           options.DormantTimeoutDisabledByIdleStreams = optionBool "DormantTimeoutDisabledByIdleStreams";
           options.DirCache = optionBool "DirCache";
           options.DirPolicy = mkOption {
-            description = descriptionGeneric "DirPolicy";
+            description = lib.mdDoc (descriptionGeneric "DirPolicy");
             type = with types; listOf str;
             default = [];
             example = ["accept *:*"];
@@ -680,7 +645,7 @@ in
           options.ExitPortStatistics = optionBool "ExitPortStatistics";
           options.ExitRelay = optionBool "ExitRelay"; # default is null and like "auto"
           options.ExtORPort = mkOption {
-            description = descriptionGeneric "ExtORPort";
+            description = lib.mdDoc (descriptionGeneric "ExtORPort");
             default = null;
             type = with types; nullOr (oneOf [
               port (enum ["auto"]) (submodule ({...}: {
@@ -709,7 +674,7 @@ in
           options.GeoIPv6File = optionPath "GeoIPv6File";
           options.GuardfractionFile = optionPath "GuardfractionFile";
           options.HidServAuth = mkOption {
-            description = descriptionGeneric "HidServAuth";
+            description = lib.mdDoc (descriptionGeneric "HidServAuth");
             default = [];
             type = with types; listOf (oneOf [
               (submodule {
@@ -744,7 +709,7 @@ in
           options.LogMessageDomains = optionBool "LogMessageDomains";
           options.LongLivedPorts = optionPorts "LongLivedPorts";
           options.MainloopStats = optionBool "MainloopStats";
-          options.MaxAdvertisedBandwidth = optionBandwith "MaxAdvertisedBandwidth";
+          options.MaxAdvertisedBandwidth = optionBandwidth "MaxAdvertisedBandwidth";
           options.MaxCircuitDirtiness = optionInt "MaxCircuitDirtiness";
           options.MaxClientCircuitsPending = optionInt "MaxClientCircuitsPending";
           options.NATDPort = optionIsolablePorts "NATDPort";
@@ -754,21 +719,21 @@ in
           options.OfflineMasterKey = optionBool "OfflineMasterKey";
           options.OptimisticData = optionBool "OptimisticData"; # default is null and like "auto"
           options.PaddingStatistics = optionBool "PaddingStatistics";
-          options.PerConnBWBurst = optionBandwith "PerConnBWBurst";
-          options.PerConnBWRate = optionBandwith "PerConnBWRate";
+          options.PerConnBWBurst = optionBandwidth "PerConnBWBurst";
+          options.PerConnBWRate = optionBandwidth "PerConnBWRate";
           options.PidFile = optionPath "PidFile";
           options.ProtocolWarnings = optionBool "ProtocolWarnings";
           options.PublishHidServDescriptors = optionBool "PublishHidServDescriptors";
           options.PublishServerDescriptor = mkOption {
-            description = descriptionGeneric "PublishServerDescriptor";
+            description = lib.mdDoc (descriptionGeneric "PublishServerDescriptor");
             type = with types; nullOr (enum [false true 0 1 "0" "1" "v3" "bridge"]);
             default = null;
           };
           options.ReducedExitPolicy = optionBool "ReducedExitPolicy";
           options.RefuseUnknownExits = optionBool "RefuseUnknownExits"; # default is null and like "auto"
           options.RejectPlaintextPorts = optionPorts "RejectPlaintextPorts";
-          options.RelayBandwidthBurst = optionBandwith "RelayBandwidthBurst";
-          options.RelayBandwidthRate = optionBandwith "RelayBandwidthRate";
+          options.RelayBandwidthBurst = optionBandwidth "RelayBandwidthBurst";
+          options.RelayBandwidthRate = optionBandwidth "RelayBandwidthRate";
           #options.RunAsDaemon
           options.Sandbox = optionBool "Sandbox";
           options.ServerDNSAllowBrokenConfig = optionBool "ServerDNSAllowBrokenConfig";
@@ -778,7 +743,7 @@ in
           options.ServerDNSResolvConfFile = optionPath "ServerDNSResolvConfFile";
           options.ServerDNSSearchDomains = optionBool "ServerDNSSearchDomains";
           options.ServerTransportPlugin = mkOption {
-            description = descriptionGeneric "ServerTransportPlugin";
+            description = lib.mdDoc (descriptionGeneric "ServerTransportPlugin");
             default = null;
             type = with types; nullOr (submodule ({...}: {
               options = {
@@ -797,13 +762,13 @@ in
           options.ShutdownWaitLength = mkOption {
             type = types.int;
             default = 30;
-            description = descriptionGeneric "ShutdownWaitLength";
+            description = lib.mdDoc (descriptionGeneric "ShutdownWaitLength");
           };
           options.SocksPolicy = optionStrings "SocksPolicy" // {
             example = ["accept *:*"];
           };
           options.SOCKSPort = mkOption {
-            description = descriptionGeneric "SOCKSPort";
+            description = lib.mdDoc (descriptionGeneric "SOCKSPort");
             default = if cfg.settings.HiddenServiceNonAnonymousMode == true then [{port = 0;}] else [];
             defaultText = literalExpression ''
               if config.${opt.settings}.HiddenServiceNonAnonymousMode == true
@@ -816,7 +781,7 @@ in
           options.TestingTorNetwork = optionBool "TestingTorNetwork";
           options.TransPort = optionIsolablePorts "TransPort";
           options.TransProxyType = mkOption {
-            description = descriptionGeneric "TransProxyType";
+            description = lib.mdDoc (descriptionGeneric "TransProxyType");
             type = with types; nullOr (enum ["default" "TPROXY" "ipfw" "pf-divert"]);
             default = null;
           };
@@ -851,13 +816,13 @@ in
         always create a container/VM with a separate Tor daemon instance.
       '' ++
       flatten (mapAttrsToList (n: o:
-        optional (o.settings.HiddenServiceVersion == 2) [
+        optionals (o.settings.HiddenServiceVersion == 2) [
           (optional (o.settings.HiddenServiceExportCircuitID != null) ''
             HiddenServiceExportCircuitID is used in the HiddenService: ${n}
             but this option is only for v3 hidden services.
           '')
         ] ++
-        optional (o.settings.HiddenServiceVersion != 2) [
+        optionals (o.settings.HiddenServiceVersion != 2) [
           (optional (o.settings.HiddenServiceAuthorizeClient != null) ''
             HiddenServiceAuthorizeClient is used in the HiddenService: ${n}
             but this option is only for v2 hidden services.
diff --git a/nixpkgs/nixos/modules/services/security/torify.nix b/nixpkgs/nixos/modules/services/security/torify.nix
index 770e445d733f..4d311adebcae 100644
--- a/nixpkgs/nixos/modules/services/security/torify.nix
+++ b/nixpkgs/nixos/modules/services/security/torify.nix
@@ -27,16 +27,16 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to build tsocks wrapper script to relay application traffic via Tor.
 
-          <important>
-            <para>You shouldn't use this unless you know what you're
-            doing because your installation of Tor already comes with
-            its own superior (doesn't leak DNS queries)
-            <literal>torsocks</literal> wrapper which does pretty much
-            exactly the same thing as this.</para>
-          </important>
+          ::: {.important}
+          You shouldn't use this unless you know what you're
+          doing because your installation of Tor already comes with
+          its own superior (doesn't leak DNS queries)
+          `torsocks` wrapper which does pretty much
+          exactly the same thing as this.
+          :::
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/security/usbguard.nix b/nixpkgs/nixos/modules/services/security/usbguard.nix
index 242475939068..1d846b194077 100644
--- a/nixpkgs/nixos/modules/services/security/usbguard.nix
+++ b/nixpkgs/nixos/modules/services/security/usbguard.nix
@@ -39,7 +39,7 @@ in
 
   options = {
     services.usbguard = {
-      enable = mkEnableOption "USBGuard daemon";
+      enable = mkEnableOption (lib.mdDoc "USBGuard daemon");
 
       package = mkOption {
         type = types.package;
@@ -118,9 +118,9 @@ in
         description = lib.mdDoc ''
           The  USBGuard  daemon  modifies  some attributes of controller
           devices like the default authorization state of new child device
-          instances. Using this setting, you can controll whether the daemon
+          instances. Using this setting, you can control whether the daemon
           will try to restore the attribute values to the state before
-          modificaton on shutdown.
+          modification on shutdown.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/security/vault-agent.nix b/nixpkgs/nixos/modules/services/security/vault-agent.nix
new file mode 100644
index 000000000000..17b8ff83592e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/vault-agent.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  format = pkgs.formats.json { };
+  commonOptions = { pkgName, flavour ? pkgName }: mkOption {
+    default = { };
+    description = mdDoc ''
+      Attribute set of ${flavour} instances.
+      Creates independent `${flavour}-''${name}.service` systemd units for each instance defined here.
+    '';
+    type = with types; attrsOf (submodule ({ name, ... }: {
+      options = {
+        enable = mkEnableOption (mdDoc "this ${flavour} instance") // { default = true; };
+
+        package = mkPackageOptionMD pkgs pkgName { };
+
+        user = mkOption {
+          type = types.str;
+          default = "root";
+          description = mdDoc ''
+            User under which this instance runs.
+          '';
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "root";
+          description = mdDoc ''
+            Group under which this instance runs.
+          '';
+        };
+
+        settings = mkOption {
+          type = types.submodule {
+            freeformType = format.type;
+
+            options = {
+              pid_file = mkOption {
+                default = "/run/${flavour}/${name}.pid";
+                type = types.str;
+                description = mdDoc ''
+                  Path to use for the pid file.
+                '';
+              };
+
+              template = mkOption {
+                default = [ ];
+                type = with types; listOf (attrsOf anything);
+                description =
+                  let upstreamDocs =
+                    if flavour == "vault-agent"
+                    then "https://developer.hashicorp.com/vault/docs/agent/template"
+                    else "https://github.com/hashicorp/consul-template/blob/main/docs/configuration.md#templates";
+                  in
+                  mdDoc ''
+                    Template section of ${flavour}.
+                    Refer to <${upstreamDocs}> for supported values.
+                  '';
+              };
+            };
+          };
+
+          default = { };
+
+          description =
+            let upstreamDocs =
+              if flavour == "vault-agent"
+              then "https://developer.hashicorp.com/vault/docs/agent#configuration-file-options"
+              else "https://github.com/hashicorp/consul-template/blob/main/docs/configuration.md#configuration-file";
+            in
+            mdDoc ''
+              Free-form settings written directly to the `config.json` file.
+              Refer to <${upstreamDocs}> for supported values.
+
+              ::: {.note}
+              Resulting format is JSON not HCL.
+              Refer to <https://www.hcl2json.com/> if you are unsure how to convert HCL options to JSON.
+              :::
+            '';
+        };
+      };
+    }));
+  };
+
+  createAgentInstance = { instance, name, flavour }:
+    let
+      configFile = format.generate "${name}.json" instance.settings;
+    in
+    mkIf (instance.enable) {
+      description = "${flavour} daemon - ${name}";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.getent ];
+      startLimitIntervalSec = 60;
+      startLimitBurst = 3;
+      serviceConfig = {
+        User = instance.user;
+        Group = instance.group;
+        RuntimeDirectory = flavour;
+        ExecStart = "${getExe instance.package} ${optionalString ((getName instance.package) == "vault") "agent"} -config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        KillSignal = "SIGINT";
+        TimeoutStopSec = "30s";
+        Restart = "on-failure";
+      };
+    };
+in
+{
+  options = {
+    services.consul-template.instances = commonOptions { pkgName = "consul-template"; };
+    services.vault-agent.instances = commonOptions { pkgName = "vault"; flavour = "vault-agent"; };
+  };
+
+  config = mkMerge (map
+    (flavour:
+      let cfg = config.services.${flavour}; in
+      mkIf (cfg.instances != { }) {
+        systemd.services = mapAttrs'
+          (name: instance: nameValuePair "${flavour}-${name}" (createAgentInstance { inherit name instance flavour; }))
+          cfg.instances;
+      })
+    [ "consul-template" "vault-agent" ]);
+
+  meta.maintainers = with maintainers; [ emilylange tcheronneau ];
+}
+
diff --git a/nixpkgs/nixos/modules/services/security/vault.nix b/nixpkgs/nixos/modules/services/security/vault.nix
index c471bf01869b..7b9e31a8d990 100644
--- a/nixpkgs/nixos/modules/services/security/vault.nix
+++ b/nixpkgs/nixos/modules/services/security/vault.nix
@@ -43,7 +43,7 @@ in
 {
   options = {
     services.vault = {
-      enable = mkEnableOption "Vault daemon";
+      enable = mkEnableOption (lib.mdDoc "Vault daemon");
 
       package = mkOption {
         type = types.package;
@@ -141,17 +141,17 @@ in
       extraSettingsPaths = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Configuration files to load besides the immutable one defined by the NixOS module.
           This can be used to avoid putting credentials in the Nix store, which can be read by any user.
 
           Each path can point to a JSON- or HCL-formatted file, or a directory
-          to be scanned for files with <literal>.hcl</literal> or
-          <literal>.json</literal> extensions.
+          to be scanned for files with `.hcl` or
+          `.json` extensions.
 
           To upload the confidential file with NixOps, use for example:
 
-          <programlisting><![CDATA[
+          ```
           # https://releases.nixos.org/nixops/latest/manual/manual.html#opt-deployment.keys
           deployment.keys."vault.hcl" = let db = import ./db-credentials.nix; in {
             text = ${"''"}
@@ -164,7 +164,7 @@ in
           services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
           services.vault.storageBackend = "postgresql";
           users.users.vault.extraGroups = ["keys"];
-          ]]></programlisting>
+          ```
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/security/vaultwarden/default.nix b/nixpkgs/nixos/modules/services/security/vaultwarden/default.nix
index 3aa38ed819f6..aaa3f5507f77 100644
--- a/nixpkgs/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixpkgs/nixos/modules/services/security/vaultwarden/default.nix
@@ -22,9 +22,9 @@ let
   # we can only check for values consistently after converting them to their corresponding environment variable name.
   configEnv =
     let
-      configEnv = listToAttrs (concatLists (mapAttrsToList (name: value:
-        if value != null then [ (nameValuePair (nameToEnvVar name) (if isBool value then boolToString value else toString value)) ] else []
-      ) cfg.config));
+      configEnv = concatMapAttrs (name: value: optionalAttrs (value != null) {
+        ${nameToEnvVar name} = if isBool value then boolToString value else toString value;
+      }) cfg.config;
     in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
       WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault";
     } // configEnv;
@@ -39,7 +39,7 @@ in {
   ];
 
   options.services.vaultwarden = with types; {
-    enable = mkEnableOption "vaultwarden";
+    enable = mkEnableOption (lib.mdDoc "vaultwarden");
 
     dbBackend = mkOption {
       type = enum [ "sqlite" "mysql" "postgresql" ];
@@ -97,26 +97,26 @@ in {
           SMTP_FROM_NAME = "example.com Bitwarden server";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         The configuration of vaultwarden is done through environment variables,
-        therefore it is recommended to use upper snake case (e.g. <envar>DISABLE_2FA_REMEMBER</envar>).
+        therefore it is recommended to use upper snake case (e.g. {env}`DISABLE_2FA_REMEMBER`).
 
-        However, camel case (e.g. <literal>disable2FARemember</literal>) is also supported:
+        However, camel case (e.g. `disable2FARemember`) is also supported:
         The NixOS module will convert it automatically to
-        upper case snake case (e.g. <envar>DISABLE_2FA_REMEMBER</envar>).
+        upper case snake case (e.g. {env}`DISABLE_2FA_REMEMBER`).
         In this conversion digits (0-9) are handled just like upper case characters,
-        so <literal>foo2</literal> would be converted to <envar>FOO_2</envar>.
-        Names already in this format remain unchanged, so <literal>FOO2</literal> remains <literal>FOO2</literal> if passed as such,
-        even though <literal>foo2</literal> would have been converted to <envar>FOO_2</envar>.
+        so `foo2` would be converted to {env}`FOO_2`.
+        Names already in this format remain unchanged, so `FOO2` remains `FOO2` if passed as such,
+        even though `foo2` would have been converted to {env}`FOO_2`.
         This allows working around any potential future conflicting naming conventions.
 
         Based on the attributes passed to this config option an environment file will be generated
         that is passed to vaultwarden's systemd service.
 
         The available configuration options can be found in
-        <link xlink:href="https://github.com/dani-garcia/vaultwarden/blob/${vaultwarden.version}/.env.template">the environment template file</link>.
+        [the environment template file](https://github.com/dani-garcia/vaultwarden/blob/${vaultwarden.version}/.env.template).
 
-        See <xref linkend="opt-services.vaultwarden.environmentFile"/> for how
+        See ()[#opt-services.vaultwarden.environmentFile) for how
         to set up access to the Admin UI to invite initial users.
       '';
     };
@@ -125,31 +125,31 @@ in {
       type = with types; nullOr path;
       default = null;
       example = "/var/lib/vaultwarden.env";
-      description = ''
-        Additional environment file as defined in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+      description = lib.mdDoc ''
+        Additional environment file as defined in {manpage}`systemd.exec(5)`.
 
-        Secrets like <envar>ADMIN_TOKEN</envar> and <envar>SMTP_PASSWORD</envar>
+        Secrets like {env}`ADMIN_TOKEN` and {env}`SMTP_PASSWORD`
         may be passed to the service without adding them to the world-readable Nix store.
 
         Note that this file needs to be available on the host on which
-        <literal>vaultwarden</literal> is running.
+        `vaultwarden` is running.
 
         As a concrete example, to make the Admin UI available
         (from which new users can be invited initially),
-        the secret <envar>ADMIN_TOKEN</envar> needs to be defined as described
-        <link xlink:href="https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page">here</link>.
-        Setting <literal>environmentFile</literal> to <literal>/var/lib/vaultwarden.env</literal>
+        the secret {env}`ADMIN_TOKEN` needs to be defined as described
+        [here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page).
+        Setting `environmentFile` to `/var/lib/vaultwarden.env`
         and ensuring permissions with e.g.
-        <literal>chown vaultwarden:vaultwarden /var/lib/vaultwarden.env</literal>
-        (the <literal>vaultwarden</literal> user will only exist after activating with
-        <literal>enable = true;</literal> before this), we can set the contents of the file to have
+        `chown vaultwarden:vaultwarden /var/lib/vaultwarden.env`
+        (the `vaultwarden` user will only exist after activating with
+        `enable = true;` before this), we can set the contents of the file to have
         contents such as:
 
-<programlisting>
-# Admin secret token, see
-# https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
-ADMIN_TOKEN=...copy-paste a unique generated secret token here...
-</programlisting>
+        ```
+        # Admin secret token, see
+        # https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
+        ADMIN_TOKEN=...copy-paste a unique generated secret token here...
+        ```
       '';
     };
 
@@ -162,8 +162,8 @@ ADMIN_TOKEN=...copy-paste a unique generated secret token here...
 
     webVaultPackage = mkOption {
       type = package;
-      default = pkgs.vaultwarden-vault;
-      defaultText = literalExpression "pkgs.vaultwarden-vault";
+      default = pkgs.vaultwarden.webvault;
+      defaultText = literalExpression "pkgs.vaultwarden.webvault";
       description = lib.mdDoc "Web vault package to use.";
     };
   };
@@ -196,6 +196,8 @@ ADMIN_TOKEN=...copy-paste a unique generated secret token here...
         ProtectSystem = "strict";
         AmbientCapabilities = "CAP_NET_BIND_SERVICE";
         StateDirectory = "bitwarden_rs";
+        StateDirectoryMode = "0700";
+        Restart = "always";
       };
       wantedBy = [ "multi-user.target" ];
     };
@@ -208,6 +210,8 @@ ADMIN_TOKEN=...copy-paste a unique generated secret token here...
         BACKUP_FOLDER = cfg.backupDir;
       };
       path = with pkgs; [ sqlite ];
+      # if both services are started at the same time, vaultwarden fails with "database is locked"
+      before = [ "vaultwarden.service" ];
       serviceConfig = {
         SyslogIdentifier = "backup-vaultwarden";
         Type = "oneshot";
@@ -219,7 +223,7 @@ ADMIN_TOKEN=...copy-paste a unique generated secret token here...
     };
 
     systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) {
-      aliases = [ "backup-bitwarden_rs.service" ];
+      aliases = [ "backup-bitwarden_rs.timer" ];
       description = "Backup vaultwarden on time";
       timerConfig = {
         OnCalendar = mkDefault "23:00";
diff --git a/nixpkgs/nixos/modules/services/security/yubikey-agent.nix b/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
index c91ff3e69a0a..ee57ec8bf812 100644
--- a/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
@@ -57,6 +57,9 @@ in
       ];
     };
 
+    # Yubikey-agent expects pcsd to be running in order to function.
+    services.pcscd.enable = true;
+
     environment.extraInit = ''
       if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
         export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
diff --git a/nixpkgs/nixos/modules/services/system/automatic-timezoned.nix b/nixpkgs/nixos/modules/services/system/automatic-timezoned.nix
new file mode 100644
index 000000000000..9bdd64dd33a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/automatic-timezoned.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.automatic-timezoned;
+in
+{
+  options = {
+    services.automatic-timezoned = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc ''
+          Enable `automatic-timezoned`, simple daemon for keeping the system
+          timezone up-to-date based on the current location. It uses geoclue2 to
+          determine the current location and systemd-timedated to actually set
+          the timezone.
+        '';
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.automatic-timezoned;
+        defaultText = literalExpression "pkgs.automatic-timezoned";
+        description = mdDoc ''
+          Which `automatic-timezoned` package to use.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.polkit.extraConfig = ''
+      polkit.addRule(function(action, subject) {
+        if (action.id == "org.freedesktop.timedate1.set-timezone"
+            && subject.user == "automatic-timezoned") {
+          return polkit.Result.YES;
+        }
+      });
+    '';
+
+    services.geoclue2 = {
+      enable = true;
+      appConfig.automatic-timezoned = {
+        isAllowed = true;
+        isSystem = true;
+        users = [ (toString config.ids.uids.automatic-timezoned) ];
+      };
+    };
+
+    systemd.services = {
+
+      automatic-timezoned = {
+        description = "Automatically update system timezone based on location";
+        requires = [ "automatic-timezoned-geoclue-agent.service" ];
+        after = [ "automatic-timezoned-geoclue-agent.service" ];
+        serviceConfig = {
+          Type = "exec";
+          User = "automatic-timezoned";
+          ExecStart = "${cfg.package}/bin/automatic-timezoned --zoneinfo-path=${pkgs.tzdata}/share/zoneinfo/zone1970.tab";
+        };
+        wantedBy = [ "default.target" ];
+      };
+
+      automatic-timezoned-geoclue-agent = {
+        description = "Geoclue agent for automatic-timezoned";
+        requires = [ "geoclue.service" ];
+        after = [ "geoclue.service" ];
+        serviceConfig = {
+          Type = "exec";
+          User = "automatic-timezoned";
+          ExecStart = "${pkgs.geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/agent";
+          Restart = "on-failure";
+          PrivateTmp = true;
+        };
+        wantedBy = [ "default.target" ];
+      };
+
+    };
+
+    users = {
+      users.automatic-timezoned = {
+        description = "automatic-timezoned";
+        uid = config.ids.uids.automatic-timezoned;
+        group = "automatic-timezoned";
+      };
+      groups.automatic-timezoned = {
+        gid = config.ids.gids.automatic-timezoned;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/cachix-agent/default.nix b/nixpkgs/nixos/modules/services/system/cachix-agent/default.nix
index b730118d46d8..06494ddb631a 100644
--- a/nixpkgs/nixos/modules/services/system/cachix-agent/default.nix
+++ b/nixpkgs/nixos/modules/services/system/cachix-agent/default.nix
@@ -8,7 +8,7 @@ in {
   meta.maintainers = [ lib.maintainers.domenkozar ];
 
   options.services.cachix-agent = {
-    enable = mkEnableOption "Cachix Deploy Agent: https://docs.cachix.org/deploy/";
+    enable = mkEnableOption (lib.mdDoc "Cachix Deploy Agent: https://docs.cachix.org/deploy/");
 
     name = mkOption {
       type = types.str;
@@ -29,6 +29,12 @@ in {
       description = lib.mdDoc "Profile name, defaults to 'system' (NixOS).";
     };
 
+    host = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc "Cachix uri to use.";
+    };
+
     package = mkOption {
       type = types.package;
       default = pkgs.cachix;
@@ -61,9 +67,13 @@ in {
       serviceConfig = {
         # we don't want to kill children processes as those are deployments
         KillMode = "process";
-        Restart = "on-failure";
+        Restart = "always";
+        RestartSec = 5;
         EnvironmentFile = cfg.credentialsFile;
-        ExecStart = "${cfg.package}/bin/cachix ${lib.optionalString cfg.verbose "--verbose"} deploy agent ${cfg.name} ${if cfg.profile != null then profile else ""}";
+        ExecStart = ''
+          ${cfg.package}/bin/cachix ${lib.optionalString cfg.verbose "--verbose"} ${lib.optionalString (cfg.host != null) "--host ${cfg.host}"} \
+            deploy agent ${cfg.name} ${optionalString (cfg.profile != null) cfg.profile}
+        '';
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/system/cachix-watch-store.nix b/nixpkgs/nixos/modules/services/system/cachix-watch-store.nix
new file mode 100644
index 000000000000..89157b460b9a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/cachix-watch-store.nix
@@ -0,0 +1,93 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cachix-watch-store;
+in
+{
+  meta.maintainers = [ lib.maintainers.jfroche lib.maintainers.domenkozar ];
+
+  options.services.cachix-watch-store = {
+    enable = mkEnableOption (lib.mdDoc "Cachix Watch Store: https://docs.cachix.org");
+
+    cacheName = mkOption {
+      type = types.str;
+      description = lib.mdDoc "Cachix binary cache name";
+    };
+
+    cachixTokenFile = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Required file that needs to contain the cachix auth token.
+      '';
+    };
+
+    compressionLevel = mkOption {
+      type = types.nullOr types.int;
+      description = lib.mdDoc "The compression level for ZSTD compression (between 0 and 16)";
+      default = null;
+    };
+
+    jobs = mkOption {
+      type = types.nullOr types.int;
+      description = lib.mdDoc "Number of threads used for pushing store paths";
+      default = null;
+    };
+
+    host = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc "Cachix host to connect to";
+    };
+
+    verbose = mkOption {
+      type = types.bool;
+      description = lib.mdDoc "Enable verbose output";
+      default = false;
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.cachix;
+      defaultText = literalExpression "pkgs.cachix";
+      description = lib.mdDoc "Cachix Client package to use.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.cachix-watch-store-agent = {
+      description = "Cachix watch store Agent";
+      after = [ "network-online.target" ];
+      path = [ config.nix.package ];
+      wantedBy = [ "multi-user.target" ];
+      unitConfig = {
+        # allow to restart indefinitely
+        StartLimitIntervalSec = 0;
+      };
+      serviceConfig = {
+        # don't put too much stress on the machine when restarting
+        RestartSec = 1;
+        # we don't want to kill children processes as those are deployments
+        KillMode = "process";
+        Restart = "on-failure";
+        DynamicUser = true;
+        LoadCredential = [
+          "cachix-token:${toString cfg.cachixTokenFile}"
+        ];
+      };
+      script =
+        let
+          command = [ "${cfg.package}/bin/cachix" ]
+            ++ (lib.optional cfg.verbose "--verbose") ++ (lib.optionals (cfg.host != null) [ "--host" cfg.host ])
+            ++ [ "watch-store" ] ++ (lib.optionals (cfg.compressionLevel != null) [ "--compression-level" (toString cfg.compressionLevel) ])
+            ++ (lib.optionals (cfg.jobs != null) [ "--jobs" (toString cfg.jobs) ]) ++ [ cfg.cacheName ];
+        in
+        ''
+          export CACHIX_AUTH_TOKEN="$(<"$CREDENTIALS_DIRECTORY/cachix-token")"
+          ${lib.escapeShellArgs command}
+        '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/cloud-init.nix b/nixpkgs/nixos/modules/services/system/cloud-init.nix
index 111cfa83c222..4cda06ed4248 100644
--- a/nixpkgs/nixos/modules/services/system/cloud-init.nix
+++ b/nixpkgs/nixos/modules/services/system/cloud-init.nix
@@ -2,17 +2,22 @@
 
 with lib;
 
-let cfg = config.services.cloud-init;
-    path = with pkgs; [
-      cloud-init
-      iproute2
-      nettools
-      openssh
-      shadow
-      util-linux
-    ] ++ optional cfg.btrfs.enable btrfs-progs
-      ++ optional cfg.ext4.enable e2fsprogs
-    ;
+let
+  cfg = config.services.cloud-init;
+  path = with pkgs; [
+    cloud-init
+    iproute2
+    nettools
+    openssh
+    shadow
+    util-linux
+    busybox
+  ]
+  ++ optional cfg.btrfs.enable btrfs-progs
+  ++ optional cfg.ext4.enable e2fsprogs
+  ;
+  settingsFormat = pkgs.formats.yaml { };
+  cfgfile = settingsFormat.generate "cloud.cfg" cfg.settings;
 in
 {
   options = {
@@ -20,14 +25,14 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Enable the cloud-init service. This services reads
           configuration metadata in a cloud environment and configures
           the machine according to this metadata.
 
           This configuration is not completely compatible with the
           NixOS way of doing configuration, as configuration done by
-          cloud-init might be overriden by a subsequent nixos-rebuild
+          cloud-init might be overridden by a subsequent nixos-rebuild
           call. However, some parts of cloud-init fall outside of
           NixOS's responsibility, like filesystem resizing and ssh
           public key provisioning, and cloud-init is useful for that
@@ -39,7 +44,7 @@ in
       btrfs.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = mdDoc ''
           Allow the cloud-init service to operate `btrfs` filesystem.
         '';
       };
@@ -47,7 +52,7 @@ in
       ext4.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = mdDoc ''
           Allow the cloud-init service to operate `ext4` filesystem.
         '';
       };
@@ -55,61 +60,30 @@ in
       network.enable = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Allow the cloud-init service to configure network interfaces
           through systemd-networkd.
         '';
       };
 
+      settings = mkOption {
+        description = mdDoc ''
+          Structured cloud-init configuration.
+        '';
+        type = types.submodule {
+          freeformType = settingsFormat.type;
+        };
+        default = { };
+      };
+
       config = mkOption {
         type = types.str;
-        default = ''
-          system_info:
-            distro: nixos
-            network:
-              renderers: [ 'networkd' ]
-          users:
-             - root
-
-          disable_root: false
-          preserve_hostname: false
-
-          cloud_init_modules:
-           - migrator
-           - seed_random
-           - bootcmd
-           - write-files
-           - growpart
-           - resizefs
-           - update_etc_hosts
-           - ca-certs
-           - rsyslog
-           - users-groups
-
-          cloud_config_modules:
-           - disk_setup
-           - mounts
-           - ssh-import-id
-           - set-passwords
-           - timezone
-           - disable-ec2-metadata
-           - runcmd
-           - ssh
-
-          cloud_final_modules:
-           - rightscale_userdata
-           - scripts-vendor
-           - scripts-per-once
-           - scripts-per-boot
-           - scripts-per-instance
-           - scripts-user
-           - ssh-authkey-fingerprints
-           - keys-to-console
-           - phone-home
-           - final-message
-           - power-state-change
-          '';
-        description = lib.mdDoc "cloud-init configuration.";
+        default = "";
+        description = mdDoc ''
+          raw cloud-init configuration.
+
+          Takes precedence over the `settings` option if set.
+        '';
       };
 
     };
@@ -117,78 +91,140 @@ in
   };
 
   config = mkIf cfg.enable {
+    services.cloud-init.settings = {
+      system_info = mkDefault {
+        distro = "nixos";
+        network = {
+          renderers = [ "networkd" ];
+        };
+      };
 
-    environment.etc."cloud/cloud.cfg".text = cfg.config;
+      users = mkDefault [ "root" ];
+      disable_root = mkDefault false;
+      preserve_hostname = mkDefault false;
+
+      cloud_init_modules = mkDefault [
+        "migrator"
+        "seed_random"
+        "bootcmd"
+        "write-files"
+        "growpart"
+        "resizefs"
+        "update_hostname"
+        "resolv_conf"
+        "ca-certs"
+        "rsyslog"
+        "users-groups"
+      ];
+
+      cloud_config_modules = mkDefault [
+        "disk_setup"
+        "mounts"
+        "ssh-import-id"
+        "set-passwords"
+        "timezone"
+        "disable-ec2-metadata"
+        "runcmd"
+        "ssh"
+      ];
+
+      cloud_final_modules = mkDefault [
+        "rightscale_userdata"
+        "scripts-vendor"
+        "scripts-per-once"
+        "scripts-per-boot"
+        "scripts-per-instance"
+        "scripts-user"
+        "ssh-authkey-fingerprints"
+        "keys-to-console"
+        "phone-home"
+        "final-message"
+        "power-state-change"
+      ];
+    };
+
+    environment.etc."cloud/cloud.cfg" =
+      if cfg.config == "" then
+        { source = cfgfile; }
+      else
+        { text = cfg.config; }
+    ;
 
     systemd.network.enable = cfg.network.enable;
 
-    systemd.services.cloud-init-local =
-      { description = "Initial cloud-init job (pre-networking)";
-        wantedBy = [ "multi-user.target" ];
-        before = ["systemd-networkd.service"];
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-init-local = {
+      description = "Initial cloud-init job (pre-networking)";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "systemd-networkd.service" ];
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.services.cloud-init =
-      { description = "Initial cloud-init job (metadata service crawler)";
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" "cloud-init-local.service"
-                  "sshd.service" "sshd-keygen.service" ];
-        after = [ "network-online.target" "cloud-init-local.service" ];
-        before = [ "sshd.service" "sshd-keygen.service" ];
-        requires = [ "network.target"];
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-init = {
+      description = "Initial cloud-init job (metadata service crawler)";
+      wantedBy = [ "multi-user.target" ];
+      wants = [
+        "network-online.target"
+        "cloud-init-local.service"
+        "sshd.service"
+        "sshd-keygen.service"
+      ];
+      after = [ "network-online.target" "cloud-init-local.service" ];
+      before = [ "sshd.service" "sshd-keygen.service" ];
+      requires = [ "network.target" ];
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.services.cloud-config =
-      { description = "Apply the settings specified in cloud-config";
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" ];
-        after = [ "network-online.target" "syslog.target" "cloud-config.target" ];
-
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-config = {
+      description = "Apply the settings specified in cloud-config";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" "syslog.target" "cloud-config.target" ];
+
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.services.cloud-final =
-      { description = "Execute cloud user/final scripts";
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" ];
-        after = [ "network-online.target" "syslog.target" "cloud-config.service" "rc-local.service" ];
-        requires = [ "cloud-config.target" ];
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-final = {
+      description = "Execute cloud user/final scripts";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" "syslog.target" "cloud-config.service" "rc-local.service" ];
+      requires = [ "cloud-config.target" ];
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.targets.cloud-config =
-      { description = "Cloud-config availability";
-        requires = [ "cloud-init-local.service" "cloud-init.service" ];
-      };
+    systemd.targets.cloud-config = {
+      description = "Cloud-config availability";
+      requires = [ "cloud-init-local.service" "cloud-init.service" ];
+    };
   };
+
+  meta.maintainers = [ maintainers.zimbatm ];
 }
diff --git a/nixpkgs/nixos/modules/services/system/dbus.nix b/nixpkgs/nixos/modules/services/system/dbus.nix
index def04d944f05..9d8a62ec78c5 100644
--- a/nixpkgs/nixos/modules/services/system/dbus.nix
+++ b/nixpkgs/nixos/modules/services/system/dbus.nix
@@ -1,8 +1,6 @@
 # D-Bus configuration and system bus daemon.
 
-{ config, lib, options, pkgs, ... }:
-
-with lib;
+{ config, lib, pkgs, ... }:
 
 let
 
@@ -16,25 +14,41 @@ let
     serviceDirectories = cfg.packages;
   };
 
+  inherit (lib) mkOption mkEnableOption mkIf mkMerge types;
+
 in
 
 {
-  ###### interface
-
   options = {
 
+    boot.initrd.systemd.dbus = {
+      enable = mkEnableOption (lib.mdDoc "dbus in stage 1") // { visible = false; };
+    };
+
     services.dbus = {
 
       enable = mkOption {
         type = types.bool;
         default = false;
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to start the D-Bus message bus daemon, which is
           required by many other system services and applications.
         '';
       };
 
+      implementation = mkOption {
+        type = types.enum [ "dbus" "broker" ];
+        default = "dbus";
+        description = lib.mdDoc ''
+          The implementation to use for the message bus defined by the D-Bus specification.
+          Can be either the classic dbus daemon or dbus-broker, which aims to provide high
+          performance and reliability, while keeping compatibility to the D-Bus
+          reference implementation.
+        '';
+
+      };
+
       packages = mkOption {
         type = types.listOf types.path;
         default = [ ];
@@ -65,75 +79,132 @@ in
         '';
         default = "disabled";
       };
-
-      socketActivated = mkOption {
-        type = types.nullOr types.bool;
-        default = null;
-        visible = false;
-        description = ''
-          Removed option, do not use.
-        '';
-      };
     };
   };
 
-  ###### implementation
+  config = mkIf cfg.enable (mkMerge [
+    {
+      environment.etc."dbus-1".source = configDir;
 
-  config = mkIf cfg.enable {
-    warnings = optional (cfg.socketActivated != null) (
-      let
-        files = showFiles options.services.dbus.socketActivated.files;
-      in
-        "The option 'services.dbus.socketActivated' in ${files} no longer has"
-        + " any effect and can be safely removed: the user D-Bus session is"
-        + " now always socket activated."
-    );
+      environment.pathsToLink = [
+        "/etc/dbus-1"
+        "/share/dbus-1"
+      ];
+
+      users.users.messagebus = {
+        uid = config.ids.uids.messagebus;
+        description = "D-Bus system message bus daemon user";
+        home = homeDir;
+        group = "messagebus";
+      };
 
-    environment.systemPackages = [ pkgs.dbus.daemon pkgs.dbus ];
+      users.groups.messagebus.gid = config.ids.gids.messagebus;
+
+      # You still need the dbus reference implementation installed to use dbus-broker
+      systemd.packages = [
+        pkgs.dbus
+      ];
+
+      services.dbus.packages = [
+        pkgs.dbus
+        config.system.path
+      ];
+
+      systemd.user.sockets.dbus.wantedBy = [
+        "sockets.target"
+      ];
+    }
+
+    (mkIf config.boot.initrd.systemd.dbus.enable {
+      boot.initrd.systemd = {
+        users.messagebus = { };
+        groups.messagebus = { };
+        contents."/etc/dbus-1".source = pkgs.makeDBusConf {
+          inherit (cfg) apparmor;
+          suidHelper = "/bin/false";
+          serviceDirectories = [ pkgs.dbus ];
+        };
+        packages = [ pkgs.dbus ];
+        storePaths = [ "${pkgs.dbus}/bin/dbus-daemon" ];
+        targets.sockets.wants = [ "dbus.socket" ];
+      };
+    })
+
+    (mkIf (cfg.implementation == "dbus") {
+      environment.systemPackages = [
+        pkgs.dbus
+      ];
+
+      security.wrappers.dbus-daemon-launch-helper = {
+        source = "${pkgs.dbus}/libexec/dbus-daemon-launch-helper";
+        owner = "root";
+        group = "messagebus";
+        setuid = true;
+        setgid = false;
+        permissions = "u+rx,g+rx,o-rx";
+      };
 
-    environment.etc."dbus-1".source = configDir;
+      systemd.services.dbus = {
+        # Don't restart dbus-daemon. Bad things tend to happen if we do.
+        reloadIfChanged = true;
+        restartTriggers = [
+          configDir
+        ];
+        environment = {
+          LD_LIBRARY_PATH = config.system.nssModules.path;
+        };
+      };
 
-    users.users.messagebus = {
-      uid = config.ids.uids.messagebus;
-      description = "D-Bus system message bus daemon user";
-      home = homeDir;
-      group = "messagebus";
-    };
+      systemd.user.services.dbus = {
+        # Don't restart dbus-daemon. Bad things tend to happen if we do.
+        reloadIfChanged = true;
+        restartTriggers = [
+          configDir
+        ];
+      };
 
-    users.groups.messagebus.gid = config.ids.gids.messagebus;
+    })
 
-    systemd.packages = [ pkgs.dbus.daemon ];
+    (mkIf (cfg.implementation == "broker") {
+      environment.systemPackages = [
+        pkgs.dbus-broker
+      ];
 
-    security.wrappers.dbus-daemon-launch-helper = {
-      source = "${pkgs.dbus.daemon}/libexec/dbus-daemon-launch-helper";
-      owner = "root";
-      group = "messagebus";
-      setuid = true;
-      setgid = false;
-      permissions = "u+rx,g+rx,o-rx";
-    };
+      systemd.packages = [
+        pkgs.dbus-broker
+      ];
 
-    services.dbus.packages = [
-      pkgs.dbus.out
-      config.system.path
-    ];
+      # Just to be sure we don't restart through the unit alias
+      systemd.services.dbus.reloadIfChanged = true;
+      systemd.user.services.dbus.reloadIfChanged = true;
 
-    systemd.services.dbus = {
-      # Don't restart dbus-daemon. Bad things tend to happen if we do.
-      reloadIfChanged = true;
-      restartTriggers = [ configDir ];
-      environment = { LD_LIBRARY_PATH = config.system.nssModules.path; };
-    };
+      # NixOS Systemd Module doesn't respect 'Install'
+      # https://github.com/NixOS/nixpkgs/issues/108643
+      systemd.services.dbus-broker = {
+        aliases = [
+          "dbus.service"
+        ];
+        # Don't restart dbus. Bad things tend to happen if we do.
+        reloadIfChanged = true;
+        restartTriggers = [
+          configDir
+        ];
+        environment = {
+          LD_LIBRARY_PATH = config.system.nssModules.path;
+        };
+      };
 
-    systemd.user = {
-      services.dbus = {
-        # Don't restart dbus-daemon. Bad things tend to happen if we do.
+      systemd.user.services.dbus-broker = {
+        aliases = [
+          "dbus.service"
+        ];
+        # Don't restart dbus. Bad things tend to happen if we do.
         reloadIfChanged = true;
-        restartTriggers = [ configDir ];
+        restartTriggers = [
+          configDir
+        ];
       };
-      sockets.dbus.wantedBy = [ "sockets.target" ];
-    };
+    })
 
-    environment.pathsToLink = [ "/etc/dbus-1" "/share/dbus-1" ];
-  };
+  ]);
 }
diff --git a/nixpkgs/nixos/modules/services/system/earlyoom.nix b/nixpkgs/nixos/modules/services/system/earlyoom.nix
index b2e2d21002ce..3f501d453460 100644
--- a/nixpkgs/nixos/modules/services/system/earlyoom.nix
+++ b/nixpkgs/nixos/modules/services/system/earlyoom.nix
@@ -11,7 +11,7 @@ let
 in
 {
   options.services.earlyoom = {
-    enable = mkEnableOption "Early out of memory killing";
+    enable = mkEnableOption (lib.mdDoc "Early out of memory killing");
 
     freeMemThreshold = mkOption {
       type = types.ints.between 1 100;
@@ -72,7 +72,7 @@ in
     enableNotifications = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Send notifications about killed processes via the system d-bus.
 
         WARNING: enabling this option (while convenient) should *not* be done on a
@@ -80,10 +80,10 @@ in
         local user to DoS your session by spamming notifications.
 
         To actually see the notifications in your GUI session, you need to have
-        <literal>systembus-notify</literal> running as your user, which this
-        option handles by enabling <option>services.systembus-notify</option>.
+        `systembus-notify` running as your user, which this
+        option handles by enabling {option}`services.systembus-notify`.
 
-        See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
+        See [README](https://github.com/rfjakob/earlyoom#notifications) for details.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/default.nix b/nixpkgs/nixos/modules/services/system/kerberos/default.nix
index 3ace9de5ea7d..4ed48e463741 100644
--- a/nixpkgs/nixos/modules/services/system/kerberos/default.nix
+++ b/nixpkgs/nixos/modules/services/system/kerberos/default.nix
@@ -51,7 +51,7 @@ in
   ###### interface
   options = {
     services.kerberos_server = {
-      enable = lib.mkEnableOption "the kerberos authentification server";
+      enable = lib.mkEnableOption (lib.mdDoc "the kerberos authentication server");
 
       realms = mkOption {
         type = types.attrsOf (types.submodule realm);
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/mit.nix b/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
index 25d7d51e808a..112000140453 100644
--- a/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
+++ b/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
@@ -33,7 +33,7 @@ let
 in
 
 {
-  config = mkIf (cfg.enable && kerberos == pkgs.krb5Full) {
+  config = mkIf (cfg.enable && kerberos == pkgs.krb5) {
     systemd.services.kadmind = {
       description = "Kerberos Administration Daemon";
       wantedBy = [ "multi-user.target" ];
diff --git a/nixpkgs/nixos/modules/services/system/nscd.nix b/nixpkgs/nixos/modules/services/system/nscd.nix
index f3dfd2af3d2f..971dffbadc13 100644
--- a/nixpkgs/nixos/modules/services/system/nscd.nix
+++ b/nixpkgs/nixos/modules/services/system/nscd.nix
@@ -27,10 +27,20 @@ in
         '';
       };
 
+      enableNsncd = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Whether to use nsncd instead of nscd from glibc.
+          This is a nscd-compatible daemon, that proxies lookups, without any caching.
+          Using nscd from glibc is discouraged.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nscd";
-        description = ''
+        description = lib.mdDoc ''
           User account under which nscd runs.
         '';
       };
@@ -38,7 +48,7 @@ in
       group = mkOption {
         type = types.str;
         default = "nscd";
-        description = ''
+        description = lib.mdDoc ''
           User group under which nscd runs.
         '';
       };
@@ -46,12 +56,16 @@ in
       config = mkOption {
         type = types.lines;
         default = builtins.readFile ./nscd.conf;
-        description = lib.mdDoc "Configuration to use for Name Service Cache Daemon.";
+        description = lib.mdDoc ''
+          Configuration to use for Name Service Cache Daemon.
+          Only used in case glibc-nscd is used.
+        '';
       };
 
       package = mkOption {
         type = types.package;
-        default = if pkgs.stdenv.hostPlatform.libc == "glibc"
+        default =
+          if pkgs.stdenv.hostPlatform.libc == "glibc"
           then pkgs.stdenv.cc.libc.bin
           else pkgs.glibc.bin;
         defaultText = lib.literalExpression ''
@@ -59,7 +73,10 @@ in
             then pkgs.stdenv.cc.libc.bin
             else pkgs.glibc.bin;
         '';
-        description = lib.mdDoc "package containing the nscd binary to be used by the service";
+        description = lib.mdDoc ''
+          package containing the nscd binary to be used by the service.
+          Ignored when enableNsncd is set to true.
+        '';
       };
 
     };
@@ -77,10 +94,12 @@ in
       group = cfg.group;
     };
 
-    users.groups.${cfg.group} = {};
+    users.groups.${cfg.group} = { };
 
     systemd.services.nscd =
-      { description = "Name Service Cache Daemon";
+      {
+        description = "Name Service Cache Daemon"
+          + lib.optionalString cfg.enableNsncd " (nsncd)";
 
         before = [ "nss-lookup.target" "nss-user-lookup.target" ];
         wants = [ "nss-lookup.target" "nss-user-lookup.target" ];
@@ -89,14 +108,14 @@ in
 
         environment = { LD_LIBRARY_PATH = nssModulesPath; };
 
-        restartTriggers = [
+        restartTriggers = lib.optionals (!cfg.enableNsncd) ([
           config.environment.etc.hosts.source
           config.environment.etc."nsswitch.conf".source
           config.environment.etc."nscd.conf".source
         ] ++ optionals config.users.mysql.enable [
           config.environment.etc."libnss-mysql.cfg".source
           config.environment.etc."libnss-mysql-root.cfg".source
-        ];
+        ]);
 
         # In some configurations, nscd needs to be started as root; it will
         # drop privileges after all the NSS modules have read their
@@ -106,8 +125,11 @@ in
         # sill want to read their configuration files after the privilege drop
         # and so users can set the owner of those files to the nscd user.
         serviceConfig =
-          { ExecStart = "!@${cfg.package}/bin/nscd nscd";
-            Type = "forking";
+          {
+            ExecStart =
+              if cfg.enableNsncd then "${pkgs.nsncd}/bin/nsncd"
+              else "!@${cfg.package}/bin/nscd nscd";
+            Type = if cfg.enableNsncd then "notify" else "forking";
             User = cfg.user;
             Group = cfg.group;
             RemoveIPC = true;
@@ -120,12 +142,12 @@ in
             PIDFile = "/run/nscd/nscd.pid";
             Restart = "always";
             ExecReload =
-              [ "${cfg.package}/bin/nscd --invalidate passwd"
+              lib.optionals (!cfg.enableNsncd) [
+                "${cfg.package}/bin/nscd --invalidate passwd"
                 "${cfg.package}/bin/nscd --invalidate group"
                 "${cfg.package}/bin/nscd --invalidate hosts"
               ];
           };
       };
-
   };
 }
diff --git a/nixpkgs/nixos/modules/services/system/saslauthd.nix b/nixpkgs/nixos/modules/services/system/saslauthd.nix
index c3fa7f7aefca..09720146aaa9 100644
--- a/nixpkgs/nixos/modules/services/system/saslauthd.nix
+++ b/nixpkgs/nixos/modules/services/system/saslauthd.nix
@@ -16,7 +16,7 @@ in
 
     services.saslauthd = {
 
-      enable = mkEnableOption "saslauthd, the Cyrus SASL authentication daemon";
+      enable = mkEnableOption (lib.mdDoc "saslauthd, the Cyrus SASL authentication daemon");
 
       package = mkOption {
         default = pkgs.cyrus_sasl.bin;
diff --git a/nixpkgs/nixos/modules/services/system/self-deploy.nix b/nixpkgs/nixos/modules/services/system/self-deploy.nix
index ff56206573c1..b5d8ea3f56e7 100644
--- a/nixpkgs/nixos/modules/services/system/self-deploy.nix
+++ b/nixpkgs/nixos/modules/services/system/self-deploy.nix
@@ -18,12 +18,12 @@ let
     in
     lib.concatStrings (lib.mapAttrsToList toArg args);
 
-  isPathType = x: lib.strings.isCoercibleToString x && builtins.substring 0 1 (toString x) == "/";
+  isPathType = x: lib.types.path.check x;
 
 in
 {
   options.services.self-deploy = {
-    enable = lib.mkEnableOption "self-deploy";
+    enable = lib.mkEnableOption (lib.mdDoc "self-deploy");
 
     nixFile = lib.mkOption {
       type = lib.types.path;
@@ -41,7 +41,7 @@ in
 
       default = null;
 
-      description = ''
+      description = lib.mdDoc ''
         Attribute of `nixFile` that builds the current system.
       '';
     };
@@ -51,7 +51,7 @@ in
 
       default = { };
 
-      description = ''
+      description = lib.mdDoc ''
         Arguments to `nix-build` passed as `--argstr` or `--arg` depending on
         the type.
       '';
@@ -62,7 +62,7 @@ in
 
       default = "switch";
 
-      description = ''
+      description = lib.mdDoc ''
         The `switch-to-configuration` subcommand used.
       '';
     };
@@ -70,7 +70,7 @@ in
     repository = lib.mkOption {
       type = with lib.types; oneOf [ path str ];
 
-      description = ''
+      description = lib.mdDoc ''
         The repository to fetch from. Must be properly formatted for git.
 
         If this value is set to a path (must begin with `/`) then it's
@@ -99,7 +99,7 @@ in
 
       default = "master";
 
-      description = ''
+      description = lib.mdDoc ''
         Branch to track
 
         Technically speaking any ref can be specified here, as this is
@@ -113,7 +113,7 @@ in
 
       default = "hourly";
 
-      description = ''
+      description = lib.mdDoc ''
         The schedule on which to run the `self-deploy` service. Format
         specified by `systemd.time 7`.
 
@@ -125,20 +125,24 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    systemd.services.self-deploy = {
+    systemd.services.self-deploy = rec {
       inherit (cfg) startAt;
 
-      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "oneshot";
 
       requires = lib.mkIf (!(isPathType cfg.repository)) [ "network-online.target" ];
 
-      environment.GIT_SSH_COMMAND = lib.mkIf (!(isNull cfg.sshKeyFile))
+      after = requires;
+
+      environment.GIT_SSH_COMMAND = lib.mkIf (cfg.sshKeyFile != null)
         "${pkgs.openssh}/bin/ssh -i ${lib.escapeShellArg cfg.sshKeyFile}";
 
       restartIfChanged = false;
 
       path = with pkgs; [
         git
+        gnutar
+        gzip
         nix
       ] ++ lib.optionals (cfg.switchCommand == "boot") [ systemd ];
 
diff --git a/nixpkgs/nixos/modules/services/system/systembus-notify.nix b/nixpkgs/nixos/modules/services/system/systembus-notify.nix
index e918bc552ece..269197b3997e 100644
--- a/nixpkgs/nixos/modules/services/system/systembus-notify.nix
+++ b/nixpkgs/nixos/modules/services/system/systembus-notify.nix
@@ -8,13 +8,13 @@ let
 in
 {
   options.services.systembus-notify = {
-    enable = mkEnableOption ''
+    enable = mkEnableOption (lib.mdDoc ''
       System bus notification support
 
       WARNING: enabling this option (while convenient) should *not* be done on a
       machine where you do not trust the other users as it allows any other
       local user to DoS your session by spamming notifications.
-    '';
+    '');
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/torrent/deluge.nix b/nixpkgs/nixos/modules/services/torrent/deluge.nix
index 3f4cd2ff6e01..003f7b2613b7 100644
--- a/nixpkgs/nixos/modules/services/torrent/deluge.nix
+++ b/nixpkgs/nixos/modules/services/torrent/deluge.nix
@@ -37,7 +37,7 @@ in {
   options = {
     services = {
       deluge = {
-        enable = mkEnableOption "Deluge daemon";
+        enable = mkEnableOption (lib.mdDoc "Deluge daemon");
 
         openFilesLimit = mkOption {
           default = openFilesLimit;
@@ -66,7 +66,7 @@ in {
             `true`. String values must be quoted, integer and
             boolean values must not. See
             <https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41>
-            for the availaible options.
+            for the available options.
           '';
         };
 
@@ -93,7 +93,7 @@ in {
             `true`.
 
             It does NOT apply to the daemon port nor the web UI port. To access those
-            ports secuerly check the documentation
+            ports securely check the documentation
             <https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel>
             or use a VPN or configure certificates for deluge.
           '';
@@ -117,7 +117,7 @@ in {
             when {option}`services.deluge.declarative` is set to
             `true`.
             See <https://dev.deluge-torrent.org/wiki/UserGuide/Authentication> for
-            more informations.
+            more information.
           '';
         };
 
@@ -157,7 +157,7 @@ in {
       };
 
       deluge.web = {
-        enable = mkEnableOption "Deluge Web daemon";
+        enable = mkEnableOption (lib.mdDoc "Deluge Web daemon");
 
         port = mkOption {
           type = types.port;
diff --git a/nixpkgs/nixos/modules/services/torrent/flexget.nix b/nixpkgs/nixos/modules/services/torrent/flexget.nix
index 17d77bfae5fa..1b971838b32e 100644
--- a/nixpkgs/nixos/modules/services/torrent/flexget.nix
+++ b/nixpkgs/nixos/modules/services/torrent/flexget.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.flexget;
-  pkg = pkgs.flexget;
+  pkg = cfg.package;
   ymlFile = pkgs.writeText "flexget.yml" ''
     ${cfg.config}
 
@@ -14,7 +14,9 @@ let
 in {
   options = {
     services.flexget = {
-      enable = mkEnableOption "Run FlexGet Daemon";
+      enable = mkEnableOption (lib.mdDoc "Run FlexGet Daemon");
+
+      package = mkPackageOptionMD pkgs "flexget" {};
 
       user = mkOption {
         default = "deluge";
diff --git a/nixpkgs/nixos/modules/services/torrent/magnetico.nix b/nixpkgs/nixos/modules/services/torrent/magnetico.nix
index 11f1c71e3f81..dc6b4e9aa734 100644
--- a/nixpkgs/nixos/modules/services/torrent/magnetico.nix
+++ b/nixpkgs/nixos/modules/services/torrent/magnetico.nix
@@ -43,7 +43,7 @@ in {
   ###### interface
 
   options.services.magnetico = {
-    enable = mkEnableOption "Magnetico, Bittorrent DHT crawler";
+    enable = mkEnableOption (lib.mdDoc "Magnetico, Bittorrent DHT crawler");
 
     crawler.address = mkOption {
       type = types.str;
@@ -116,43 +116,41 @@ in {
           myuser = "$2y$12$YE01LZ8jrbQbx6c0s2hdZO71dSjn2p/O9XsYJpz.5968yCysUgiaG";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         The credentials to access the web interface, in case authentication is
-        enabled, in the format <literal>username:hash</literal>. If unset no
+        enabled, in the format `username:hash`. If unset no
         authentication will be required.
 
         Usernames must start with a lowercase ([a-z]) ASCII character, might
         contain non-consecutive underscores except at the end, and consists of
         small-case a-z characters and digits 0-9.  The
-        <command>htpasswd</command> tool from the <package>apacheHttpd
-        </package> package may be used to generate the hash: <command>htpasswd
-        -bnBC 12 username password</command>
-
-        <warning>
-        <para>
-          The hashes will be stored world-readable in the nix store.
-          Consider using the <literal>credentialsFile</literal> option if you
-          don't want this.
-        </para>
-        </warning>
+        {command}`htpasswd` tool from the `apacheHttpd`
+        package may be used to generate the hash:
+        {command}`htpasswd -bnBC 12 username password`
+
+        ::: {.warning}
+        The hashes will be stored world-readable in the nix store.
+        Consider using the `credentialsFile` option if you
+        don't want this.
+        :::
       '';
     };
 
     web.credentialsFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The path to the file holding the credentials to access the web
         interface. If unset no authentication will be required.
 
-        The file must constain user names and password hashes in the format
-        <literal>username:hash </literal>, one for each line.  Usernames must
+        The file must contain user names and password hashes in the format
+        `username:hash`, one for each line.  Usernames must
         start with a lowecase ([a-z]) ASCII character, might contain
         non-consecutive underscores except at the end, and consists of
         small-case a-z characters and digits 0-9.
-        The <command>htpasswd</command> tool from the <package>apacheHttpd
-        </package> package may be used to generate the hash:
-        <command>htpasswd -bnBC 12 username password</command>
+        The {command}`htpasswd` tool from the `apacheHttpd`
+        package may be used to generate the hash:
+        {command}`htpasswd -bnBC 12 username password`
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/torrent/opentracker.nix b/nixpkgs/nixos/modules/services/torrent/opentracker.nix
index 20b3d35a603a..7d67491c1191 100644
--- a/nixpkgs/nixos/modules/services/torrent/opentracker.nix
+++ b/nixpkgs/nixos/modules/services/torrent/opentracker.nix
@@ -5,7 +5,7 @@ let
   cfg = config.services.opentracker;
 in {
   options.services.opentracker = {
-    enable = mkEnableOption "opentracker";
+    enable = mkEnableOption (lib.mdDoc "opentracker");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/torrent/rtorrent.nix b/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
index a805e09923ff..64cda7fb675f 100644
--- a/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
+++ b/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
@@ -9,7 +9,7 @@ let
 
 in {
   options.services.rtorrent = {
-    enable = mkEnableOption "rtorrent";
+    enable = mkEnableOption (lib.mdDoc "rtorrent");
 
     dataDir = mkOption {
       type = types.str;
@@ -19,6 +19,15 @@ in {
       '';
     };
 
+    dataPermissions = mkOption {
+      type = types.str;
+      default = "0750";
+      example = "0755";
+      description = lib.mdDoc ''
+        Unix Permissions in octal on the rtorrent directory.
+      '';
+    };
+
     downloadDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/download";
@@ -82,7 +91,7 @@ in {
       type = types.lines;
       default = "";
       description = lib.mdDoc ''
-        The content of {file}`rtorrent.rc`. The [modernized configuration template](https://rtorrent-docs.readthedocs.io/en/latest/cookbook.html#modernized-configuration-template) with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completly.
+        The content of {file}`rtorrent.rc`. The [modernized configuration template](https://rtorrent-docs.readthedocs.io/en/latest/cookbook.html#modernized-configuration-template) with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completely.
       '';
     };
   };
@@ -205,7 +214,7 @@ in {
         };
       };
 
-      tmpfiles.rules = [ "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} -" ];
+      tmpfiles.rules = [ "d '${cfg.dataDir}' ${cfg.dataPermissions} ${cfg.user} ${cfg.group} -" ];
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/torrent/transmission.nix b/nixpkgs/nixos/modules/services/torrent/transmission.nix
index 6a038dc0a32c..752ab91fe631 100644
--- a/nixpkgs/nixos/modules/services/torrent/transmission.nix
+++ b/nixpkgs/nixos/modules/services/torrent/transmission.nix
@@ -19,19 +19,23 @@ in
   imports = [
     (mkRenamedOptionModule ["services" "transmission" "port"]
                            ["services" "transmission" "settings" "rpc-port"])
-    (mkAliasOptionModule ["services" "transmission" "openFirewall"]
-                         ["services" "transmission" "openPeerPorts"])
+    (mkAliasOptionModuleMD ["services" "transmission" "openFirewall"]
+                           ["services" "transmission" "openPeerPorts"])
   ];
   options = {
     services.transmission = {
-      enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
+      enable = mkEnableOption (lib.mdDoc "transmission") // {
+        description = lib.mdDoc ''
+          Whether to enable the headless Transmission BitTorrent daemon.
 
-        Transmission daemon can be controlled via the RPC interface using
-        transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
-        or other clients like stig or tremc.
+          Transmission daemon can be controlled via the RPC interface using
+          transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
+          or other clients like stig or tremc.
 
-        Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
-        accessible to users in the "transmission" group'';
+          Torrents are downloaded to [](#opt-services.transmission.home)/${downloadsDir} by default and are
+          accessible to users in the "transmission" group.
+        '';
+      };
 
       settings = mkOption {
         description = lib.mdDoc ''
@@ -40,7 +44,7 @@ in
           (each time the service starts).
 
           See [Transmission's Wiki](https://github.com/transmission/transmission/wiki/Editing-Configuration-Files)
-          for documentation of settings not explicitely covered by this module.
+          for documentation of settings not explicitly covered by this module.
         '';
         default = {};
         type = types.submodule {
@@ -67,7 +71,7 @@ in
           options.incomplete-dir-enabled = mkOption {
             type = types.bool;
             default = true;
-            description = "";
+            description = lib.mdDoc "";
           };
           options.message-level = mkOption {
             type = types.ints.between 0 3;
@@ -104,9 +108,9 @@ in
             type = types.str;
             default = "127.0.0.1";
             example = "0.0.0.0";
-            description = ''
+            description = lib.mdDoc ''
               Where to listen for RPC connections.
-              Use \"0.0.0.0\" to listen on all interfaces.
+              Use `0.0.0.0` to listen on all interfaces.
             '';
           };
           options.rpc-port = mkOption {
@@ -170,6 +174,8 @@ in
         };
       };
 
+      package = mkPackageOptionMD pkgs "transmission" {};
+
       downloadDirPermissions = mkOption {
         type = with types; nullOr str;
         default = null;
@@ -229,18 +235,22 @@ in
         '';
       };
 
-      openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
+      openPeerPorts = mkEnableOption (lib.mdDoc "opening of the peer port(s) in the firewall");
 
-      openRPCPort = mkEnableOption "opening of the RPC port in the firewall";
+      openRPCPort = mkEnableOption (lib.mdDoc "opening of the RPC port in the firewall");
 
-      performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
-        to open many more connections at the same time.
+      performanceNetParameters = mkEnableOption (lib.mdDoc "performance tweaks") // {
+        description = lib.mdDoc ''
+          Whether to enable tweaking of kernel parameters
+          to open many more connections at the same time.
 
-        Note that you may also want to increase
-        <literal>peer-limit-global"</literal>.
-        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'';
+          Note that you may also want to increase
+          `peer-limit-global`.
+          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.
+        '';
+      };
     };
   };
 
@@ -279,7 +289,7 @@ 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} ${escapeShellArgs cfg.extraFlags}";
+        ExecStart="${cfg.package}/bin/transmission-daemon -f -g ${cfg.home}/${settingsDir} ${escapeShellArgs cfg.extraFlags}";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         User = cfg.user;
         Group = cfg.group;
@@ -345,7 +355,7 @@ in
         PrivateUsers = true;
         ProtectClock = true;
         ProtectControlGroups = true;
-        # ProtectHome=true would not allow BindPaths= to work accross /home,
+        # ProtectHome=true would not allow BindPaths= to work across /home,
         # and ProtectHome=tmpfs would break statfs(),
         # preventing transmission-daemon to report the available free space.
         # However, RootDirectory= is used, so this is not a security concern
@@ -377,7 +387,7 @@ in
     };
 
     # It's useful to have transmission in path, e.g. for remote control
-    environment.systemPackages = [ pkgs.transmission ];
+    environment.systemPackages = [ cfg.package ];
 
     users.users = optionalAttrs (cfg.user == "transmission") ({
       transmission = {
@@ -423,7 +433,7 @@ in
       # https://trac.transmissionbt.com/browser/trunk/libtransmission/tr-udp.c?rev=11956.
       # at least up to the values hardcoded here:
       (mkIf cfg.settings.utp-enabled {
-        "net.core.rmem_max" = mkDefault "4194304"; # 4MB
+        "net.core.rmem_max" = mkDefault 4194304; # 4MB
         "net.core.wmem_max" = mkDefault "1048576"; # 1MB
       })
       (mkIf cfg.performanceNetParameters {
@@ -449,7 +459,7 @@ in
     ];
 
     security.apparmor.policies."bin.transmission-daemon".profile = ''
-      include "${pkgs.transmission.apparmor}/bin.transmission-daemon"
+      include "${cfg.package.apparmor}/bin.transmission-daemon"
     '';
     security.apparmor.includes."local/bin.transmission-daemon" = ''
       r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
diff --git a/nixpkgs/nixos/modules/services/tracing/tempo.nix b/nixpkgs/nixos/modules/services/tracing/tempo.nix
index 201f850656da..4a098c31effe 100644
--- a/nixpkgs/nixos/modules/services/tracing/tempo.nix
+++ b/nixpkgs/nixos/modules/services/tracing/tempo.nix
@@ -8,7 +8,7 @@ let
   settingsFormat = pkgs.formats.yaml {};
 in {
   options.services.tempo = {
-    enable = mkEnableOption "Grafana Tempo";
+    enable = mkEnableOption (lib.mdDoc "Grafana Tempo");
 
     settings = mkOption {
       type = settingsFormat.type;
diff --git a/nixpkgs/nixos/modules/services/ttys/getty.nix b/nixpkgs/nixos/modules/services/ttys/getty.nix
index aec65903cecb..22ae9c27e5bc 100644
--- a/nixpkgs/nixos/modules/services/ttys/getty.nix
+++ b/nixpkgs/nixos/modules/services/ttys/getty.nix
@@ -146,7 +146,7 @@ in
         enable = mkDefault config.boot.isContainer;
       };
 
-    environment.etc.issue =
+    environment.etc.issue = mkDefault
       { # Friendly greeting on the virtual consoles.
         source = pkgs.writeText "issue" ''
 
diff --git a/nixpkgs/nixos/modules/services/video/epgstation/default.nix b/nixpkgs/nixos/modules/services/video/epgstation/default.nix
index 51f71389263c..fca483b0dbd7 100644
--- a/nixpkgs/nixos/modules/services/video/epgstation/default.nix
+++ b/nixpkgs/nixos/modules/services/video/epgstation/default.nix
@@ -78,13 +78,13 @@ in
   ];
 
   options.services.epgstation = {
-    enable = lib.mkEnableOption description;
+    enable = lib.mkEnableOption (lib.mdDoc description);
 
-    package = lib.mkOption {
-      default = pkgs.epgstation;
-      type = lib.types.package;
-      defaultText = lib.literalExpression "pkgs.epgstation";
-      description = lib.mdDoc "epgstation package to use";
+    package = lib.mkPackageOptionMD pkgs "epgstation" { };
+
+    ffmpeg = lib.mkPackageOptionMD pkgs "ffmpeg" {
+      default = [ "ffmpeg-headless" ];
+      example = "pkgs.ffmpeg-full";
     };
 
     usePreconfiguredStreaming = lib.mkOption {
@@ -101,16 +101,14 @@ in
     openFirewall = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open ports in the firewall for the EPGStation web interface.
 
-        <warning>
-          <para>
-            Exposing EPGStation to the open internet is generally advised
-            against. Only use it inside a trusted local network, or consider
-            putting it behind a VPN if you want remote access.
-          </para>
-        </warning>
+        ::: {.warning}
+        Exposing EPGStation to the open internet is generally advised
+        against. Only use it inside a trusted local network, or consider
+        putting it behind a VPN if you want remote access.
+        :::
       '';
     };
 
@@ -266,6 +264,9 @@ in
       description = "EPGStation user";
       group = config.users.groups.epgstation.name;
       isSystemUser = true;
+
+      # NPM insists on creating ~/.npm
+      home = "/var/cache/epgstation";
     };
 
     users.groups.epgstation = { };
@@ -277,6 +278,8 @@ in
       package = lib.mkDefault pkgs.mariadb;
       ensureDatabases = [ cfg.database.name ];
       # FIXME: enable once mysqljs supports auth_socket
+      # https://github.com/mysqljs/mysql/issues/1507
+      #
       # ensureUsers = [ {
       #   name = username;
       #   ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
@@ -294,8 +297,8 @@ in
             database = cfg.database.name;
           };
 
-          ffmpeg = lib.mkDefault "${pkgs.ffmpeg-full}/bin/ffmpeg";
-          ffprobe = lib.mkDefault "${pkgs.ffmpeg-full}/bin/ffprobe";
+          ffmpeg = lib.mkDefault "${cfg.ffmpeg}/bin/ffmpeg";
+          ffprobe = lib.mkDefault "${cfg.ffmpeg}/bin/ffprobe";
 
           # for disambiguation with TypeScript files
           recordedFileExtension = lib.mkDefault ".m2ts";
@@ -307,9 +310,15 @@ in
       ];
 
     systemd.tmpfiles.rules = [
+      "d '/var/lib/epgstation/key' - ${username} ${groupname} - -"
       "d '/var/lib/epgstation/streamfiles' - ${username} ${groupname} - -"
+      "d '/var/lib/epgstation/drop' - ${username} ${groupname} - -"
       "d '/var/lib/epgstation/recorded' - ${username} ${groupname} - -"
       "d '/var/lib/epgstation/thumbnail' - ${username} ${groupname} - -"
+      "d '/var/lib/epgstation/db/subscribers' - ${username} ${groupname} - -"
+      "d '/var/lib/epgstation/db/migrations/mysql' - ${username} ${groupname} - -"
+      "d '/var/lib/epgstation/db/migrations/postgres' - ${username} ${groupname} - -"
+      "d '/var/lib/epgstation/db/migrations/sqlite' - ${username} ${groupname} - -"
     ];
 
     systemd.services.epgstation = {
@@ -320,11 +329,14 @@ in
         ++ lib.optional config.services.mirakurun.enable "mirakurun.service"
         ++ lib.optional config.services.mysql.enable "mysql.service";
 
+      environment.NODE_ENV = "production";
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/epgstation start";
         ExecStartPre = "+${preStartScript}";
         User = username;
         Group = groupname;
+        CacheDirectory = "epgstation";
         StateDirectory = "epgstation";
         LogsDirectory = "epgstation";
         ConfigurationDirectory = "epgstation";
diff --git a/nixpkgs/nixos/modules/services/video/frigate.nix b/nixpkgs/nixos/modules/services/video/frigate.nix
new file mode 100644
index 000000000000..217637cbebcf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/video/frigate.nix
@@ -0,0 +1,368 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  inherit (lib)
+    literalExpression
+    mkDefault
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkOption
+    types;
+
+  cfg = config.services.frigate;
+
+  format = pkgs.formats.yaml {};
+
+  filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! lib.elem v [ null ])) cfg.settings;
+
+  cameraFormat = with types; submodule {
+    freeformType = format.type;
+    options = {
+      ffmpeg = {
+        inputs = mkOption {
+          description = mdDoc ''
+            List of inputs for this camera.
+          '';
+          type = listOf (submodule {
+            freeformType = format.type;
+            options = {
+              path = mkOption {
+                type = str;
+                example = "rtsp://192.0.2.1:554/rtsp";
+                description = mdDoc ''
+                  Stream URL
+                '';
+              };
+              roles = mkOption {
+                type = listOf (enum [ "detect" "record" "rtmp" ]);
+                example = literalExpression ''
+                  [ "detect" "rtmp" ]
+                '';
+                description = mdDoc ''
+                  List of roles for this stream
+                '';
+              };
+            };
+          });
+        };
+      };
+    };
+  };
+
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.frigate = with types; {
+    enable = mkEnableOption (mdDoc "Frigate NVR");
+
+    package = mkOption {
+      type = package;
+      default = pkgs.frigate;
+      description = mdDoc ''
+        The frigate package to use.
+      '';
+    };
+
+    hostname = mkOption {
+      type = str;
+      example = "frigate.exampe.com";
+      description = mdDoc ''
+        Hostname of the nginx vhost to configure.
+
+        Only nginx is supported by upstream for direct reverse proxying.
+      '';
+    };
+
+    settings = mkOption {
+      type = submodule {
+        freeformType = format.type;
+        options = {
+          cameras = mkOption {
+            type = attrsOf cameraFormat;
+            description = mdDoc ''
+              Attribute set of cameras configurations.
+
+              https://docs.frigate.video/configuration/cameras
+            '';
+          };
+
+          database = {
+            path = mkOption {
+              type = path;
+              default = "/var/lib/frigate/frigate.db";
+              description = mdDoc ''
+                Path to the SQLite database used
+              '';
+            };
+          };
+
+          mqtt = {
+            enabled = mkEnableOption (mdDoc "MQTT support");
+
+            host = mkOption {
+              type = nullOr str;
+              default = null;
+              example = "mqtt.example.com";
+              description = mdDoc ''
+                MQTT server hostname
+              '';
+            };
+          };
+        };
+      };
+      default = {};
+      description = mdDoc ''
+        Frigate configuration as a nix attribute set.
+
+        See the project documentation for how to configure frigate.
+        - [Creating a config file](https://docs.frigate.video/guides/getting_started)
+        - [Configuration reference](https://docs.frigate.video/configuration/index)
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.nginx = {
+      enable =true;
+      additionalModules = with pkgs.nginxModules; [
+        secure-token
+        rtmp
+        vod
+      ];
+      recommendedProxySettings = mkDefault true;
+      recommendedGzipSettings = mkDefault true;
+      upstreams = {
+        frigate-api.servers = {
+          "127.0.0.1:5001" = {};
+        };
+        frigate-mqtt-ws.servers = {
+          "127.0.0.1:5002" = {};
+        };
+        frigate-jsmpeg.servers = {
+          "127.0.0.1:8082" = {};
+        };
+        frigate-go2rtc.servers = {
+          "127.0.0.1:1984" = {};
+        };
+      };
+      # Based on https://github.com/blakeblackshear/frigate/blob/v0.12.0/docker/rootfs/usr/local/nginx/conf/nginx.conf
+      virtualHosts."${cfg.hostname}" = {
+        locations = {
+          "/api/" = {
+            proxyPass = "http://frigate-api/";
+          };
+          "~* /api/.*\.(jpg|jpeg|png)$" = {
+            proxyPass = "http://frigate-api";
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' '*';
+              add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
+              rewrite ^/api/(.*)$ $1 break;
+            '';
+          };
+          "/vod/" = {
+            extraConfig = ''
+              aio threads;
+              vod hls;
+
+              secure_token $args;
+              secure_token_types application/vnd.apple.mpegurl;
+
+              add_header Access-Control-Allow-Headers '*';
+              add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range';
+              add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
+              add_header Access-Control-Allow-Origin '*';
+              add_header Cache-Control "no-store";
+              expires off;
+            '';
+          };
+          "/stream/" = {
+            # TODO
+          };
+          "/ws" = {
+            proxyPass = "http://frigate-mqtt-ws/";
+            proxyWebsockets = true;
+          };
+          "/live/jsmpeg" = {
+            proxyPass = "http://frigate-jsmpeg/";
+            proxyWebsockets = true;
+          };
+          "/live/mse/" = {
+            proxyPass = "http://frigate-go2rtc/";
+            proxyWebsockets = true;
+          };
+          "/live/webrtc/" = {
+            proxyPass = "http://frigate-go2rtc/";
+            proxyWebsockets = true;
+          };
+          "/cache/" = {
+            alias = "/var/cache/frigate/";
+          };
+          "/clips/" = {
+            root = "/var/lib/frigate";
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+              add_header 'Access-Control-Allow-Credentials' 'true';
+              add_header 'Access-Control-Expose-Headers' 'Content-Length';
+              if ($request_method = 'OPTIONS') {
+                  add_header 'Access-Control-Allow-Origin' "$http_origin";
+                  add_header 'Access-Control-Max-Age' 1728000;
+                  add_header 'Content-Type' 'text/plain charset=UTF-8';
+                  add_header 'Content-Length' 0;
+                  return 204;
+              }
+
+              types {
+                  video/mp4 mp4;
+                  image/jpeg jpg;
+              }
+
+              autoindex on;
+            '';
+          };
+          "/recordings/" = {
+            root = "/var/lib/frigate";
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' "$http_origin" always;
+              add_header 'Access-Control-Allow-Credentials' 'true';
+              add_header 'Access-Control-Expose-Headers' 'Content-Length';
+              if ($request_method = 'OPTIONS') {
+                  add_header 'Access-Control-Allow-Origin' "$http_origin";
+                  add_header 'Access-Control-Max-Age' 1728000;
+                  add_header 'Content-Type' 'text/plain charset=UTF-8';
+                  add_header 'Content-Length' 0;
+                  return 204;
+              }
+
+              types {
+                  video/mp4 mp4;
+              }
+
+              autoindex on;
+              autoindex_format json;
+            '';
+          };
+          "/assets/" = {
+            root = cfg.package.web;
+            extraConfig = ''
+              access_log off;
+              expires 1y;
+              add_header Cache-Control "public";
+            '';
+          };
+          "/" = {
+            root = cfg.package.web;
+            tryFiles = "$uri $uri/ /index.html";
+            extraConfig = ''
+              add_header Cache-Control "no-store";
+              expires off;
+
+              sub_filter 'href="/BASE_PATH/' 'href="$http_x_ingress_path/';
+              sub_filter 'url(/BASE_PATH/' 'url($http_x_ingress_path/';
+              sub_filter '"/BASE_PATH/dist/' '"$http_x_ingress_path/dist/';
+              sub_filter '"/BASE_PATH/js/' '"$http_x_ingress_path/js/';
+              sub_filter '"/BASE_PATH/assets/' '"$http_x_ingress_path/assets/';
+              sub_filter '"/BASE_PATH/monacoeditorwork/' '"$http_x_ingress_path/assets/';
+              sub_filter 'return"/BASE_PATH/"' 'return window.baseUrl';
+              sub_filter '<body>' '<body><script>window.baseUrl="$http_x_ingress_path/";</script>';
+              sub_filter_types text/css application/javascript;
+              sub_filter_once off;
+            '';
+          };
+        };
+        extraConfig = ''
+          # vod settings
+          vod_base_url "";
+          vod_segments_base_url "";
+          vod_mode mapped;
+          vod_max_mapping_response_size 1m;
+          vod_upstream_location /api;
+          vod_align_segments_to_key_frames on;
+          vod_manifest_segment_durations_mode accurate;
+          vod_ignore_edit_list on;
+          vod_segment_duration 10000;
+          vod_hls_mpegts_align_frames off;
+          vod_hls_mpegts_interleave_frames on;
+          # file handle caching / aio
+          open_file_cache max=1000 inactive=5m;
+          open_file_cache_valid 2m;
+          open_file_cache_min_uses 1;
+          open_file_cache_errors on;
+          aio on;
+          # https://github.com/kaltura/nginx-vod-module#vod_open_file_thread_pool
+          vod_open_file_thread_pool default;
+          # vod caches
+          vod_metadata_cache metadata_cache 512m;
+          vod_mapping_cache mapping_cache 5m 10m;
+          # gzip manifest
+          gzip_types application/vnd.apple.mpegurl;
+        '';
+      };
+      appendConfig = ''
+        rtmp {
+            server {
+                listen 1935;
+                chunk_size 4096;
+                allow publish 127.0.0.1;
+                deny publish all;
+                allow play all;
+                application live {
+                    live on;
+                    record off;
+                    meta copy;
+                }
+            }
+        }
+      '';
+    };
+
+    systemd.services.frigate = {
+      after = [
+        "go2rtc.service"
+        "network.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      environment = {
+        CONFIG_FILE = format.generate "frigate.yml" filteredConfig;
+        HOME = "/var/lib/frigate";
+        PYTHONPATH = cfg.package.pythonPath;
+      };
+      path = with pkgs; [
+        # unfree:
+        # config.boot.kernelPackages.nvidiaPackages.latest.bin
+        ffmpeg_5-headless
+        libva-utils
+        procps
+        radeontop
+      ] ++ lib.optionals (!stdenv.isAarch64) [
+        # not available on aarch64-linux
+        intel-gpu-tools
+      ];
+      serviceConfig = {
+        ExecStart = "${cfg.package.python.interpreter} -m frigate";
+
+        DynamicUser = true;
+        User = "frigate";
+
+        StateDirectory = "frigate";
+        UMask = "0077";
+
+        # Caches
+        PrivateTmp = true;
+        CacheDirectory = "frigate";
+
+        BindPaths = [
+          "/migrations:${cfg.package}/share/frigate/migrations:ro"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/video/go2rtc/default.nix b/nixpkgs/nixos/modules/services/video/go2rtc/default.nix
new file mode 100644
index 000000000000..1151d31b68e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/video/go2rtc/default.nix
@@ -0,0 +1,115 @@
+{ lib
+, config
+, options
+, pkgs
+, ...
+}:
+
+let
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkEnableOption
+    mkOption
+    mkPackageOptionMD
+    types
+    ;
+
+  cfg = config.services.go2rtc;
+  opt = options.services.go2rtc;
+
+  format = pkgs.formats.yaml {};
+  configFile = format.generate "go2rtc.yaml" cfg.settings;
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.go2rtc = with types; {
+    enable = mkEnableOption (mdDoc "go2rtc streaming server");
+
+    package = mkPackageOptionMD pkgs "go2rtc" { };
+
+    settings = mkOption {
+      default = {};
+      description = mdDoc ''
+        go2rtc configuration as a Nix attribute set.
+
+        See the [wiki](https://github.com/AlexxIT/go2rtc/wiki/Configuration) for possible configuration options.
+      '';
+      type = submodule {
+        freeformType = format.type;
+        options = {
+          # https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#module-api
+          api = {
+            listen = mkOption {
+              type = str;
+              default = ":1984";
+              example = "127.0.0.1:1984";
+              description = mdDoc ''
+                API listen address, conforming to a Go address string.
+              '';
+            };
+          };
+
+          # https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#source-ffmpeg
+          ffmpeg = {
+            bin = mkOption {
+              type = path;
+              default = "${lib.getBin pkgs.ffmpeg_6-headless}/bin/ffmpeg";
+              defaultText = literalExpression "\${lib.getBin pkgs.ffmpeg_6-headless}/bin/ffmpeg";
+              description = mdDoc ''
+                The ffmpeg package to use for transcoding.
+              '';
+            };
+          };
+
+          # TODO: https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#module-rtsp
+          rtsp = {
+          };
+
+          streams = mkOption {
+            type = attrsOf (either str (listOf str));
+            default = {};
+            example = literalExpression ''
+              {
+                cam1 = "onvif://admin:password@192.168.1.123:2020";
+                cam2 = "tcp://192.168.1.123:12345";
+              }
+            '';
+            description = mdDoc ''
+              Stream source configuration. Multiple source types are supported.
+
+              Check the [configuration reference](https://github.com/AlexxIT/go2rtc/blob/v${cfg.package.version}/README.md#module-streams) for possible options.
+            '';
+          };
+
+          # TODO: https://github.com/AlexxIT/go2rtc/blob/v1.5.0/README.md#module-webrtc
+          webrtc = {
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.go2rtc = {
+      after = [
+        "network-online.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      serviceConfig = {
+        DynamicUser = true;
+        User = "go2rtc";
+        SupplementaryGroups = [
+          # for v4l2 devices
+          "video"
+        ];
+        StateDirectory = "go2rtc";
+        ExecStart = "${cfg.package}/bin/go2rtc -config ${configFile}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix b/nixpkgs/nixos/modules/services/video/mediamtx.nix
index db6f0441bbf1..18a9e3d5fe30 100644
--- a/nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix
+++ b/nixpkgs/nixos/modules/services/video/mediamtx.nix
@@ -3,19 +3,19 @@
 with lib;
 
 let
-  cfg = config.services.rtsp-simple-server;
-  package = pkgs.rtsp-simple-server;
+  cfg = config.services.mediamtx;
+  package = pkgs.mediamtx;
   format = pkgs.formats.yaml {};
 in
 {
   options = {
-    services.rtsp-simple-server = {
-      enable = mkEnableOption "RTSP Simple Server";
+    services.mediamtx = {
+      enable = mkEnableOption (lib.mdDoc "MediaMTX");
 
       settings = mkOption {
         description = lib.mdDoc ''
-          Settings for rtsp-simple-server.
-          Read more at <https://github.com/aler9/rtsp-simple-server/blob/main/rtsp-simple-server.yml>
+          Settings for MediaMTX.
+          Read more at <https://github.com/aler9/mediamtx/blob/main/mediamtx.yml>
         '';
         type = format.type;
 
@@ -25,7 +25,7 @@ in
             "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";
+          logFile = "/var/log/mediamtx/mediamtx.log";
         };
 
         example = {
@@ -40,20 +40,20 @@ in
 
       env = mkOption {
         type = with types; attrsOf anything;
-        description = lib.mdDoc "Extra environment variables for RTSP Simple Server";
+        description = lib.mdDoc "Extra environment variables for MediaMTX";
         default = {};
         example = {
-          RTSP_CONFKEY = "mykey";
+          MTX_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;
+    # NOTE: mediamtx watches this file and automatically reloads if it changes
+    environment.etc."mediamtx.yaml".source = format.generate "mediamtx.yaml" cfg.settings;
 
-    systemd.services.rtsp-simple-server = {
+    systemd.services.mediamtx = {
       environment = cfg.env;
 
       after = [ "network.target" ];
@@ -65,15 +65,15 @@ in
 
       serviceConfig = {
         DynamicUser = true;
-        User = "rtsp-simple-server";
-        Group = "rtsp-simple-server";
+        User = "mediamtx";
+        Group = "mediamtx";
 
-        LogsDirectory = "rtsp-simple-server";
+        LogsDirectory = "mediamtx";
 
         # 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";
+        ExecStart = "${package}/bin/mediamtx /etc/mediamtx.yaml";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/video/mirakurun.nix b/nixpkgs/nixos/modules/services/video/mirakurun.nix
index 6891b84ff455..31f90650ba9a 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 "the Mirakurun DVR Tuner Server";
+        enable = mkEnableOption (lib.mdDoc "the Mirakurun DVR Tuner Server");
 
         port = mkOption {
           type = with types; nullOr port;
@@ -38,16 +38,14 @@ in
         openFirewall = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Open ports in the firewall for Mirakurun.
 
-            <warning>
-              <para>
-                Exposing Mirakurun to the open internet is generally advised
-                against. Only use it inside a trusted local network, or
-                consider putting it behind a VPN if you want remote access.
-              </para>
-            </warning>
+            ::: {.warning}
+            Exposing Mirakurun to the open internet is generally advised
+            against. Only use it inside a trusted local network, or
+            consider putting it behind a VPN if you want remote access.
+            :::
           '';
         };
 
@@ -156,6 +154,9 @@ in
         description = "Mirakurun user";
         group = "video";
         isSystemUser = true;
+
+        # NPM insists on creating ~/.npm
+        home = "/var/cache/mirakurun";
       };
 
       services.mirakurun.serverSettings = {
@@ -173,9 +174,10 @@ in
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
         serviceConfig = {
-          ExecStart = "${mirakurun}/bin/mirakurun-start";
+          ExecStart = "${mirakurun}/bin/mirakurun start";
           User = username;
           Group = groupname;
+          CacheDirectory = "mirakurun";
           RuntimeDirectory="mirakurun";
           StateDirectory="mirakurun";
           Nice = -10;
diff --git a/nixpkgs/nixos/modules/services/video/replay-sorcery.nix b/nixpkgs/nixos/modules/services/video/replay-sorcery.nix
index f3cecfc248cf..1be02f4d6da5 100644
--- a/nixpkgs/nixos/modules/services/video/replay-sorcery.nix
+++ b/nixpkgs/nixos/modules/services/video/replay-sorcery.nix
@@ -9,12 +9,12 @@ in
 {
   options = with types; {
     services.replay-sorcery = {
-      enable = mkEnableOption "the ReplaySorcery service for instant-replays";
+      enable = mkEnableOption (lib.mdDoc "the ReplaySorcery service for instant-replays");
 
-      enableSysAdminCapability = mkEnableOption ''
+      enableSysAdminCapability = mkEnableOption (lib.mdDoc ''
         the system admin capability to support hardware accelerated
         video capture. This is equivalent to running ReplaySorcery as
-        root, so use with caution'';
+        root, so use with caution'');
 
       autoStart = mkOption {
         type = bool;
diff --git a/nixpkgs/nixos/modules/services/video/unifi-video.nix b/nixpkgs/nixos/modules/services/video/unifi-video.nix
index fcc3cb02a1b0..cb438a08150f 100644
--- a/nixpkgs/nixos/modules/services/video/unifi-video.nix
+++ b/nixpkgs/nixos/modules/services/video/unifi-video.nix
@@ -123,7 +123,7 @@ in
 
     mongodbPackage = mkOption {
       type = types.package;
-      default = pkgs.mongodb-4_0;
+      default = pkgs.mongodb-4_4;
       defaultText = literalExpression "pkgs.mongodb";
       description = lib.mdDoc ''
         The mongodb package to use.
@@ -148,7 +148,7 @@ in
 
     openFirewall = mkOption {
       type = types.bool;
-      default = true;
+      default = false;
       description = lib.mdDoc ''
         Whether or not to open the required ports on the firewall.
       '';
@@ -159,7 +159,7 @@ in
       default = 1024;
       example = 4096;
       description = lib.mdDoc ''
-        Set the maximimum heap size for the JVM in MB.
+        Set the maximum heap size for the JVM in MB.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/video/v4l2-relayd.nix b/nixpkgs/nixos/modules/services/video/v4l2-relayd.nix
new file mode 100644
index 000000000000..2a9dbe00158f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/video/v4l2-relayd.nix
@@ -0,0 +1,199 @@
+{ config, lib, pkgs, utils, ... }:
+let
+
+  inherit (lib) attrValues concatStringsSep filterAttrs length listToAttrs literalExpression
+    makeSearchPathOutput mkEnableOption mkIf mkOption nameValuePair optionals types;
+  inherit (utils) escapeSystemdPath;
+
+  cfg = config.services.v4l2-relayd;
+
+  kernelPackages = config.boot.kernelPackages;
+
+  gst = (with pkgs.gst_all_1; [
+    gst-plugins-bad
+    gst-plugins-base
+    gst-plugins-good
+    gstreamer.out
+  ]);
+
+  instanceOpts = { name, ... }: {
+    options = {
+      enable = mkEnableOption (lib.mdDoc "this v4l2-relayd instance");
+
+      name = mkOption {
+        type = types.str;
+        default = name;
+        description = lib.mdDoc ''
+          The name of the instance.
+        '';
+      };
+
+      cardLabel = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          The name the camera will show up as.
+        '';
+      };
+
+      extraPackages = mkOption {
+        type = with types; listOf package;
+        default = [ ];
+        description = lib.mdDoc ''
+          Extra packages to add to {env}`GST_PLUGIN_PATH` for the instance.
+        '';
+      };
+
+      input = {
+        pipeline = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            The gstreamer-pipeline to use for the input-stream.
+          '';
+        };
+
+        format = mkOption {
+          type = types.str;
+          default = "YUY2";
+          description = lib.mdDoc ''
+            The video-format to read from input-stream.
+          '';
+        };
+
+        width = mkOption {
+          type = types.ints.positive;
+          default = 1280;
+          description = lib.mdDoc ''
+            The width to read from input-stream.
+          '';
+        };
+
+        height = mkOption {
+          type = types.ints.positive;
+          default = 720;
+          description = lib.mdDoc ''
+            The height to read from input-stream.
+          '';
+        };
+
+        framerate = mkOption {
+          type = types.ints.positive;
+          default = 30;
+          description = lib.mdDoc ''
+            The framerate to read from input-stream.
+          '';
+        };
+      };
+
+      output = {
+        format = mkOption {
+          type = types.str;
+          default = "YUY2";
+          description = lib.mdDoc ''
+            The video-format to write to output-stream.
+          '';
+        };
+      };
+
+    };
+  };
+
+in
+{
+
+  options.services.v4l2-relayd = {
+
+    instances = mkOption {
+      type = with types; attrsOf (submodule instanceOpts);
+      default = { };
+      example = literalExpression ''
+        {
+          example = {
+            cardLabel = "Example card";
+            input.pipeline = "videotestsrc";
+          };
+        }
+      '';
+      description = lib.mdDoc ''
+        v4l2-relayd instances to be created.
+      '';
+    };
+
+  };
+
+  config =
+    let
+
+      mkInstanceService = instance: {
+        description = "Streaming relay for v4l2loopback using GStreamer";
+
+        after = [ "modprobe@v4l2loopback.service" "systemd-logind.service" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          Type = "simple";
+          Restart = "always";
+          PrivateNetwork = true;
+          PrivateTmp = true;
+          LimitNPROC = 1;
+        };
+
+        environment = {
+          GST_PLUGIN_PATH = makeSearchPathOutput "lib" "lib/gstreamer-1.0" (gst ++ instance.extraPackages);
+          V4L2_DEVICE_FILE = "/run/v4l2-relayd-${instance.name}/device";
+        };
+
+        script =
+          let
+            appsrcOptions = concatStringsSep "," [
+              "caps=video/x-raw"
+              "format=${instance.input.format}"
+              "width=${toString instance.input.width}"
+              "height=${toString instance.input.height}"
+              "framerate=${toString instance.input.framerate}/1"
+            ];
+
+            outputPipeline = [
+              "appsrc name=appsrc ${appsrcOptions}"
+              "videoconvert"
+            ] ++ optionals (instance.input.format != instance.output.format) [
+              "video/x-raw,format=${instance.output.format}"
+              "queue"
+            ] ++ [ "v4l2sink name=v4l2sink device=$(cat $V4L2_DEVICE_FILE)" ];
+          in
+          ''
+            exec ${pkgs.v4l2-relayd}/bin/v4l2-relayd -i "${instance.input.pipeline}" -o "${concatStringsSep " ! " outputPipeline}"
+          '';
+
+        preStart = ''
+          mkdir -p $(dirname $V4L2_DEVICE_FILE)
+          ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl add -x 1 -n "${instance.cardLabel}" > $V4L2_DEVICE_FILE
+        '';
+
+        postStop = ''
+          ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl delete $(cat $V4L2_DEVICE_FILE)
+          rm -rf $(dirname $V4L2_DEVICE_FILE)
+        '';
+      };
+
+      mkInstanceServices = instances: listToAttrs (map
+        (instance:
+          nameValuePair "v4l2-relayd-${escapeSystemdPath instance.name}" (mkInstanceService instance)
+        )
+        instances);
+
+      enabledInstances = attrValues (filterAttrs (n: v: v.enable) cfg.instances);
+
+    in
+    {
+
+      boot = mkIf ((length enabledInstances) > 0) {
+        extraModulePackages = [ kernelPackages.v4l2loopback ];
+        kernelModules = [ "v4l2loopback" ];
+      };
+
+      systemd.services = mkInstanceServices enabledInstances;
+
+    };
+
+  meta.maintainers = with lib.maintainers; [ betaboon ];
+}
diff --git a/nixpkgs/nixos/modules/services/wayland/cage.nix b/nixpkgs/nixos/modules/services/wayland/cage.nix
index c7accc5f9e18..330dce1d0c0f 100644
--- a/nixpkgs/nixos/modules/services/wayland/cage.nix
+++ b/nixpkgs/nixos/modules/services/wayland/cage.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.cage;
 in {
-  options.services.cage.enable = mkEnableOption "cage kiosk service";
+  options.services.cage.enable = mkEnableOption (lib.mdDoc "cage kiosk service");
 
   options.services.cage.user = mkOption {
     type = types.str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/akkoma.md b/nixpkgs/nixos/modules/services/web-apps/akkoma.md
new file mode 100644
index 000000000000..83dd1a8b35f2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/akkoma.md
@@ -0,0 +1,332 @@
+# Akkoma {#module-services-akkoma}
+
+[Akkoma](https://akkoma.dev/) is a lightweight ActivityPub microblogging server forked from Pleroma.
+
+## Service configuration {#modules-services-akkoma-service-configuration}
+
+The Elixir configuration file required by Akkoma is generated automatically from
+[{option}`services.akkoma.config`](options.html#opt-services.akkoma.config). Secrets must be
+included from external files outside of the Nix store by setting the configuration option to
+an attribute set containing the attribute {option}`_secret` – a string pointing to the file
+containing the actual value of the option.
+
+For the mandatory configuration settings these secrets will be generated automatically if the
+referenced file does not exist during startup, unless disabled through
+[{option}`services.akkoma.initSecrets`](options.html#opt-services.akkoma.initSecrets).
+
+The following configuration binds Akkoma to the Unix socket `/run/akkoma/socket`, expecting to
+be run behind a HTTP proxy on `fediverse.example.com`.
+
+
+```nix
+services.akkoma.enable = true;
+services.akkoma.config = {
+  ":pleroma" = {
+    ":instance" = {
+      name = "My Akkoma instance";
+      description = "More detailed description";
+      email = "admin@example.com";
+      registration_open = false;
+    };
+
+    "Pleroma.Web.Endpoint" = {
+      url.host = "fediverse.example.com";
+    };
+  };
+};
+```
+
+Please refer to the [configuration cheat sheet](https://docs.akkoma.dev/stable/configuration/cheatsheet/)
+for additional configuration options.
+
+## User management {#modules-services-akkoma-user-management}
+
+After the Akkoma service is running, the administration utility can be used to
+[manage users](https://docs.akkoma.dev/stable/administration/CLI_tasks/user/). In particular an
+administrative user can be created with
+
+```ShellSession
+$ pleroma_ctl user new <nickname> <email> --admin --moderator --password <password>
+```
+
+## Proxy configuration {#modules-services-akkoma-proxy-configuration}
+
+Although it is possible to expose Akkoma directly, it is common practice to operate it behind an
+HTTP reverse proxy such as nginx.
+
+```nix
+services.akkoma.nginx = {
+  enableACME = true;
+  forceSSL = true;
+};
+
+services.nginx = {
+  enable = true;
+
+  clientMaxBodySize = "16m";
+  recommendedTlsSettings = true;
+  recommendedOptimisation = true;
+  recommendedGzipSettings = true;
+};
+```
+
+Please refer to [](#module-security-acme) for details on how to provision an SSL/TLS certificate.
+
+### Media proxy {#modules-services-akkoma-media-proxy}
+
+Without the media proxy function, Akkoma does not store any remote media like pictures or video
+locally, and clients have to fetch them directly from the source server.
+
+```nix
+# Enable nginx slice module distributed with Tengine
+services.nginx.package = pkgs.tengine;
+
+# Enable media proxy
+services.akkoma.config.":pleroma".":media_proxy" = {
+  enabled = true;
+  proxy_opts.redirect_on_failure = true;
+};
+
+# Adjust the persistent cache size as needed:
+#  Assuming an average object size of 128 KiB, around 1 MiB
+#  of memory is required for the key zone per GiB of cache.
+# Ensure that the cache directory exists and is writable by nginx.
+services.nginx.commonHttpConfig = ''
+  proxy_cache_path /var/cache/nginx/cache/akkoma-media-cache
+    levels= keys_zone=akkoma_media_cache:16m max_size=16g
+    inactive=1y use_temp_path=off;
+'';
+
+services.akkoma.nginx = {
+  locations."/proxy" = {
+    proxyPass = "http://unix:/run/akkoma/socket";
+
+    extraConfig = ''
+      proxy_cache akkoma_media_cache;
+
+      # Cache objects in slices of 1 MiB
+      slice 1m;
+      proxy_cache_key $host$uri$is_args$args$slice_range;
+      proxy_set_header Range $slice_range;
+
+      # Decouple proxy and upstream responses
+      proxy_buffering on;
+      proxy_cache_lock on;
+      proxy_ignore_client_abort on;
+
+      # Default cache times for various responses
+      proxy_cache_valid 200 1y;
+      proxy_cache_valid 206 301 304 1h;
+
+      # Allow serving of stale items
+      proxy_cache_use_stale error timeout invalid_header updating;
+    '';
+  };
+};
+```
+
+#### Prefetch remote media {#modules-services-akkoma-prefetch-remote-media}
+
+The following example enables the `MediaProxyWarmingPolicy` MRF policy which automatically
+fetches all media associated with a post through the media proxy, as soon as the post is
+received by the instance.
+
+```nix
+services.akkoma.config.":pleroma".":mrf".policies =
+  map (pkgs.formats.elixirConf { }).lib.mkRaw [
+    "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy"
+];
+```
+
+#### Media previews {#modules-services-akkoma-media-previews}
+
+Akkoma can generate previews for media.
+
+```nix
+services.akkoma.config.":pleroma".":media_preview_proxy" = {
+  enabled = true;
+  thumbnail_max_width = 1920;
+  thumbnail_max_height = 1080;
+};
+```
+
+## Frontend management {#modules-services-akkoma-frontend-management}
+
+Akkoma will be deployed with the `akkoma-fe` and `admin-fe` frontends by default. These can be
+modified by setting
+[{option}`services.akkoma.frontends`](options.html#opt-services.akkoma.frontends).
+
+The following example overrides the primary frontend’s default configuration using a custom
+derivation.
+
+```nix
+services.akkoma.frontends.primary.package = pkgs.runCommand "akkoma-fe" {
+  config = builtins.toJSON {
+    expertLevel = 1;
+    collapseMessageWithSubject = false;
+    stopGifs = false;
+    replyVisibility = "following";
+    webPushHideIfCW = true;
+    hideScopeNotice = true;
+    renderMisskeyMarkdown = false;
+    hideSiteFavicon = true;
+    postContentType = "text/markdown";
+    showNavShortcuts = false;
+  };
+  nativeBuildInputs = with pkgs; [ jq xorg.lndir ];
+  passAsFile = [ "config" ];
+} ''
+  mkdir $out
+  lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
+
+  rm $out/static/config.json
+  jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
+    >$out/static/config.json
+'';
+```
+
+## Federation policies {#modules-services-akkoma-federation-policies}
+
+Akkoma comes with a number of modules to police federation with other ActivityPub instances.
+The most valuable for typical users is the
+[`:mrf_simple`](https://docs.akkoma.dev/stable/configuration/cheatsheet/#mrf_simple) module
+which allows limiting federation based on instance hostnames.
+
+This configuration snippet provides an example on how these can be used. Choosing an adequate
+federation policy is not trivial and entails finding a balance between connectivity to the rest
+of the fediverse and providing a pleasant experience to the users of an instance.
+
+
+```nix
+services.akkoma.config.":pleroma" = with (pkgs.formats.elixirConf { }).lib; {
+  ":mrf".policies = map mkRaw [
+    "Pleroma.Web.ActivityPub.MRF.SimplePolicy"
+  ];
+
+  ":mrf_simple" = {
+    # Tag all media as sensitive
+    media_nsfw = mkMap {
+      "nsfw.weird.kinky" = "Untagged NSFW content";
+    };
+
+    # Reject all activities except deletes
+    reject = mkMap {
+      "kiwifarms.cc" = "Persistent harassment of users, no moderation";
+    };
+
+    # Force posts to be visible by followers only
+    followers_only = mkMap {
+      "beta.birdsite.live" = "Avoid polluting timelines with Twitter posts";
+    };
+  };
+};
+```
+
+## Upload filters {#modules-services-akkoma-upload-filters}
+
+This example strips GPS and location metadata from uploads, deduplicates them and anonymises the
+the file name.
+
+```nix
+services.akkoma.config.":pleroma"."Pleroma.Upload".filters =
+  map (pkgs.formats.elixirConf { }).lib.mkRaw [
+    "Pleroma.Upload.Filter.Exiftool"
+    "Pleroma.Upload.Filter.Dedupe"
+    "Pleroma.Upload.Filter.AnonymizeFilename"
+  ];
+```
+
+## Migration from Pleroma {#modules-services-akkoma-migration-pleroma}
+
+Pleroma instances can be migrated to Akkoma either by copying the database and upload data or by
+pointing Akkoma to the existing data. The necessary database migrations are run automatically
+during startup of the service.
+
+The configuration has to be copy‐edited manually.
+
+Depending on the size of the database, the initial migration may take a long time and exceed the
+startup timeout of the system manager. To work around this issue one may adjust the startup timeout
+{option}`systemd.services.akkoma.serviceConfig.TimeoutStartSec` or simply run the migrations
+manually:
+
+```ShellSession
+pleroma_ctl migrate
+```
+
+### Copying data {#modules-services-akkoma-migration-pleroma-copy}
+
+Copying the Pleroma data instead of re‐using it in place may permit easier reversion to Pleroma,
+but allows the two data sets to diverge.
+
+First disable Pleroma and then copy its database and upload data:
+
+```ShellSession
+# Create a copy of the database
+nix-shell -p postgresql --run 'createdb -T pleroma akkoma'
+
+# Copy upload data
+mkdir /var/lib/akkoma
+cp -R --reflink=auto /var/lib/pleroma/uploads /var/lib/akkoma/
+```
+
+After the data has been copied, enable the Akkoma service and verify that the migration has been
+successful. If no longer required, the original data may then be deleted:
+
+```ShellSession
+# Delete original database
+nix-shell -p postgresql --run 'dropdb pleroma'
+
+# Delete original Pleroma state
+rm -r /var/lib/pleroma
+```
+
+### Re‐using data {#modules-services-akkoma-migration-pleroma-reuse}
+
+To re‐use the Pleroma data in place, disable Pleroma and enable Akkoma, pointing it to the
+Pleroma database and upload directory.
+
+```nix
+# Adjust these settings according to the database name and upload directory path used by Pleroma
+services.akkoma.config.":pleroma"."Pleroma.Repo".database = "pleroma";
+services.akkoma.config.":pleroma".":instance".upload_dir = "/var/lib/pleroma/uploads";
+```
+
+Please keep in mind that after the Akkoma service has been started, any migrations applied by
+Akkoma have to be rolled back before the database can be used again with Pleroma. This can be
+achieved through `pleroma_ctl ecto.rollback`. Refer to the
+[Ecto SQL documentation](https://hexdocs.pm/ecto_sql/Mix.Tasks.Ecto.Rollback.html) for
+details.
+
+## Advanced deployment options {#modules-services-akkoma-advanced-deployment}
+
+### Confinement {#modules-services-akkoma-confinement}
+
+The Akkoma systemd service may be confined to a chroot with
+
+```nix
+services.systemd.akkoma.confinement.enable = true;
+```
+
+Confinement of services is not generally supported in NixOS and therefore disabled by default.
+Depending on the Akkoma configuration, the default confinement settings may be insufficient and
+lead to subtle errors at run time, requiring adjustment:
+
+Use
+[{option}`services.systemd.akkoma.confinement.packages`](options.html#opt-systemd.services._name_.confinement.packages)
+to make packages available in the chroot.
+
+{option}`services.systemd.akkoma.serviceConfig.BindPaths` and
+{option}`services.systemd.akkoma.serviceConfig.BindReadOnlyPaths` permit access to outside paths
+through bind mounts. Refer to
+[`BindPaths=`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths=)
+of {manpage}`systemd.exec(5)` for details.
+
+### Distributed deployment {#modules-services-akkoma-distributed-deployment}
+
+Being an Elixir application, Akkoma can be deployed in a distributed fashion.
+
+This requires setting
+[{option}`services.akkoma.dist.address`](options.html#opt-services.akkoma.dist.address) and
+[{option}`services.akkoma.dist.cookie`](options.html#opt-services.akkoma.dist.cookie). The
+specifics depend strongly on the deployment environment. For more information please check the
+relevant [Erlang documentation](https://www.erlang.org/doc/reference_manual/distributed.html).
diff --git a/nixpkgs/nixos/modules/services/web-apps/akkoma.nix b/nixpkgs/nixos/modules/services/web-apps/akkoma.nix
new file mode 100644
index 000000000000..8d1775258612
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/akkoma.nix
@@ -0,0 +1,1086 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.akkoma;
+  ex = cfg.config;
+  db = ex.":pleroma"."Pleroma.Repo";
+  web = ex.":pleroma"."Pleroma.Web.Endpoint";
+
+  isConfined = config.systemd.services.akkoma.confinement.enable;
+  hasSmtp = (attrByPath [ ":pleroma" "Pleroma.Emails.Mailer" "adapter" "value" ] null ex) == "Swoosh.Adapters.SMTP";
+
+  isAbsolutePath = v: isString v && substring 0 1 v == "/";
+  isSecret = v: isAttrs v && v ? _secret && isAbsolutePath v._secret;
+
+  absolutePath = with types; mkOptionType {
+    name = "absolutePath";
+    description = "absolute path";
+    descriptionClass = "noun";
+    check = isAbsolutePath;
+    inherit (str) merge;
+  };
+
+  secret = mkOptionType {
+    name = "secret";
+    description = "secret value";
+    descriptionClass = "noun";
+    check = isSecret;
+    nestedTypes = {
+      _secret = absolutePath;
+    };
+  };
+
+  ipAddress = with types; mkOptionType {
+    name = "ipAddress";
+    description = "IPv4 or IPv6 address";
+    descriptionClass = "conjunction";
+    check = x: str.check x && builtins.match "[.0-9:A-Fa-f]+" x != null;
+    inherit (str) merge;
+  };
+
+  elixirValue = let
+    elixirValue' = with types;
+      nullOr (oneOf [ bool int float str (attrsOf elixirValue') (listOf elixirValue') ]) // {
+        description = "Elixir value";
+      };
+  in elixirValue';
+
+  frontend = {
+    options = {
+      package = mkOption {
+        type = types.package;
+        description = mdDoc "Akkoma frontend package.";
+        example = literalExpression "pkgs.akkoma-frontends.akkoma-fe";
+      };
+
+      name = mkOption {
+        type = types.nonEmptyStr;
+        description = mdDoc "Akkoma frontend name.";
+        example = "akkoma-fe";
+      };
+
+      ref = mkOption {
+        type = types.nonEmptyStr;
+        description = mdDoc "Akkoma frontend reference.";
+        example = "stable";
+      };
+    };
+  };
+
+  sha256 = builtins.hashString "sha256";
+
+  replaceSec = let
+    replaceSec' = { }@args: v:
+      if isAttrs v
+        then if v ? _secret
+          then if isAbsolutePath v._secret
+            then sha256 v._secret
+            else abort "Invalid secret path (_secret = ${v._secret})"
+          else mapAttrs (_: val: replaceSec' args val) v
+        else if isList v
+          then map (replaceSec' args) v
+          else v;
+    in replaceSec' { };
+
+  # Erlang/Elixir uses a somewhat special format for IP addresses
+  erlAddr = addr: fileContents
+    (pkgs.runCommand addr {
+      nativeBuildInputs = with pkgs; [ elixir ];
+      code = ''
+        case :inet.parse_address('${addr}') do
+          {:ok, addr} -> IO.inspect addr
+          {:error, _} -> System.halt(65)
+        end
+      '';
+      passAsFile = [ "code" ];
+    } ''elixir "$codePath" >"$out"'');
+
+  format = pkgs.formats.elixirConf { };
+  configFile = format.generate "config.exs"
+    (replaceSec
+      (attrsets.updateManyAttrsByPath [{
+        path = [ ":pleroma" "Pleroma.Web.Endpoint" "http" "ip" ];
+        update = addr:
+          if isAbsolutePath addr
+            then format.lib.mkTuple
+              [ (format.lib.mkAtom ":local") addr ]
+            else format.lib.mkRaw (erlAddr addr);
+      }] cfg.config));
+
+  writeShell = { name, text, runtimeInputs ? [ ] }:
+    pkgs.writeShellApplication { inherit name text runtimeInputs; } + "/bin/${name}";
+
+  genScript = writeShell {
+    name = "akkoma-gen-cookie";
+    runtimeInputs = with pkgs; [ coreutils util-linux ];
+    text = ''
+      install -m 0400 \
+        -o ${escapeShellArg cfg.user } \
+        -g ${escapeShellArg cfg.group} \
+        <(hexdump -n 16 -e '"%02x"' /dev/urandom) \
+        "$RUNTIME_DIRECTORY/cookie"
+    '';
+  };
+
+  copyScript = writeShell {
+    name = "akkoma-copy-cookie";
+    runtimeInputs = with pkgs; [ coreutils ];
+    text = ''
+      install -m 0400 \
+        -o ${escapeShellArg cfg.user} \
+        -g ${escapeShellArg cfg.group} \
+        ${escapeShellArg cfg.dist.cookie._secret} \
+        "$RUNTIME_DIRECTORY/cookie"
+    '';
+  };
+
+  secretPaths = catAttrs "_secret" (collect isSecret cfg.config);
+
+  vapidKeygen = pkgs.writeText "vapidKeygen.exs" ''
+    [public_path, private_path] = System.argv()
+    {public_key, private_key} = :crypto.generate_key :ecdh, :prime256v1
+    File.write! public_path, Base.url_encode64(public_key, padding: false)
+    File.write! private_path, Base.url_encode64(private_key, padding: false)
+  '';
+
+  initSecretsScript = writeShell {
+    name = "akkoma-init-secrets";
+    runtimeInputs = with pkgs; [ coreutils elixir ];
+    text = let
+      key-base = web.secret_key_base;
+      jwt-signer = ex.":joken".":default_signer";
+      signing-salt = web.signing_salt;
+      liveview-salt = web.live_view.signing_salt;
+      vapid-private = ex.":web_push_encryption".":vapid_details".private_key;
+      vapid-public = ex.":web_push_encryption".":vapid_details".public_key;
+    in ''
+      secret() {
+        # Generate default secret if non‐existent
+        test -e "$2" || install -D -m 0600 <(tr -dc 'A-Za-z-._~' </dev/urandom | head -c "$1") "$2"
+        if [ "$(stat --dereference --format='%s' "$2")" -lt "$1" ]; then
+          echo "Secret '$2' is smaller than minimum size of $1 bytes." >&2
+          exit 65
+        fi
+      }
+
+      secret 64 ${escapeShellArg key-base._secret}
+      secret 64 ${escapeShellArg jwt-signer._secret}
+      secret 8 ${escapeShellArg signing-salt._secret}
+      secret 8 ${escapeShellArg liveview-salt._secret}
+
+      ${optionalString (isSecret vapid-public) ''
+        { test -e ${escapeShellArg vapid-private._secret} && \
+          test -e ${escapeShellArg vapid-public._secret}; } || \
+            elixir ${escapeShellArgs [ vapidKeygen vapid-public._secret vapid-private._secret ]}
+      ''}
+    '';
+  };
+
+  configScript = writeShell {
+    name = "akkoma-config";
+    runtimeInputs = with pkgs; [ coreutils replace-secret ];
+    text = ''
+      cd "$RUNTIME_DIRECTORY"
+      tmp="$(mktemp config.exs.XXXXXXXXXX)"
+      trap 'rm -f "$tmp"' EXIT TERM
+
+      cat ${escapeShellArg configFile} >"$tmp"
+      ${concatMapStrings (file: ''
+        replace-secret ${escapeShellArgs [ (sha256 file) file ]} "$tmp"
+      '') secretPaths}
+
+      chown ${escapeShellArg cfg.user}:${escapeShellArg cfg.group} "$tmp"
+      chmod 0400 "$tmp"
+      mv -f "$tmp" config.exs
+    '';
+  };
+
+  pgpass = let
+    esc = escape [ ":" ''\'' ];
+  in if (cfg.initDb.password != null)
+    then pkgs.writeText "pgpass.conf" ''
+      *:*:*${esc cfg.initDb.username}:${esc (sha256 cfg.initDb.password._secret)}
+    ''
+    else null;
+
+  escapeSqlId = x: ''"${replaceStrings [ ''"'' ] [ ''""'' ] x}"'';
+  escapeSqlStr = x: "'${replaceStrings [ "'" ] [ "''" ] x}'";
+
+  setupSql = pkgs.writeText "setup.psql" ''
+    \set ON_ERROR_STOP on
+
+    ALTER ROLE ${escapeSqlId db.username}
+      LOGIN PASSWORD ${if db ? password
+        then "${escapeSqlStr (sha256 db.password._secret)}"
+        else "NULL"};
+
+    ALTER DATABASE ${escapeSqlId db.database}
+      OWNER TO ${escapeSqlId db.username};
+
+    \connect ${escapeSqlId db.database}
+    CREATE EXTENSION IF NOT EXISTS citext;
+    CREATE EXTENSION IF NOT EXISTS pg_trgm;
+    CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+  '';
+
+  dbHost = if db ? socket_dir then db.socket_dir
+    else if db ? socket then db.socket
+      else if db ? hostname then db.hostname
+        else null;
+
+  initDbScript = writeShell {
+    name = "akkoma-initdb";
+    runtimeInputs = with pkgs; [ coreutils replace-secret config.services.postgresql.package ];
+    text = ''
+      pgpass="$(mktemp -t pgpass-XXXXXXXXXX.conf)"
+      setupSql="$(mktemp -t setup-XXXXXXXXXX.psql)"
+      trap 'rm -f "$pgpass $setupSql"' EXIT TERM
+
+      ${optionalString (dbHost != null) ''
+        export PGHOST=${escapeShellArg dbHost}
+      ''}
+      export PGUSER=${escapeShellArg cfg.initDb.username}
+      ${optionalString (pgpass != null) ''
+        cat ${escapeShellArg pgpass} >"$pgpass"
+        replace-secret ${escapeShellArgs [
+          (sha256 cfg.initDb.password._secret) cfg.initDb.password._secret ]} "$pgpass"
+        export PGPASSFILE="$pgpass"
+      ''}
+
+      cat ${escapeShellArg setupSql} >"$setupSql"
+      ${optionalString (db ? password) ''
+        replace-secret ${escapeShellArgs [
+         (sha256 db.password._secret) db.password._secret ]} "$setupSql"
+      ''}
+
+      # Create role if non‐existent
+      psql -tAc "SELECT 1 FROM pg_roles
+        WHERE rolname = "${escapeShellArg (escapeSqlStr db.username)} | grep -F -q 1 || \
+        psql -tAc "CREATE ROLE "${escapeShellArg (escapeSqlId db.username)}
+
+      # Create database if non‐existent
+      psql -tAc "SELECT 1 FROM pg_database
+        WHERE datname = "${escapeShellArg (escapeSqlStr db.database)} | grep -F -q 1 || \
+        psql -tAc "CREATE DATABASE "${escapeShellArg (escapeSqlId db.database)}"
+          OWNER "${escapeShellArg (escapeSqlId db.username)}"
+          TEMPLATE template0
+          ENCODING 'utf8'
+          LOCALE 'C'"
+
+      psql -f "$setupSql"
+    '';
+  };
+
+  envWrapper = let
+    script = writeShell {
+      name = "akkoma-env";
+      text = ''
+        cd "${cfg.package}"
+
+        RUNTIME_DIRECTORY="''${RUNTIME_DIRECTORY:-/run/akkoma}"
+        AKKOMA_CONFIG_PATH="$RUNTIME_DIRECTORY/config.exs" \
+        ERL_EPMD_ADDRESS="${cfg.dist.address}" \
+        ERL_EPMD_PORT="${toString cfg.dist.epmdPort}" \
+        ERL_FLAGS="${concatStringsSep " " [
+          "-kernel inet_dist_use_interface '${erlAddr cfg.dist.address}'"
+          "-kernel inet_dist_listen_min ${toString cfg.dist.portMin}"
+          "-kernel inet_dist_listen_max ${toString cfg.dist.portMax}"
+        ]}" \
+        RELEASE_COOKIE="$(<"$RUNTIME_DIRECTORY/cookie")" \
+        RELEASE_NAME="akkoma" \
+          exec "${cfg.package}/bin/$(basename "$0")" "$@"
+      '';
+    };
+  in pkgs.runCommandLocal "akkoma-env" { } ''
+    mkdir -p "$out/bin"
+
+    ln -r -s ${escapeShellArg script} "$out/bin/pleroma"
+    ln -r -s ${escapeShellArg script} "$out/bin/pleroma_ctl"
+  '';
+
+  userWrapper = pkgs.writeShellApplication {
+    name = "pleroma_ctl";
+    text = ''
+      if [ "''${1-}" == "update" ]; then
+        echo "OTP releases are not supported on NixOS." >&2
+        exit 64
+      fi
+
+      exec sudo -u ${escapeShellArg cfg.user} \
+        "${envWrapper}/bin/pleroma_ctl" "$@"
+    '';
+  };
+
+  socketScript = if isAbsolutePath web.http.ip
+    then writeShell {
+      name = "akkoma-socket";
+      runtimeInputs = with pkgs; [ coreutils inotify-tools ];
+      text = ''
+        coproc {
+          inotifywait -q -m -e create ${escapeShellArg (dirOf web.http.ip)}
+        }
+
+        trap 'kill "$COPROC_PID"' EXIT TERM
+
+        until test -S ${escapeShellArg web.http.ip}
+          do read -r -u "''${COPROC[0]}"
+        done
+
+        chmod 0666 ${escapeShellArg web.http.ip}
+      '';
+    }
+    else null;
+
+  staticDir = ex.":pleroma".":instance".static_dir;
+  uploadDir = ex.":pleroma".":instance".upload_dir;
+
+  staticFiles = pkgs.runCommandLocal "akkoma-static" { } ''
+    ${concatStringsSep "\n" (mapAttrsToList (key: val: ''
+      mkdir -p $out/frontends/${escapeShellArg val.name}/
+      ln -s ${escapeShellArg val.package} $out/frontends/${escapeShellArg val.name}/${escapeShellArg val.ref}
+    '') cfg.frontends)}
+
+    ${optionalString (cfg.extraStatic != null)
+      (concatStringsSep "\n" (mapAttrsToList (key: val: ''
+        mkdir -p "$out/$(dirname ${escapeShellArg key})"
+        ln -s ${escapeShellArg val} $out/${escapeShellArg key}
+      '') cfg.extraStatic))}
+  '';
+in {
+  options = {
+    services.akkoma = {
+      enable = mkEnableOption (mdDoc "Akkoma");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.akkoma;
+        defaultText = literalExpression "pkgs.akkoma";
+        description = mdDoc "Akkoma package to use.";
+      };
+
+      user = mkOption {
+        type = types.nonEmptyStr;
+        default = "akkoma";
+        description = mdDoc "User account under which Akkoma runs.";
+      };
+
+      group = mkOption {
+        type = types.nonEmptyStr;
+        default = "akkoma";
+        description = mdDoc "Group account under which Akkoma runs.";
+      };
+
+      initDb = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = mdDoc ''
+            Whether to automatically initialise the database on startup. This will create a
+            database role and database if they do not already exist, and (re)set the role password
+            and the ownership of the database.
+
+            This setting can be used safely even if the database already exists and contains data.
+
+            The database settings are configured through
+            [{option}`config.services.akkoma.config.":pleroma"."Pleroma.Repo"`](#opt-services.akkoma.config.__pleroma_._Pleroma.Repo_).
+
+            If disabled, the database has to be set up manually:
+
+            ```SQL
+            CREATE ROLE akkoma LOGIN;
+
+            CREATE DATABASE akkoma
+              OWNER akkoma
+              TEMPLATE template0
+              ENCODING 'utf8'
+              LOCALE 'C';
+
+            \connect akkoma
+            CREATE EXTENSION IF NOT EXISTS citext;
+            CREATE EXTENSION IF NOT EXISTS pg_trgm;
+            CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+            ```
+          '';
+        };
+
+        username = mkOption {
+          type = types.nonEmptyStr;
+          default = config.services.postgresql.superUser;
+          defaultText = literalExpression "config.services.postgresql.superUser";
+          description = mdDoc ''
+            Name of the database user to initialise the database with.
+
+            This user is required to have the `CREATEROLE` and `CREATEDB` capabilities.
+          '';
+        };
+
+        password = mkOption {
+          type = types.nullOr secret;
+          default = null;
+          description = mdDoc ''
+            Password of the database user to initialise the database with.
+
+            If set to `null`, no password will be used.
+
+            The attribute `_secret` should point to a file containing the secret.
+          '';
+        };
+      };
+
+      initSecrets = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc ''
+          Whether to initialise non‐existent secrets with random values.
+
+          If enabled, appropriate secrets for the following options will be created automatically
+          if the files referenced in the `_secrets` attribute do not exist during startup.
+
+          - {option}`config.":pleroma"."Pleroma.Web.Endpoint".secret_key_base`
+          - {option}`config.":pleroma"."Pleroma.Web.Endpoint".signing_salt`
+          - {option}`config.":pleroma"."Pleroma.Web.Endpoint".live_view.signing_salt`
+          - {option}`config.":web_push_encryption".":vapid_details".private_key`
+          - {option}`config.":web_push_encryption".":vapid_details".public_key`
+          - {option}`config.":joken".":default_signer"`
+        '';
+      };
+
+      installWrapper = mkOption {
+        type = types.bool;
+        default = true;
+        description = mdDoc ''
+          Whether to install a wrapper around `pleroma_ctl` to simplify administration of the
+          Akkoma instance.
+        '';
+      };
+
+      extraPackages = mkOption {
+        type = with types; listOf package;
+        default = with pkgs; [ exiftool ffmpeg_5-headless graphicsmagick-imagemagick-compat ];
+        defaultText = literalExpression "with pkgs; [ exiftool graphicsmagick-imagemagick-compat ffmpeg_5-headless ]";
+        example = literalExpression "with pkgs; [ exiftool imagemagick ffmpeg_5-full ]";
+        description = mdDoc ''
+          List of extra packages to include in the executable search path of the service unit.
+          These are needed by various configurable components such as:
+
+          - ExifTool for the `Pleroma.Upload.Filter.Exiftool` upload filter,
+          - ImageMagick for still image previews in the media proxy as well as for the
+            `Pleroma.Upload.Filters.Mogrify` upload filter, and
+          - ffmpeg for video previews in the media proxy.
+        '';
+      };
+
+      frontends = mkOption {
+        description = mdDoc "Akkoma frontends.";
+        type = with types; attrsOf (submodule frontend);
+        default = {
+          primary = {
+            package = pkgs.akkoma-frontends.akkoma-fe;
+            name = "akkoma-fe";
+            ref = "stable";
+          };
+          admin = {
+            package = pkgs.akkoma-frontends.admin-fe;
+            name = "admin-fe";
+            ref = "stable";
+          };
+        };
+        defaultText = literalExpression ''
+          {
+            primary = {
+              package = pkgs.akkoma-frontends.akkoma-fe;
+              name = "akkoma-fe";
+              ref = "stable";
+            };
+            admin = {
+              package = pkgs.akkoma-frontends.admin-fe;
+              name = "admin-fe";
+              ref = "stable";
+            };
+          }
+        '';
+      };
+
+      extraStatic = mkOption {
+        type = with types; nullOr (attrsOf package);
+        description = mdDoc ''
+          Attribute set of extra packages to add to the static files directory.
+
+          Do not add frontends here. These should be configured through
+          [{option}`services.akkoma.frontends`](#opt-services.akkoma.frontends).
+        '';
+        default = null;
+        example = literalExpression ''
+          {
+            "emoji/blobs.gg" = pkgs.akkoma-emoji.blobs_gg;
+            "static/terms-of-service.html" = pkgs.writeText "terms-of-service.html" '''
+              …
+            ''';
+            "favicon.png" = let
+              rev = "697a8211b0f427a921e7935a35d14bb3e32d0a2c";
+            in pkgs.stdenvNoCC.mkDerivation {
+              name = "favicon.png";
+
+              src = pkgs.fetchurl {
+                url = "https://raw.githubusercontent.com/TilCreator/NixOwO/''${rev}/NixOwO_plain.svg";
+                hash = "sha256-tWhHMfJ3Od58N9H5yOKPMfM56hYWSOnr/TGCBi8bo9E=";
+              };
+
+              nativeBuildInputs = with pkgs; [ librsvg ];
+
+              dontUnpack = true;
+              installPhase = '''
+                rsvg-convert -o $out -w 96 -h 96 $src
+              ''';
+            };
+          }
+        '';
+      };
+
+      dist = {
+        address = mkOption {
+          type = ipAddress;
+          default = "127.0.0.1";
+          description = mdDoc ''
+            Listen address for Erlang distribution protocol and Port Mapper Daemon (epmd).
+          '';
+        };
+
+        epmdPort = mkOption {
+          type = types.port;
+          default = 4369;
+          description = mdDoc "TCP port to bind Erlang Port Mapper Daemon to.";
+        };
+
+        portMin = mkOption {
+          type = types.port;
+          default = 49152;
+          description = mdDoc "Lower bound for Erlang distribution protocol TCP port.";
+        };
+
+        portMax = mkOption {
+          type = types.port;
+          default = 65535;
+          description = mdDoc "Upper bound for Erlang distribution protocol TCP port.";
+        };
+
+        cookie = mkOption {
+          type = types.nullOr secret;
+          default = null;
+          example = { _secret = "/var/lib/secrets/akkoma/releaseCookie"; };
+          description = mdDoc ''
+            Erlang release cookie.
+
+            If set to `null`, a temporary random cookie will be generated.
+          '';
+        };
+      };
+
+      config = mkOption {
+        description = mdDoc ''
+          Configuration for Akkoma. The attributes are serialised to Elixir DSL.
+
+          Refer to <https://docs.akkoma.dev/stable/configuration/cheatsheet/> for
+          configuration options.
+
+          Settings containing secret data should be set to an attribute set containing the
+          attribute `_secret` - a string pointing to a file containing the value the option
+          should be set to.
+        '';
+        type = types.submodule {
+          freeformType = format.type;
+          options = {
+            ":pleroma" = {
+              ":instance" = {
+                name = mkOption {
+                  type = types.nonEmptyStr;
+                  description = mdDoc "Instance name.";
+                };
+
+                email = mkOption {
+                  type = types.nonEmptyStr;
+                  description = mdDoc "Instance administrator email.";
+                };
+
+                description = mkOption {
+                  type = types.nonEmptyStr;
+                  description = mdDoc "Instance description.";
+                };
+
+                static_dir = mkOption {
+                  type = types.path;
+                  default = toString staticFiles;
+                  defaultText = literalMD ''
+                    Derivation gathering the following paths into a directory:
+
+                    - [{option}`services.akkoma.frontends`](#opt-services.akkoma.frontends)
+                    - [{option}`services.akkoma.extraStatic`](#opt-services.akkoma.extraStatic)
+                  '';
+                  description = mdDoc ''
+                    Directory of static files.
+
+                    This directory can be built using a derivation, or it can be managed as mutable
+                    state by setting the option to an absolute path.
+                  '';
+                };
+
+                upload_dir = mkOption {
+                  type = absolutePath;
+                  default = "/var/lib/akkoma/uploads";
+                  description = mdDoc ''
+                    Directory where Akkoma will put uploaded files.
+                  '';
+                };
+              };
+
+              "Pleroma.Repo" = mkOption {
+                type = elixirValue;
+                default = {
+                  adapter = format.lib.mkRaw "Ecto.Adapters.Postgres";
+                  socket_dir = "/run/postgresql";
+                  username = cfg.user;
+                  database = "akkoma";
+                };
+                defaultText = literalExpression ''
+                  {
+                    adapter = (pkgs.formats.elixirConf { }).lib.mkRaw "Ecto.Adapters.Postgres";
+                    socket_dir = "/run/postgresql";
+                    username = config.services.akkoma.user;
+                    database = "akkoma";
+                  }
+                '';
+                description = mdDoc ''
+                  Database configuration.
+
+                  Refer to
+                  <https://hexdocs.pm/ecto_sql/Ecto.Adapters.Postgres.html#module-connection-options>
+                  for options.
+                '';
+              };
+
+              "Pleroma.Web.Endpoint" = {
+                url = {
+                  host = mkOption {
+                    type = types.nonEmptyStr;
+                    default = config.networking.fqdn;
+                    defaultText = literalExpression "config.networking.fqdn";
+                    description = mdDoc "Domain name of the instance.";
+                  };
+
+                  scheme = mkOption {
+                    type = types.nonEmptyStr;
+                    default = "https";
+                    description = mdDoc "URL scheme.";
+                  };
+
+                  port = mkOption {
+                    type = types.port;
+                    default = 443;
+                    description = mdDoc "External port number.";
+                  };
+                };
+
+                http = {
+                  ip = mkOption {
+                    type = types.either absolutePath ipAddress;
+                    default = "/run/akkoma/socket";
+                    example = "::1";
+                    description = mdDoc ''
+                      Listener IP address or Unix socket path.
+
+                      The value is automatically converted to Elixir’s internal address
+                      representation during serialisation.
+                    '';
+                  };
+
+                  port = mkOption {
+                    type = types.port;
+                    default = if isAbsolutePath web.http.ip then 0 else 4000;
+                    defaultText = literalExpression ''
+                      if isAbsolutePath config.services.akkoma.config.:pleroma"."Pleroma.Web.Endpoint".http.ip
+                        then 0
+                        else 4000;
+                    '';
+                    description = mdDoc ''
+                      Listener port number.
+
+                      Must be 0 if using a Unix socket.
+                    '';
+                  };
+                };
+
+                secret_key_base = mkOption {
+                  type = secret;
+                  default = { _secret = "/var/lib/secrets/akkoma/key-base"; };
+                  description = mdDoc ''
+                    Secret key used as a base to generate further secrets for encrypting and
+                    signing data.
+
+                    The attribute `_secret` should point to a file containing the secret.
+
+                    This key can generated can be generated as follows:
+
+                    ```ShellSession
+                    $ tr -dc 'A-Za-z-._~' </dev/urandom | head -c 64
+                    ```
+                  '';
+                };
+
+                live_view = {
+                  signing_salt = mkOption {
+                    type = secret;
+                    default = { _secret = "/var/lib/secrets/akkoma/liveview-salt"; };
+                    description = mdDoc ''
+                      LiveView signing salt.
+
+                      The attribute `_secret` should point to a file containing the secret.
+
+                      This salt can be generated as follows:
+
+                      ```ShellSession
+                      $ tr -dc 'A-Za-z0-9-._~' </dev/urandom | head -c 8
+                      ```
+                    '';
+                  };
+                };
+
+                signing_salt = mkOption {
+                  type = secret;
+                  default = { _secret = "/var/lib/secrets/akkoma/signing-salt"; };
+                  description = mdDoc ''
+                    Signing salt.
+
+                    The attribute `_secret` should point to a file containing the secret.
+
+                    This salt can be generated as follows:
+
+                    ```ShellSession
+                    $ tr -dc 'A-Za-z0-9-._~' </dev/urandom | head -c 8
+                    ```
+                  '';
+                };
+              };
+
+              ":frontends" = mkOption {
+                type = elixirValue;
+                default = mapAttrs
+                  (key: val: format.lib.mkMap { name = val.name; ref = val.ref; })
+                  cfg.frontends;
+                defaultText = literalExpression ''
+                  lib.mapAttrs (key: val:
+                    (pkgs.formats.elixirConf { }).lib.mkMap { name = val.name; ref = val.ref; })
+                    config.services.akkoma.frontends;
+                '';
+                description = mdDoc ''
+                  Frontend configuration.
+
+                  Users should rely on the default value and prefer to configure frontends through
+                  [{option}`config.services.akkoma.frontends`](#opt-services.akkoma.frontends).
+                '';
+              };
+            };
+
+            ":web_push_encryption" = mkOption {
+              default = { };
+              description = mdDoc ''
+                Web Push Notifications configuration.
+
+                The necessary key pair can be generated as follows:
+
+                ```ShellSession
+                $ nix-shell -p nodejs --run 'npx web-push generate-vapid-keys'
+                ```
+              '';
+              type = types.submodule {
+                freeformType = elixirValue;
+                options = {
+                  ":vapid_details" = {
+                    subject = mkOption {
+                      type = types.nonEmptyStr;
+                      default = "mailto:${ex.":pleroma".":instance".email}";
+                      defaultText = literalExpression ''
+                        "mailto:''${config.services.akkoma.config.":pleroma".":instance".email}"
+                      '';
+                      description = mdDoc "mailto URI for administrative contact.";
+                    };
+
+                    public_key = mkOption {
+                      type = with types; either nonEmptyStr secret;
+                      default = { _secret = "/var/lib/secrets/akkoma/vapid-public"; };
+                      description = mdDoc "base64-encoded public ECDH key.";
+                    };
+
+                    private_key = mkOption {
+                      type = secret;
+                      default = { _secret = "/var/lib/secrets/akkoma/vapid-private"; };
+                      description = mdDoc ''
+                        base64-encoded private ECDH key.
+
+                        The attribute `_secret` should point to a file containing the secret.
+                      '';
+                    };
+                  };
+                };
+              };
+            };
+
+            ":joken" = {
+              ":default_signer" = mkOption {
+                type = secret;
+                default = { _secret = "/var/lib/secrets/akkoma/jwt-signer"; };
+                description = mdDoc ''
+                  JWT signing secret.
+
+                  The attribute `_secret` should point to a file containing the secret.
+
+                  This secret can be generated as follows:
+
+                  ```ShellSession
+                  $ tr -dc 'A-Za-z0-9-._~' </dev/urandom | head -c 64
+                  ```
+                '';
+              };
+            };
+
+            ":logger" = {
+              ":backends" = mkOption {
+                type = types.listOf elixirValue;
+                visible = false;
+                default = with format.lib; [
+                  (mkTuple [ (mkRaw "ExSyslogger") (mkAtom ":ex_syslogger") ])
+                ];
+              };
+
+              ":ex_syslogger" = {
+                ident = mkOption {
+                  type = types.str;
+                  visible = false;
+                  default = "akkoma";
+                };
+
+                level = mkOption {
+                  type = types.nonEmptyStr;
+                  apply = format.lib.mkAtom;
+                  default = ":info";
+                  example = ":warning";
+                  description = mdDoc ''
+                    Log level.
+
+                    Refer to
+                    <https://hexdocs.pm/logger/Logger.html#module-levels>
+                    for options.
+                  '';
+                };
+              };
+            };
+
+            ":tzdata" = {
+              ":data_dir" = mkOption {
+                type = elixirValue;
+                internal = true;
+                default = format.lib.mkRaw ''
+                  Path.join(System.fetch_env!("CACHE_DIRECTORY"), "tzdata")
+                '';
+              };
+            };
+          };
+        };
+      };
+
+      nginx = mkOption {
+        type = with types; nullOr (submodule
+          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }));
+        default = null;
+        description = mdDoc ''
+          Extra configuration for the nginx virtual host of Akkoma.
+
+          If set to `null`, no virtual host will be added to the nginx configuration.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    warnings = optionals (!config.security.sudo.enable) [''
+      The pleroma_ctl wrapper enabled by the installWrapper option relies on
+      sudo, which appears to have been disabled through security.sudo.enable.
+    ''];
+
+    users = {
+      users."${cfg.user}" = {
+        description = "Akkoma user";
+        group = cfg.group;
+        isSystemUser = true;
+      };
+      groups."${cfg.group}" = { };
+    };
+
+    # Confinement of the main service unit requires separation of the
+    # configuration generation into a separate unit to permit access to secrets
+    # residing outside of the chroot.
+    systemd.services.akkoma-config = {
+      description = "Akkoma social network configuration";
+      reloadTriggers = [ configFile ] ++ secretPaths;
+
+      unitConfig.PropagatesReloadTo = [ "akkoma.service" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        UMask = "0077";
+
+        RuntimeDirectory = "akkoma";
+
+        ExecStart = mkMerge [
+          (mkIf (cfg.dist.cookie == null) [ genScript ])
+          (mkIf (cfg.dist.cookie != null) [ copyScript ])
+          (mkIf cfg.initSecrets [ initSecretsScript ])
+          [ configScript ]
+        ];
+
+        ExecReload = mkMerge [
+          (mkIf cfg.initSecrets [ initSecretsScript ])
+          [ configScript ]
+        ];
+      };
+    };
+
+    systemd.services.akkoma-initdb = mkIf cfg.initDb.enable {
+      description = "Akkoma social network database setup";
+      requires = [ "akkoma-config.service" ];
+      requiredBy = [ "akkoma.service" ];
+      after = [ "akkoma-config.service" "postgresql.service" ];
+      before = [ "akkoma.service" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = mkIf (db ? socket_dir || db ? socket)
+          cfg.initDb.username;
+        RemainAfterExit = true;
+        UMask = "0077";
+        ExecStart = initDbScript;
+        PrivateTmp = true;
+      };
+    };
+
+    systemd.services.akkoma = let
+      runtimeInputs = with pkgs; [ coreutils gawk gnused ] ++ cfg.extraPackages;
+    in {
+      description = "Akkoma social network";
+      documentation = [ "https://docs.akkoma.dev/stable/" ];
+
+      # This service depends on network-online.target and is sequenced after
+      # it because it requires access to the Internet to function properly.
+      bindsTo = [ "akkoma-config.service" ];
+      wants = [ "network-online.service" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [
+        "akkoma-config.target"
+        "network.target"
+        "network-online.target"
+        "postgresql.service"
+      ];
+
+      confinement.packages = mkIf isConfined runtimeInputs;
+      path = runtimeInputs;
+
+      serviceConfig = {
+        Type = "exec";
+        User = cfg.user;
+        Group = cfg.group;
+        UMask = "0077";
+
+        # The run‐time directory is preserved as it is managed by the akkoma-config.service unit.
+        RuntimeDirectory = "akkoma";
+        RuntimeDirectoryPreserve = true;
+
+        CacheDirectory = "akkoma";
+
+        BindPaths = [ "${uploadDir}:${uploadDir}:norbind" ];
+        BindReadOnlyPaths = mkMerge [
+          (mkIf (!isStorePath staticDir) [ "${staticDir}:${staticDir}:norbind" ])
+          (mkIf isConfined (mkMerge [
+            [ "/etc/hosts" "/etc/resolv.conf" ]
+            (mkIf (isStorePath staticDir) (map (dir: "${dir}:${dir}:norbind")
+              (splitString "\n" (readFile ((pkgs.closureInfo { rootPaths = staticDir; }) + "/store-paths")))))
+            (mkIf (db ? socket_dir) [ "${db.socket_dir}:${db.socket_dir}:norbind" ])
+            (mkIf (db ? socket) [ "${db.socket}:${db.socket}:norbind" ])
+          ]))
+        ];
+
+        ExecStartPre = "${envWrapper}/bin/pleroma_ctl migrate";
+        ExecStart = "${envWrapper}/bin/pleroma start";
+        ExecStartPost = socketScript;
+        ExecStop = "${envWrapper}/bin/pleroma stop";
+        ExecStopPost = mkIf (isAbsolutePath web.http.ip)
+          "${pkgs.coreutils}/bin/rm -f '${web.http.ip}'";
+
+        ProtectProc = "noaccess";
+        ProcSubset = "pid";
+        ProtectSystem = mkIf (!isConfined) "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+
+        CapabilityBoundingSet = mkIf
+          (any (port: port > 0 && port < 1024)
+            [ web.http.port cfg.dist.epmdPort cfg.dist.portMin ])
+          [ "CAP_NET_BIND_SERVICE" ];
+
+        NoNewPrivileges = true;
+        SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
+        SystemCallArchitectures = "native";
+
+        DeviceAllow = null;
+        DevicePolicy = "closed";
+
+        # SMTP adapter uses dynamic port 0 binding, which is incompatible with bind address filtering
+        SocketBindAllow = mkIf (!hasSmtp) (mkMerge [
+          [ "tcp:${toString cfg.dist.epmdPort}" "tcp:${toString cfg.dist.portMin}-${toString cfg.dist.portMax}" ]
+          (mkIf (web.http.port != 0) [ "tcp:${toString web.http.port}" ])
+        ]);
+        SocketBindDeny = mkIf (!hasSmtp) "any";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${uploadDir}  0700 ${cfg.user} ${cfg.group} - -"
+      "Z ${uploadDir} ~0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    environment.systemPackages = mkIf (cfg.installWrapper) [ userWrapper ];
+
+    services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
+      ${web.url.host} = mkMerge [ cfg.nginx {
+        locations."/" = {
+          proxyPass =
+            if isAbsolutePath web.http.ip
+              then "http://unix:${web.http.ip}"
+              else if hasInfix ":" web.http.ip
+                then "http://[${web.http.ip}]:${toString web.http.port}"
+                else "http://${web.http.ip}:${toString web.http.port}";
+
+          proxyWebsockets = true;
+          recommendedProxySettings = true;
+        };
+      }];
+    };
+  };
+
+  meta.maintainers = with maintainers; [ mvs ];
+  meta.doc = ./akkoma.md;
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/alps.nix b/nixpkgs/nixos/modules/services/web-apps/alps.nix
new file mode 100644
index 000000000000..05fb676102df
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/alps.nix
@@ -0,0 +1,132 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.alps;
+in {
+  options.services.alps = {
+    enable = mkEnableOption (lib.mdDoc "alps");
+
+    port = mkOption {
+      type = types.port;
+      default = 1323;
+      description = lib.mdDoc ''
+        TCP port the service should listen on.
+      '';
+    };
+
+    bindIP = mkOption {
+      default = "[::]";
+      type = types.str;
+      description = lib.mdDoc ''
+        The IP the service should listen on.
+      '';
+    };
+
+    theme = mkOption {
+      type = types.enum [ "alps" "sourcehut" ];
+      default = "sourcehut";
+      description = lib.mdDoc ''
+        The frontend's theme to use.
+      '';
+    };
+
+    imaps = {
+      port = mkOption {
+        type = types.port;
+        default = 993;
+        description = lib.mdDoc ''
+          The IMAPS server port.
+        '';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "[::1]";
+        example = "mail.example.org";
+        description = lib.mdDoc ''
+          The IMAPS server address.
+        '';
+      };
+    };
+
+    smtps = {
+      port = mkOption {
+        type = types.port;
+        default = 465;
+        description = lib.mdDoc ''
+          The SMTPS server port.
+        '';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = cfg.imaps.host;
+        defaultText = "services.alps.imaps.host";
+        example = "mail.example.org";
+        description = lib.mdDoc ''
+          The SMTPS server address.
+        '';
+      };
+    };
+
+    package = mkOption {
+      internal = true;
+      type = types.package;
+      default = pkgs.alps;
+    };
+
+    args = mkOption {
+      internal = true;
+      type = types.listOf types.str;
+      default = [
+        "-addr" "${cfg.bindIP}:${toString cfg.port}"
+        "-theme" "${cfg.theme}"
+        "imaps://${cfg.imaps.host}:${toString cfg.imaps.port}"
+        "smtps://${cfg.smtps.host}:${toString cfg.smtps.port}"
+      ];
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.alps = {
+      description = "alps is a simple and extensible webmail.";
+      documentation = [ "https://git.sr.ht/~migadu/alps" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/alps ${escapeShellArgs cfg.args}";
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SocketBindAllow = cfg.port;
+        SocketBindDeny = "any";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
index 6c5de3fbe4b8..fe98c1777ea0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -29,7 +29,7 @@ in
 {
   options = {
     services.confluence = {
-      enable = mkEnableOption "Atlassian Confluence service";
+      enable = mkEnableOption (lib.mdDoc "Atlassian Confluence service");
 
       user = mkOption {
         type = types.str;
@@ -56,7 +56,7 @@ in
       };
 
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8090;
         description = lib.mdDoc "Port to listen on.";
       };
@@ -69,7 +69,7 @@ in
       };
 
       proxy = {
-        enable = mkEnableOption "proxy support";
+        enable = mkEnableOption (lib.mdDoc "proxy support");
 
         name = mkOption {
           type = types.str;
@@ -78,7 +78,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 443;
           example = 80;
           description = lib.mdDoc "Port used at the proxy";
@@ -93,7 +93,7 @@ in
       };
 
       sso = {
-        enable = mkEnableOption "SSO with Atlassian Crowd";
+        enable = mkEnableOption (lib.mdDoc "SSO with Atlassian Crowd");
 
         crowd = mkOption {
           type = types.str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
index abe3a8bdb225..c8d1eaef31d8 100644
--- a/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -34,7 +34,7 @@ in
 {
   options = {
     services.crowd = {
-      enable = mkEnableOption "Atlassian Crowd service";
+      enable = mkEnableOption (lib.mdDoc "Atlassian Crowd service");
 
       user = mkOption {
         type = types.str;
@@ -61,7 +61,7 @@ in
       };
 
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8092;
         description = lib.mdDoc "Port to listen on.";
       };
@@ -86,7 +86,7 @@ in
       };
 
       proxy = {
-        enable = mkEnableOption "reverse proxy support";
+        enable = mkEnableOption (lib.mdDoc "reverse proxy support");
 
         name = mkOption {
           type = types.str;
@@ -95,7 +95,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 443;
           example = 80;
           description = lib.mdDoc "Port used at the proxy";
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
index 5d62160ffb13..4cc858216944 100644
--- a/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -29,7 +29,7 @@ in
 {
   options = {
     services.jira = {
-      enable = mkEnableOption "Atlassian JIRA service";
+      enable = mkEnableOption (lib.mdDoc "Atlassian JIRA service");
 
       user = mkOption {
         type = types.str;
@@ -56,7 +56,7 @@ in
       };
 
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8091;
         description = lib.mdDoc "Port to listen on.";
       };
@@ -69,7 +69,7 @@ in
       };
 
       proxy = {
-        enable = mkEnableOption "reverse proxy support";
+        enable = mkEnableOption (lib.mdDoc "reverse proxy support");
 
         name = mkOption {
           type = types.str;
@@ -78,7 +78,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 443;
           example = 80;
           description = lib.mdDoc "Port used at the proxy";
@@ -99,7 +99,7 @@ in
       };
 
       sso = {
-        enable = mkEnableOption "SSO with Atlassian Crowd";
+        enable = mkEnableOption (lib.mdDoc "SSO with Atlassian Crowd");
 
         crowd = mkOption {
           type = types.str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/baget.nix b/nixpkgs/nixos/modules/services/web-apps/baget.nix
deleted file mode 100644
index dd70d462d57d..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/baget.nix
+++ /dev/null
@@ -1,170 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.baget;
-
-  defaultConfig = {
-    "PackageDeletionBehavior" = "Unlist";
-    "AllowPackageOverwrites" = false;
-
-    "Database" = {
-      "Type" = "Sqlite";
-      "ConnectionString" = "Data Source=baget.db";
-    };
-
-    "Storage" = {
-      "Type" = "FileSystem";
-      "Path" = "";
-    };
-
-    "Search" = {
-      "Type" = "Database";
-    };
-
-    "Mirror" = {
-      "Enabled" = false;
-      "PackageSource" = "https://api.nuget.org/v3/index.json";
-    };
-
-    "Logging" = {
-      "IncludeScopes" = false;
-      "Debug" = {
-        "LogLevel" = {
-          "Default" = "Warning";
-        };
-      };
-      "Console" = {
-        "LogLevel" = {
-          "Microsoft.Hosting.Lifetime" = "Information";
-          "Default" = "Warning";
-        };
-      };
-    };
-  };
-
-  configAttrs = recursiveUpdate defaultConfig cfg.extraConfig;
-
-  configFormat = pkgs.formats.json {};
-  configFile = configFormat.generate "appsettings.json" configAttrs;
-
-in
-{
-  options.services.baget = {
-    enable = mkEnableOption "BaGet NuGet-compatible server";
-
-    apiKeyFile = mkOption {
-      type = types.path;
-      example = "/root/baget.key";
-      description = lib.mdDoc ''
-        Private API key for BaGet.
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = configFormat.type;
-      default = {};
-      example = {
-        "Database" = {
-          "Type" = "PostgreSql";
-          "ConnectionString" = "Server=/run/postgresql;Port=5432;";
-        };
-      };
-      defaultText = literalExpression ''
-        {
-          "PackageDeletionBehavior" = "Unlist";
-          "AllowPackageOverwrites" = false;
-
-          "Database" = {
-            "Type" = "Sqlite";
-            "ConnectionString" = "Data Source=baget.db";
-          };
-
-          "Storage" = {
-            "Type" = "FileSystem";
-            "Path" = "";
-          };
-
-          "Search" = {
-            "Type" = "Database";
-          };
-
-          "Mirror" = {
-            "Enabled" = false;
-            "PackageSource" = "https://api.nuget.org/v3/index.json";
-          };
-
-          "Logging" = {
-            "IncludeScopes" = false;
-            "Debug" = {
-              "LogLevel" = {
-                "Default" = "Warning";
-              };
-            };
-            "Console" = {
-              "LogLevel" = {
-                "Microsoft.Hosting.Lifetime" = "Information";
-                "Default" = "Warning";
-              };
-            };
-          };
-        }
-      '';
-      description = lib.mdDoc ''
-        Extra configuration options for BaGet. Refer to <https://loic-sharma.github.io/BaGet/configuration/> for details.
-        Default value is merged with values from here.
-      '';
-    };
-  };
-
-  # implementation
-
-  config = mkIf cfg.enable {
-
-    systemd.services.baget = {
-      description = "BaGet server";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network.target" "network-online.target" ];
-      path = [ pkgs.jq ];
-      serviceConfig = {
-        WorkingDirectory = "/var/lib/baget";
-        DynamicUser = true;
-        StateDirectory = "baget";
-        StateDirectoryMode = "0700";
-        LoadCredential = "api_key:${cfg.apiKeyFile}";
-
-        CapabilityBoundingSet = "";
-        NoNewPrivileges = true;
-        PrivateDevices = true;
-        PrivateTmp = true;
-        PrivateUsers = true;
-        PrivateMounts = true;
-        ProtectHome = true;
-        ProtectClock = true;
-        ProtectProc = "noaccess";
-        ProcSubset = "pid";
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        ProtectControlGroups = true;
-        ProtectHostname = true;
-        RestrictSUIDSGID = true;
-        RestrictRealtime = true;
-        RestrictNamespaces = true;
-        LockPersonality = true;
-        RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        SystemCallFilter = [ "@system-service" "~@privileged" ];
-      };
-      script = ''
-        jq --slurpfile apiKeys <(jq -R . "$CREDENTIALS_DIRECTORY/api_key") '.ApiKey = $apiKeys[0]' ${configFile} > appsettings.json
-        ln -snf ${pkgs.baget}/lib/BaGet/wwwroot wwwroot
-        exec ${pkgs.baget}/bin/BaGet
-      '';
-    };
-
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/web-apps/bookstack.nix b/nixpkgs/nixos/modules/services/web-apps/bookstack.nix
index b939adc50fa3..d846c98577c8 100644
--- a/nixpkgs/nixos/modules/services/web-apps/bookstack.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/bookstack.nix
@@ -34,7 +34,7 @@ in {
 
   options.services.bookstack = {
 
-    enable = mkEnableOption "BookStack";
+    enable = mkEnableOption (lib.mdDoc "BookStack");
 
     user = mkOption {
       default = "bookstack";
@@ -60,11 +60,8 @@ in {
 
     hostname = lib.mkOption {
       type = lib.types.str;
-      default = if config.networking.domain != null then
-                  config.networking.fqdn
-                else
-                  config.networking.hostName;
-      defaultText = lib.literalExpression "config.networking.fqdn";
+      default = config.networking.fqdnOrHostName;
+      defaultText = lib.literalExpression "config.networking.fqdnOrHostName";
       example = "bookstack.example.com";
       description = lib.mdDoc ''
         The hostname to serve BookStack on.
@@ -234,7 +231,7 @@ in {
                 options = {
                   _secret = mkOption {
                     type = nullOr str;
-                    description = ''
+                    description = lib.mdDoc ''
                       The path to a file containing the value the
                       option should be set to in the final
                       configuration file.
@@ -362,7 +359,7 @@ in {
     };
 
     systemd.services.bookstack-setup = {
-      description = "Preperation tasks for BookStack";
+      description = "Preparation tasks for BookStack";
       before = [ "phpfpm-bookstack.service" ];
       after = optional db.createLocally "mysql.service";
       wantedBy = [ "multi-user.target" ];
@@ -372,7 +369,7 @@ in {
         User = user;
         WorkingDirectory = "${bookstack}";
         RuntimeDirectory = "bookstack/cache";
-        RuntimeDirectoryMode = 0700;
+        RuntimeDirectoryMode = "0700";
       };
       path = [ pkgs.replace-secret ];
       script =
diff --git a/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix b/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix
index 6bcf733452b9..143decfc0917 100644
--- a/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.calibre-web = {
-      enable = mkEnableOption "Calibre-Web";
+      enable = mkEnableOption (lib.mdDoc "Calibre-Web");
 
       listen = {
         ip = mkOption {
diff --git a/nixpkgs/nixos/modules/services/web-apps/changedetection-io.nix b/nixpkgs/nixos/modules/services/web-apps/changedetection-io.nix
new file mode 100644
index 000000000000..bbf4c2aed186
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/changedetection-io.nix
@@ -0,0 +1,220 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.changedetection-io;
+in
+{
+  options.services.changedetection-io = {
+    enable = mkEnableOption (lib.mdDoc "changedetection-io");
+
+    user = mkOption {
+      default = "changedetection-io";
+      type = types.str;
+      description = lib.mdDoc ''
+        User account under which changedetection-io runs.
+      '';
+    };
+
+    group = mkOption {
+      default = "changedetection-io";
+      type = types.str;
+      description = lib.mdDoc ''
+        Group account under which changedetection-io runs.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "Address the server will listen on.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 5000;
+      description = lib.mdDoc "Port the server will listen on.";
+    };
+
+    datastorePath = mkOption {
+      type = types.str;
+      default = "/var/lib/changedetection-io";
+      description = lib.mdDoc ''
+        The directory used to store all data for changedetection-io.
+      '';
+    };
+
+    baseURL = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "https://changedetection-io.example";
+      description = lib.mdDoc ''
+        The base url used in notifications and `{base_url}` token.
+      '';
+    };
+
+    behindProxy = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable this option when changedetection-io runs behind a reverse proxy, so that it trusts X-* headers.
+        It is recommend to run changedetection-io behind a TLS reverse proxy.
+      '';
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/run/secrets/changedetection-io.env";
+      description = lib.mdDoc ''
+        Securely pass environment variabels to changedetection-io.
+
+        This can be used to set for example a frontend password reproducible via `SALTED_PASS`
+        which convinetly also deactivates nags about the hosted version.
+        `SALTED_PASS` should be 64 characters long while the first 32 are the salt and the second the frontend password.
+        It can easily be retrieved from the settings file when first set via the frontend with the following command:
+        ``jq -r .settings.application.password /var/lib/changedetection-io/url-watches.json``
+      '';
+    };
+
+    webDriverSupport = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable support for fetching web pages using WebDriver and Chromium.
+        This starts a headless chromium controlled by puppeteer in an oci container.
+
+        ::: {.note}
+        Playwright can currently leak memory.
+        See https://github.com/dgtlmoon/changedetection.io/wiki/Playwright-content-fetcher#playwright-memory-leak
+        :::
+      '';
+    };
+
+    playwrightSupport = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable support for fetching web pages using playwright and Chromium.
+        This starts a headless Chromium controlled by puppeteer in an oci container.
+
+        ::: {.note}
+        Playwright can currently leak memory.
+        See https://github.com/dgtlmoon/changedetection.io/wiki/Playwright-content-fetcher#playwright-memory-leak
+        :::
+      '';
+    };
+
+    chromePort = mkOption {
+      type = types.port;
+      default = 4444;
+      description = lib.mdDoc ''
+        A free port on which webDriverSupport or playwrightSupport listen on localhost.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !((cfg.webDriverSupport == true) && (cfg.playwrightSupport == true));
+        message = "'services.changedetection-io.webDriverSupport' and 'services.changedetection-io.playwrightSupport' cannot be used together.";
+      }
+    ];
+
+    systemd = let
+      defaultStateDir = cfg.datastorePath == "/var/lib/changedetection-io";
+    in {
+      services.changedetection-io = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        preStart = ''
+          mkdir -p ${cfg.datastorePath}
+        '';
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+          StateDirectory = mkIf defaultStateDir "changedetection-io";
+          StateDirectoryMode = mkIf defaultStateDir "0750";
+          WorkingDirectory = cfg.datastorePath;
+          Environment = [ "HIDE_REFERER=true" ]
+            ++ lib.optional (cfg.baseURL != null) "BASE_URL=${cfg.baseURL}"
+            ++ lib.optional cfg.behindProxy "USE_X_SETTINGS=1"
+            ++ lib.optional cfg.webDriverSupport "WEBDRIVER_URL=http://127.0.0.1:${toString cfg.chromePort}/wd/hub"
+            ++ lib.optional cfg.playwrightSupport "PLAYWRIGHT_DRIVER_URL=ws://127.0.0.1:${toString cfg.chromePort}/?stealth=1&--disable-web-security=true";
+          EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
+          ExecStart = ''
+            ${pkgs.changedetection-io}/bin/changedetection.py \
+              -h ${cfg.listenAddress} -p ${toString cfg.port} -d ${cfg.datastorePath}
+          '';
+          ProtectHome = true;
+          ProtectSystem = true;
+          Restart = "on-failure";
+        };
+      };
+      tmpfiles.rules = mkIf defaultStateDir [
+        "d ${cfg.datastorePath} 0750 ${cfg.user} ${cfg.group} - -"
+      ];
+    };
+
+    users = {
+      users = optionalAttrs (cfg.user == "changedetection-io") {
+        "changedetection-io" = {
+          isSystemUser = true;
+          group = "changedetection-io";
+        };
+      };
+
+      groups = optionalAttrs (cfg.group == "changedetection-io") {
+        "changedetection-io" = { };
+      };
+    };
+
+    virtualisation = {
+      oci-containers.containers = lib.mkMerge [
+        (mkIf cfg.webDriverSupport {
+          changedetection-io-webdriver = {
+            image = "selenium/standalone-chrome";
+            environment = {
+              VNC_NO_PASSWORD = "1";
+              SCREEN_WIDTH = "1920";
+              SCREEN_HEIGHT = "1080";
+              SCREEN_DEPTH = "24";
+            };
+            ports = [
+              "127.0.0.1:${toString cfg.chromePort}:4444"
+            ];
+            volumes = [
+              "/dev/shm:/dev/shm"
+            ];
+            extraOptions = [ "--network=bridge" ];
+          };
+        })
+
+        (mkIf cfg.playwrightSupport {
+          changedetection-io-playwright = {
+            image = "browserless/chrome";
+            environment = {
+              SCREEN_WIDTH = "1920";
+              SCREEN_HEIGHT = "1024";
+              SCREEN_DEPTH = "16";
+              ENABLE_DEBUGGER = "false";
+              PREBOOT_CHROME = "true";
+              CONNECTION_TIMEOUT = "300000";
+              MAX_CONCURRENT_SESSIONS = "10";
+              CHROME_REFRESH_TIME = "600000";
+              DEFAULT_BLOCK_ADS = "true";
+              DEFAULT_STEALTH = "true";
+            };
+            ports = [
+              "127.0.0.1:${toString cfg.chromePort}:3000"
+            ];
+            extraOptions = [ "--network=bridge" ];
+          };
+        })
+      ];
+      podman.defaultNetwork.settings.dns_enabled = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix b/nixpkgs/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix
new file mode 100644
index 000000000000..f29d095bc10b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/chatgpt-retrieval-plugin.nix
@@ -0,0 +1,106 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.chatgpt-retrieval-plugin;
+in
+{
+  options.services.chatgpt-retrieval-plugin = {
+    enable = mkEnableOption (lib.mdDoc "chatgpt-retrieval-plugin service");
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = lib.mdDoc "Port the chatgpt-retrieval-plugin service listens on.";
+    };
+
+    host = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      example = "0.0.0.0";
+      description = lib.mdDoc "The hostname or IP address for chatgpt-retrieval-plugin to bind to.";
+    };
+
+    bearerTokenPath = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path to the secret bearer token used for the http api authentication.
+      '';
+      default = "";
+      example = "config.age.secrets.CHATGPT_RETRIEVAL_PLUGIN_BEARER_TOKEN.path";
+    };
+
+    openaiApiKeyPath = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path to the secret openai api key used for embeddings.
+      '';
+      default = "";
+      example = "config.age.secrets.CHATGPT_RETRIEVAL_PLUGIN_OPENAI_API_KEY.path";
+    };
+
+    datastore = mkOption {
+      type = types.enum [ "pinecone" "weaviate" "zilliz" "milvus" "qdrant" "redis" ];
+      default = "qdrant";
+      description = lib.mdDoc "This specifies the vector database provider you want to use to store and query embeddings.";
+    };
+
+    qdrantCollection = mkOption {
+      type = types.str;
+      description = lib.mdDoc ''
+        name of the qdrant collection used to store documents.
+      '';
+      default = "document_chunks";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.bearerTokenPath != "";
+        message = "services.chatgpt-retrieval-plugin.bearerTokenPath should not be an empty string.";
+      }
+      {
+        assertion = cfg.openaiApiKeyPath != "";
+        message = "services.chatgpt-retrieval-plugin.openaiApiKeyPath should not be an empty string.";
+      }
+    ];
+
+    systemd.services.chatgpt-retrieval-plugin = {
+      description = "ChatGPT Retrieval Plugin";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "always";
+        LoadCredential = [
+          "BEARER_TOKEN:${cfg.bearerTokenPath}"
+          "OPENAI_API_KEY:${cfg.openaiApiKeyPath}"
+        ];
+        StateDirectory = "chatgpt-retrieval-plugin";
+        StateDirectoryMode = "0755";
+      };
+
+      # it doesn't make sense to pass secrets as env vars, this is a hack until
+      # upstream has proper secret management.
+      script = ''
+        export BEARER_TOKEN=$(${pkgs.systemd}/bin/systemd-creds cat BEARER_TOKEN)
+        export OPENAI_API_KEY=$(${pkgs.systemd}/bin/systemd-creds cat OPENAI_API_KEY)
+        exec ${pkgs.chatgpt-retrieval-plugin}/bin/start --host ${cfg.host} --port ${toString cfg.port}
+      '';
+
+      environment = {
+        DATASTORE = cfg.datastore;
+        QDRANT_COLLECTION = mkIf (cfg.datastore == "qdrant") cfg.qdrantCollection;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      # create the directory for static files for fastapi
+      "C /var/lib/chatgpt-retrieval-plugin/.well-known - - - - ${pkgs.chatgpt-retrieval-plugin}/${pkgs.python3Packages.python.sitePackages}/.well-known"
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix b/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix
new file mode 100644
index 000000000000..da2cf93d7f1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/cloudlog.nix
@@ -0,0 +1,503 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cloudlog;
+  dbFile = let
+    password = if cfg.database.createLocally
+               then "''"
+               else "trim(file_get_contents('${cfg.database.passwordFile}'))";
+  in pkgs.writeText "database.php" ''
+    <?php
+    defined('BASEPATH') OR exit('No direct script access allowed');
+    $active_group = 'default';
+    $query_builder = TRUE;
+    $db['default'] = array(
+      'dsn' => "",
+      'hostname' => '${cfg.database.host}',
+      'username' => '${cfg.database.user}',
+      'password' => ${password},
+      'database' => '${cfg.database.name}',
+      'dbdriver' => 'mysqli',
+      'dbprefix' => "",
+      'pconnect' => TRUE,
+      'db_debug' => (ENVIRONMENT !== 'production'),
+      'cache_on' => FALSE,
+      'cachedir' => "",
+      'char_set' => 'utf8mb4',
+      'dbcollat' => 'utf8mb4_general_ci',
+      'swap_pre' => "",
+      'encrypt' => FALSE,
+      'compress' => FALSE,
+      'stricton' => FALSE,
+      'failover' => array(),
+      'save_queries' => TRUE
+    );
+  '';
+  configFile = pkgs.writeText "config.php" ''
+    <?php
+    include('${pkgs.cloudlog}/install/config/config.php');
+    $config['datadir'] = "${cfg.dataDir}/";
+    $config['base_url'] = "${cfg.baseUrl}";
+    ${cfg.extraConfig}
+  '';
+  package = pkgs.stdenv.mkDerivation rec {
+    pname = "cloudlog";
+    version = src.version;
+    src = pkgs.cloudlog;
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      ln -s ${configFile} $out/application/config/config.php
+      ln -s ${dbFile} $out/application/config/database.php
+
+      # link writable directories
+      for directory in updates uploads backup logbook; do
+        rm -rf $out/$directory
+        ln -s ${cfg.dataDir}/$directory $out/$directory
+      done
+
+      # link writable asset files
+      for asset in dok sota wwff; do
+        rm -rf $out/assets/json/$asset.txt
+        ln -s ${cfg.dataDir}/assets/json/$asset.txt $out/assets/json/$asset.txt
+      done
+    '';
+  };
+in
+{
+  options.services.cloudlog = with types; {
+    enable = mkEnableOption (mdDoc "Whether to enable Cloudlog");
+    dataDir = mkOption {
+      type = str;
+      default = "/var/lib/cloudlog";
+      description = mdDoc "Cloudlog data directory.";
+    };
+    baseUrl = mkOption {
+      type = str;
+      default = "http://localhost";
+      description = mdDoc "Cloudlog base URL";
+    };
+    user = mkOption {
+      type = str;
+      default = "cloudlog";
+      description = mdDoc "User account under which Cloudlog runs.";
+    };
+    database = {
+      createLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Create the database and database user locally.";
+      };
+      host = mkOption {
+        type = str;
+        description = mdDoc "MySQL database host";
+        default = "localhost";
+      };
+      name = mkOption {
+        type = str;
+        description = mdDoc "MySQL database name.";
+        default = "cloudlog";
+      };
+      user = mkOption {
+        type = str;
+        description = mdDoc "MySQL user name.";
+        default = "cloudlog";
+      };
+      passwordFile = mkOption {
+        type = nullOr str;
+        description = mdDoc "MySQL user password file.";
+        default = null;
+      };
+    };
+    poolConfig = mkOption {
+      type = attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = mdDoc ''
+        Options for Cloudlog's PHP-FPM pool.
+      '';
+    };
+    virtualHost = mkOption {
+      type = nullOr str;
+      default = "localhost";
+      description = mdDoc ''
+        Name of the nginx virtualhost to use and setup. If null, do not setup
+         any virtualhost.
+      '';
+    };
+    extraConfig = mkOption {
+      description = mdDoc ''
+       Any additional text to be appended to the config.php
+       configuration file. This is a PHP script. For configuration
+       settings, see <https://github.com/magicbug/Cloudlog/wiki/Cloudlog.php-Configuration-File>.
+      '';
+      default = "";
+      type = str;
+      example = ''
+        $config['show_time'] = TRUE;
+      '';
+    };
+    upload-lotw = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically upload logs to LoTW. If enabled, a systemd
+          timer will run the log upload task as specified by the interval
+           option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "daily";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the
+          time at which the LoTW upload will occur.
+        '';
+      };
+    };
+    upload-clublog = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically upload logs to Clublog. If enabled, a systemd
+          timer will run the log upload task as specified by the interval option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "daily";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the time
+          at which the Clublog upload will occur.
+        '';
+      };
+    };
+    update-lotw-users = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically update the list of LoTW users. If enabled, a
+          systemd timer will run the update task as specified by the interval
+          option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "weekly";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the
+          time at which the LoTW user update will occur.
+        '';
+      };
+    };
+    update-dok = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically update the DOK resource file. If enabled, a
+          systemd timer will run the update task as specified by the interval option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "monthly";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the
+          time at which the DOK update will occur.
+        '';
+      };
+    };
+    update-clublog-scp = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically update the Clublog SCP database. If enabled,
+          a systemd timer will run the update task as specified by the interval
+          option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "monthly";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the time
+          at which the Clublog SCP update will occur.
+        '';
+      };
+    };
+    update-wwff = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically update the WWFF database. If enabled, a
+          systemd timer will run the update task as specified by the interval
+          option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "monthly";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the time
+          at which the WWFF update will occur.
+        '';
+      };
+    };
+    upload-qrz = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically upload logs to QRZ. If enabled, a systemd
+          timer will run the update task as specified by the interval option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "daily";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the
+          time at which the QRZ upload will occur.
+        '';
+      };
+    };
+    update-sota = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Whether to periodically update the SOTA database. If enabled, a
+          systemd timer will run the update task as specified by the interval option.
+        '';
+      };
+      interval = mkOption {
+        type = str;
+        default = "monthly";
+        description = mdDoc ''
+          Specification (in the format described by systemd.time(7)) of the time
+          at which the SOTA update will occur.
+        '';
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "services.cloudlog.database.passwordFile cannot be specified if services.cloudlog.database.createLocally is set to true.";
+      }
+    ];
+
+    services.phpfpm = {
+      pools.cloudlog = {
+        inherit (cfg) user;
+        group = config.services.nginx.group;
+        settings =  {
+          "listen.owner" = config.services.nginx.user;
+          "listen.group" = config.services.nginx.group;
+        } // cfg.poolConfig;
+      };
+    };
+
+    services.nginx = mkIf (cfg.virtualHost != null) {
+      enable = true;
+      virtualHosts = {
+        "${cfg.virtualHost}" = {
+          root = "${package}";
+          locations."/".tryFiles = "$uri /index.php$is_args$args";
+          locations."~ ^/index.php(/|$)".extraConfig = ''
+              include ${config.services.nginx.package}/conf/fastcgi_params;
+              include ${pkgs.nginx}/conf/fastcgi.conf;
+              fastcgi_split_path_info ^(.+\.php)(.+)$;
+              fastcgi_pass unix:${config.services.phpfpm.pools.cloudlog.socket};
+              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+            '';
+        };
+      };
+    };
+
+    services.mysql = mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = {
+          "${cfg.database.name}.*" = "ALL PRIVILEGES";
+        };
+      }];
+    };
+
+    systemd = {
+      services = {
+        cloudlog-setup-database = mkIf cfg.database.createLocally {
+          description = "Set up cloudlog database";
+          serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+          };
+          wantedBy = [ "phpfpm-cloudlog.service" ];
+          after = [ "mysql.service" ];
+          script = let
+            mysql = "${config.services.mysql.package}/bin/mysql";
+          in ''
+            if [ ! -f ${cfg.dataDir}/.dbexists ]; then
+              ${mysql} ${cfg.database.name} < ${pkgs.cloudlog}/install/assets/install.sql
+              touch ${cfg.dataDir}/.dbexists
+            fi
+        '';
+        };
+        cloudlog-upload-lotw = {
+          description = "Upload QSOs to LoTW if certs have been provided";
+          enable = cfg.upload-lotw.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/lotw/lotw_upload";
+        };
+        cloudlog-update-lotw-users = {
+          description = "Update LOTW Users Database";
+          enable = cfg.update-lotw-users.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/lotw/load_users";
+        };
+        cloudlog-update-dok = {
+          description = "Update DOK File for autocomplete";
+          enable = cfg.update-dok.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_dok";
+        };
+        cloudlog-update-clublog-scp = {
+          description = "Update Clublog SCP Database File";
+          enable = cfg.update-clublog-scp.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_clublog_scp";
+        };
+        cloudlog-update-wwff = {
+          description = "Update WWFF File for autocomplete";
+          enable = cfg.update-wwff.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_wwff";
+        };
+        cloudlog-upload-qrz = {
+          description = "Upload QSOs to QRZ Logbook";
+          enable = cfg.upload-qrz.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/qrz/upload";
+        };
+        cloudlog-update-sota = {
+          description = "Update SOTA File for autocomplete";
+          enable = cfg.update-sota.enable;
+          script = "${pkgs.curl}/bin/curl -s ${cfg.baseUrl}/update/update_sota";
+        };
+      };
+      timers = {
+        cloudlog-upload-lotw = {
+          enable = cfg.upload-lotw.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-upload-lotw.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.upload-lotw.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-upload-clublog = {
+          enable = cfg.upload-clublog.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-upload-clublog.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.upload-clublog.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-update-lotw-users = {
+          enable = cfg.update-lotw-users.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-update-lotw-users.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.update-lotw-users.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-update-dok = {
+          enable = cfg.update-dok.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-update-dok.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.update-dok.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-update-clublog-scp = {
+          enable = cfg.update-clublog-scp.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-update-clublog-scp.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.update-clublog-scp.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-update-wwff =  {
+          enable = cfg.update-wwff.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-update-wwff.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.update-wwff.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-upload-qrz = {
+          enable = cfg.upload-qrz.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-upload-qrz.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.upload-qrz.interval;
+            Persistent = true;
+          };
+        };
+        cloudlog-update-sota = {
+          enable = cfg.update-sota.enable;
+          wantedBy = [ "timers.target" ];
+          partOf = [ "cloudlog-update-sota.service" ];
+          after = [ "phpfpm-cloudlog.service" ];
+          timerConfig = {
+            OnCalendar = cfg.update-sota.interval;
+            Persistent = true;
+          };
+        };
+      };
+      tmpfiles.rules = let
+        group = config.services.nginx.group;
+      in [
+        "d ${cfg.dataDir}                0750 ${cfg.user} ${group} - -"
+        "d ${cfg.dataDir}/updates        0750 ${cfg.user} ${group} - -"
+        "d ${cfg.dataDir}/uploads        0750 ${cfg.user} ${group} - -"
+        "d ${cfg.dataDir}/backup         0750 ${cfg.user} ${group} - -"
+        "d ${cfg.dataDir}/logbook        0750 ${cfg.user} ${group} - -"
+        "d ${cfg.dataDir}/assets/json    0750 ${cfg.user} ${group} - -"
+        "d ${cfg.dataDir}/assets/qslcard 0750 ${cfg.user} ${group} - -"
+      ];
+    };
+
+    users.users."${cfg.user}" = {
+      isSystemUser = true;
+      group = config.services.nginx.group;
+    };
+  };
+
+  meta.maintainers = with maintainers; [ melling ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/code-server.nix b/nixpkgs/nixos/modules/services/web-apps/code-server.nix
index 84fc03deabff..fa7d4a348c23 100644
--- a/nixpkgs/nixos/modules/services/web-apps/code-server.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/code-server.nix
@@ -1,107 +1,199 @@
 { 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";
+      enable = lib.mkEnableOption (lib.mdDoc "code-server");
 
-      package = mkOption {
-        default = pkgs.code-server;
-        defaultText = "pkgs.code-server";
-        description = lib.mdDoc "Which code-server derivation to use.";
-        type = types.package;
-      };
+      package = lib.mkPackageOptionMD pkgs "code-server" { };
 
-      extraPackages = mkOption {
+      extraPackages = lib.mkOption {
         default = [ ];
-        description = lib.mdDoc "Packages that are available in the PATH of code-server.";
-        example = "[ pkgs.go ]";
-        type = types.listOf types.package;
+        description = lib.mdDoc ''
+          Additional packages to add to the code-server {env}`PATH`.
+        '';
+        example = lib.literalExpression "[ pkgs.go ]";
+        type = lib.types.listOf lib.types.package;
       };
 
-      extraEnvironment = mkOption {
-        type = types.attrsOf types.str;
-        description =
-          lib.mdDoc "Additional environment variables to passed to code-server.";
+      extraEnvironment = lib.mkOption {
+        type = lib.types.attrsOf lib.types.str;
+        description = lib.mdDoc ''
+          Additional environment variables to pass to code-server.
+        '';
         default = { };
         example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
       };
 
-      extraArguments = mkOption {
-        default = [ "--disable-telemetry" ];
-        description = lib.mdDoc "Additional arguments that passed to code-server";
-        example = ''[ "--verbose" ]'';
-        type = types.listOf types.str;
+      extraArguments = lib.mkOption {
+        default = [ ];
+        description = lib.mdDoc ''
+          Additional arguments to pass to code-server.
+        '';
+        example = lib.literalExpression ''[ "--log=info" ]'';
+        type = lib.types.listOf lib.types.str;
       };
 
-      host = mkOption {
-        default = "127.0.0.1";
-        description = lib.mdDoc "The host-ip to bind to.";
-        type = types.str;
+      host = lib.mkOption {
+        default = "localhost";
+        description = lib.mdDoc ''
+          The host name or IP address the server should listen to.
+        '';
+        type = lib.types.str;
       };
 
-      port = mkOption {
+      port = lib.mkOption {
         default = 4444;
-        description = lib.mdDoc "The port where code-server runs.";
-        type = types.port;
+        description = lib.mdDoc ''
+          The port the server should listen to.
+        '';
+        type = lib.types.port;
       };
 
-      auth = mkOption {
+      auth = lib.mkOption {
         default = "password";
-        description = lib.mdDoc "The type of authentication to use.";
-        type = types.enum [ "none" "password" ];
+        description = lib.mdDoc ''
+          The type of authentication to use.
+        '';
+        type = lib.types.enum [ "none" "password" ];
       };
 
-      hashedPassword = mkOption {
+      hashedPassword = lib.mkOption {
         default = "";
-        description =
-          lib.mdDoc "Create the password with: 'echo -n 'thisismypassword' | npx argon2-cli -e'.";
-        type = types.str;
+        description = lib.mdDoc ''
+          Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`.
+        '';
+        type = lib.types.str;
       };
 
-      user = mkOption {
+      user = lib.mkOption {
         default = defaultUser;
         example = "yourUser";
         description = lib.mdDoc ''
           The user to run code-server as.
           By default, a user named `${defaultUser}` will be created.
         '';
-        type = types.str;
+        type = lib.types.str;
       };
 
-      group = mkOption {
+      group = lib.mkOption {
         default = defaultGroup;
         example = "yourGroup";
         description = lib.mdDoc ''
           The group to run code-server under.
           By default, a group named `${defaultGroup}` will be created.
         '';
-        type = types.str;
+        type = lib.types.str;
       };
 
-      extraGroups = mkOption {
+      extraGroups = lib.mkOption {
         default = [ ];
-        description =
-          lib.mdDoc "An array of additional groups for the `${defaultUser}` user.";
+        description = lib.mdDoc ''
+          An array of additional groups for the `${defaultUser}` user.
+        '';
         example = [ "docker" ];
-        type = types.listOf types.str;
+        type = lib.types.listOf lib.types.str;
+      };
+
+      socket = lib.mkOption {
+        default = null;
+        example = "/run/code-server/socket";
+        description = lib.mdDoc ''
+          Path to a socket (bind-addr will be ignored).
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      socketMode = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+           File mode of the socket.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      userDataDir = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+          Path to the user data directory.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      extensionsDir = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+          Path to the extensions directory.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      proxyDomain = lib.mkOption {
+        default = null;
+        example = "code-server.lan";
+        description = lib.mdDoc ''
+          Domain used for proxying ports.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      disableTelemetry = lib.mkOption {
+        default = false;
+        example = true;
+        description = lib.mdDoc ''
+          Disable telemetry.
+        '';
+        type = lib.types.bool;
+      };
+
+      disableUpdateCheck = lib.mkOption {
+        default = false;
+        example = true;
+        description = lib.mdDoc ''
+          Disable update check.
+          Without this flag, code-server checks every 6 hours against the latest github release and
+          then notifies you once every week that a new release is available.
+        '';
+        type = lib.types.bool;
+      };
+
+      disableFileDownloads = lib.mkOption {
+        default = false;
+        example = true;
+        description = lib.mdDoc ''
+          Disable file downloads from Code.
+        '';
+        type = lib.types.bool;
+      };
+
+      disableWorkspaceTrust = lib.mkOption {
+        default = false;
+        example = true;
+        description = lib.mdDoc ''
+          Disable Workspace Trust feature.
+        '';
+        type = lib.types.bool;
+      };
+
+      disableGettingStartedOverride = lib.mkOption {
+        default = false;
+        example = true;
+        description = lib.mdDoc ''
+          Disable the coder/coder override in the Help: Getting Started page.
+        '';
+        type = lib.types.bool;
       };
 
     };
   };
 
-  ###### implementation
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.code-server = {
-      description = "VSCode server";
+      description = "Code server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network-online.target" ];
       path = cfg.extraPackages;
@@ -109,18 +201,37 @@ in {
         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;
+        ExecStart = ''
+          ${lib.getExe cfg.package} \
+            --auth=${cfg.auth} \
+            --bind-addr=${cfg.host}:${toString cfg.port} \
+          '' + lib.optionalString (cfg.socket != null) ''
+            --socket=${cfg.socket} \
+          '' + lib.optionalString (cfg.userDataDir != null) ''
+            --user-data-dir=${cfg.userDataDir} \
+          '' + lib.optionalString (cfg.extensionsDir != null) ''
+            --extensions-dir=${cfg.extensionsDir} \
+          '' + lib.optionalString (cfg.disableTelemetry == true) ''
+            --disable-telemetry \
+          '' + lib.optionalString (cfg.disableUpdateCheck == true) ''
+            --disable-update-check \
+          '' + lib.optionalString (cfg.disableFileDownloads == true) ''
+            --disable-file-downloads \
+          '' + lib.optionalString (cfg.disableWorkspaceTrust == true) ''
+            --disable-workspace-trust \
+          '' + lib.optionalString (cfg.disableGettingStartedOverride == true) ''
+            --disable-getting-started-override \
+          '' + lib.escapeShellArgs 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) {
+    users.users."${cfg.user}" = lib.mkMerge [
+      (lib.mkIf (cfg.user == defaultUser) {
         isNormalUser = true;
         description = "code-server user";
         inherit (cfg) group;
@@ -131,9 +242,8 @@ in {
       }
     ];
 
-    users.groups."${defaultGroup}" = mkIf (cfg.group == defaultGroup) { };
-
+    users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { };
   };
 
-  meta.maintainers = with maintainers; [ stackshadow ];
+  meta.maintainers = [ lib.maintainers.stackshadow ];
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/coder.nix b/nixpkgs/nixos/modules/services/web-apps/coder.nix
new file mode 100644
index 000000000000..469a29bc3aa8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/coder.nix
@@ -0,0 +1,217 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.coder;
+  name = "coder";
+in {
+  options = {
+    services.coder = {
+      enable = mkEnableOption (lib.mdDoc "Coder service");
+
+      user = mkOption {
+        type = types.str;
+        default = "coder";
+        description = lib.mdDoc ''
+          User under which the coder service runs.
+
+          ::: {.note}
+          If left as the default value this user will automatically be created
+          on system activation, otherwise it needs to be configured manually.
+          :::
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "coder";
+        description = lib.mdDoc ''
+          Group under which the coder service runs.
+
+          ::: {.note}
+          If left as the default value this group will automatically be created
+          on system activation, otherwise it needs to be configured manually.
+          :::
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.coder;
+        description = lib.mdDoc ''
+          Package to use for the service.
+        '';
+        defaultText = literalExpression "pkgs.coder";
+      };
+
+      homeDir = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Home directory for coder user.
+        '';
+        default = "/var/lib/coder";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Listen address.
+        '';
+        default = "127.0.0.1:3000";
+      };
+
+      accessUrl = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          Access URL should be a external IP address or domain with DNS records pointing to Coder.
+        '';
+        default = null;
+        example = "https://coder.example.com";
+      };
+
+      wildcardAccessUrl = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains.
+        '';
+        default = null;
+        example = "*.coder.example.com";
+      };
+
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Create the database and database user locally.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "/run/postgresql";
+          description = lib.mdDoc ''
+            Hostname hosting the database.
+          '';
+        };
+
+        database = mkOption {
+          type = types.str;
+          default = "coder";
+          description = lib.mdDoc ''
+            Name of database.
+          '';
+        };
+
+        username = mkOption {
+          type = types.str;
+          default = "coder";
+          description = lib.mdDoc ''
+            Username for accessing the database.
+          '';
+        };
+
+        password = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            Password for accessing the database.
+          '';
+        };
+
+        sslmode = mkOption {
+          type = types.nullOr types.str;
+          default = "disable";
+          description = lib.mdDoc ''
+            Password for accessing the database.
+          '';
+        };
+      };
+
+      tlsCert = mkOption {
+        type = types.nullOr types.path;
+        description = lib.mdDoc ''
+          The path to the TLS certificate.
+        '';
+        default = null;
+      };
+
+      tlsKey = mkOption {
+        type = types.nullOr types.path;
+        description = lib.mdDoc ''
+          The path to the TLS key.
+        '';
+        default = null;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.username == name;
+        message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true";
+      }
+    ];
+
+    systemd.services.coder = {
+      description = "Coder - Self-hosted developer workspaces on your infra";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        CODER_ACCESS_URL = cfg.accessUrl;
+        CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl;
+        CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}";
+        CODER_ADDRESS = cfg.listenAddress;
+        CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1";
+        CODER_TLS_CERT_FILE = cfg.tlsCert;
+        CODER_TLS_KEY_FILE = cfg.tlsKey;
+      };
+
+      serviceConfig = {
+        ProtectSystem = "full";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+        SecureBits = "keep-caps";
+        AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
+        CacheDirectory = "coder";
+        CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
+        KillSignal = "SIGINT";
+        KillMode = "mixed";
+        NoNewPrivileges = "yes";
+        Restart = "on-failure";
+        ExecStart = "${cfg.package}/bin/coder server";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [
+        cfg.database.database
+      ];
+      ensureUsers = [{
+        name = cfg.database.username;
+        ensurePermissions = {
+          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
+        };
+        }
+      ];
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      "${cfg.group}" = {};
+    };
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        description = "Coder service user";
+        group = cfg.group;
+        home = cfg.homeDir;
+        createHome = true;
+        isSystemUser = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/convos.nix b/nixpkgs/nixos/modules/services/web-apps/convos.nix
index 120481c64017..cd9f9d885d69 100644
--- a/nixpkgs/nixos/modules/services/web-apps/convos.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/convos.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.services.convos = {
-    enable = mkEnableOption "Convos";
+    enable = mkEnableOption (lib.mdDoc "Convos");
     listenPort = mkOption {
       type = types.port;
       default = 3000;
diff --git a/nixpkgs/nixos/modules/services/web-apps/dex.nix b/nixpkgs/nixos/modules/services/web-apps/dex.nix
index 82fdcd212f96..f69f1749aeb8 100644
--- a/nixpkgs/nixos/modules/services/web-apps/dex.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/dex.nix
@@ -19,13 +19,13 @@ let
 in
 {
   options.services.dex = {
-    enable = mkEnableOption "the OpenID Connect and OAuth2 identity provider";
+    enable = mkEnableOption (lib.mdDoc "the OpenID Connect and OAuth2 identity provider");
 
     environmentFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
-        Environment file (see <literal>systemd.exec(5)</literal>
+      description = lib.mdDoc ''
+        Environment file (see `systemd.exec(5)`
         "EnvironmentFile=" section for the syntax) to define variables for dex.
         This option can be used to safely include secret keys into the dex configuration.
       '';
@@ -58,7 +58,7 @@ in
       '';
       description = lib.mdDoc ''
         The available options can be found in
-        [the example configuration](https://github.com/dexidp/dex/blob/v${pkgs.dex.version}/config.yaml.dist).
+        [the example configuration](https://github.com/dexidp/dex/blob/v${pkgs.dex-oidc.version}/config.yaml.dist).
 
         It's also possible to refer to environment variables (defined in [services.dex.environmentFile](#opt-services.dex.environmentFile))
         using the syntax `$VARIABLE_NAME`.
@@ -83,11 +83,12 @@ in
         AmbientCapabilities = "CAP_NET_BIND_SERVICE";
         BindReadOnlyPaths = [
           "/nix/store"
-          "-/etc/resolv.conf"
-          "-/etc/nsswitch.conf"
+          "-/etc/dex"
           "-/etc/hosts"
           "-/etc/localtime"
-          "-/etc/dex"
+          "-/etc/nsswitch.conf"
+          "-/etc/resolv.conf"
+          "-/etc/ssl/certs/ca-certificates.crt"
         ];
         BindPaths = optional (cfg.settings.storage.type == "postgres") "/var/run/postgresql";
         CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
@@ -119,7 +120,7 @@ in
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
+        SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
         TemporaryFileSystem = "/:ro";
         # Does not work well with the temporary root
         #UMask = "0066";
diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.md b/nixpkgs/nixos/modules/services/web-apps/discourse.md
new file mode 100644
index 000000000000..35180bea87d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/discourse.md
@@ -0,0 +1,286 @@
+# Discourse {#module-services-discourse}
+
+[Discourse](https://www.discourse.org/) is a
+modern and open source discussion platform.
+
+## Basic usage {#module-services-discourse-basic-usage}
+
+A minimal configuration using Let's Encrypt for TLS certificates looks like this:
+```
+services.discourse = {
+  enable = true;
+  hostname = "discourse.example.com";
+  admin = {
+    email = "admin@example.com";
+    username = "admin";
+    fullName = "Administrator";
+    passwordFile = "/path/to/password_file";
+  };
+  secretKeyBaseFile = "/path/to/secret_key_base_file";
+};
+security.acme.email = "me@example.com";
+security.acme.acceptTerms = true;
+```
+
+Provided a proper DNS setup, you'll be able to connect to the
+instance at `discourse.example.com` and log in
+using the credentials provided in
+`services.discourse.admin`.
+
+## Using a regular TLS certificate {#module-services-discourse-tls}
+
+To set up TLS using a regular certificate and key on file, use
+the [](#opt-services.discourse.sslCertificate)
+and [](#opt-services.discourse.sslCertificateKey)
+options:
+
+```
+services.discourse = {
+  enable = true;
+  hostname = "discourse.example.com";
+  sslCertificate = "/path/to/ssl_certificate";
+  sslCertificateKey = "/path/to/ssl_certificate_key";
+  admin = {
+    email = "admin@example.com";
+    username = "admin";
+    fullName = "Administrator";
+    passwordFile = "/path/to/password_file";
+  };
+  secretKeyBaseFile = "/path/to/secret_key_base_file";
+};
+```
+
+## Database access {#module-services-discourse-database}
+
+Discourse uses PostgreSQL to store most of its
+data. A database will automatically be enabled and a database
+and role created unless [](#opt-services.discourse.database.host) is changed from
+its default of `null` or [](#opt-services.discourse.database.createLocally) is set
+to `false`.
+
+External database access can also be configured by setting
+[](#opt-services.discourse.database.host),
+[](#opt-services.discourse.database.username) and
+[](#opt-services.discourse.database.passwordFile) as
+appropriate. Note that you need to manually create a database
+called `discourse` (or the name you chose in
+[](#opt-services.discourse.database.name)) and
+allow the configured database user full access to it.
+
+## Email {#module-services-discourse-mail}
+
+In addition to the basic setup, you'll want to configure an SMTP
+server Discourse can use to send user
+registration and password reset emails, among others. You can
+also optionally let Discourse receive
+email, which enables people to reply to threads and conversations
+via email.
+
+A basic setup which assumes you want to use your configured
+[hostname](#opt-services.discourse.hostname) as
+email domain can be done like this:
+
+```
+services.discourse = {
+  enable = true;
+  hostname = "discourse.example.com";
+  sslCertificate = "/path/to/ssl_certificate";
+  sslCertificateKey = "/path/to/ssl_certificate_key";
+  admin = {
+    email = "admin@example.com";
+    username = "admin";
+    fullName = "Administrator";
+    passwordFile = "/path/to/password_file";
+  };
+  mail.outgoing = {
+    serverAddress = "smtp.emailprovider.com";
+    port = 587;
+    username = "user@emailprovider.com";
+    passwordFile = "/path/to/smtp_password_file";
+  };
+  mail.incoming.enable = true;
+  secretKeyBaseFile = "/path/to/secret_key_base_file";
+};
+```
+
+This assumes you have set up an MX record for the address you've
+set in [hostname](#opt-services.discourse.hostname) and
+requires proper SPF, DKIM and DMARC configuration to be done for
+the domain you're sending from, in order for email to be reliably delivered.
+
+If you want to use a different domain for your outgoing email
+(for example `example.com` instead of
+`discourse.example.com`) you should set
+[](#opt-services.discourse.mail.notificationEmailAddress) and
+[](#opt-services.discourse.mail.contactEmailAddress) manually.
+
+::: {.note}
+Setup of TLS for incoming email is currently only configured
+automatically when a regular TLS certificate is used, i.e. when
+[](#opt-services.discourse.sslCertificate) and
+[](#opt-services.discourse.sslCertificateKey) are
+set.
+:::
+
+## Additional settings {#module-services-discourse-settings}
+
+Additional site settings and backend settings, for which no
+explicit NixOS options are provided,
+can be set in [](#opt-services.discourse.siteSettings) and
+[](#opt-services.discourse.backendSettings) respectively.
+
+### Site settings {#module-services-discourse-site-settings}
+
+"Site settings" are the settings that can be
+changed through the Discourse
+UI. Their *default* values can be set using
+[](#opt-services.discourse.siteSettings).
+
+Settings are expressed as a Nix attribute set which matches the
+structure of the configuration in
+[config/site_settings.yml](https://github.com/discourse/discourse/blob/master/config/site_settings.yml).
+To find a setting's path, you only need to care about the first
+two levels; i.e. its category (e.g. `login`)
+and name (e.g. `invite_only`).
+
+Settings containing secret data should be set to an attribute
+set containing the attribute `_secret` - a
+string pointing to a file containing the value the option
+should be set to. See the example.
+
+### Backend settings {#module-services-discourse-backend-settings}
+
+Settings are expressed as a Nix attribute set which matches the
+structure of the configuration in
+[config/discourse.conf](https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf).
+Empty parameters can be defined by setting them to
+`null`.
+
+### Example {#module-services-discourse-settings-example}
+
+The following example sets the title and description of the
+Discourse instance and enables
+GitHub login in the site settings,
+and changes a few request limits in the backend settings:
+```
+services.discourse = {
+  enable = true;
+  hostname = "discourse.example.com";
+  sslCertificate = "/path/to/ssl_certificate";
+  sslCertificateKey = "/path/to/ssl_certificate_key";
+  admin = {
+    email = "admin@example.com";
+    username = "admin";
+    fullName = "Administrator";
+    passwordFile = "/path/to/password_file";
+  };
+  mail.outgoing = {
+    serverAddress = "smtp.emailprovider.com";
+    port = 587;
+    username = "user@emailprovider.com";
+    passwordFile = "/path/to/smtp_password_file";
+  };
+  mail.incoming.enable = true;
+  siteSettings = {
+    required = {
+      title = "My Cats";
+      site_description = "Discuss My Cats (and be nice plz)";
+    };
+    login = {
+      enable_github_logins = true;
+      github_client_id = "a2f6dfe838cb3206ce20";
+      github_client_secret._secret = /run/keys/discourse_github_client_secret;
+    };
+  };
+  backendSettings = {
+    max_reqs_per_ip_per_minute = 300;
+    max_reqs_per_ip_per_10_seconds = 60;
+    max_asset_reqs_per_ip_per_10_seconds = 250;
+    max_reqs_per_ip_mode = "warn+block";
+  };
+  secretKeyBaseFile = "/path/to/secret_key_base_file";
+};
+```
+
+In the resulting site settings file, the
+`login.github_client_secret` key will be set
+to the contents of the
+{file}`/run/keys/discourse_github_client_secret`
+file.
+
+## Plugins {#module-services-discourse-plugins}
+
+You can install Discourse plugins
+using the [](#opt-services.discourse.plugins)
+option. Pre-packaged plugins are provided in
+`<your_discourse_package_here>.plugins`. If
+you want the full suite of plugins provided through
+`nixpkgs`, you can also set the [](#opt-services.discourse.package) option to
+`pkgs.discourseAllPlugins`.
+
+Plugins can be built with the
+`<your_discourse_package_here>.mkDiscoursePlugin`
+function. Normally, it should suffice to provide a
+`name` and `src` attribute. If
+the plugin has Ruby dependencies, however, they need to be
+packaged in accordance with the [Developing with Ruby](https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby)
+section of the Nixpkgs manual and the
+appropriate gem options set in `bundlerEnvArgs`
+(normally `gemdir` is sufficient). A plugin's
+Ruby dependencies are listed in its
+{file}`plugin.rb` file as function calls to
+`gem`. To construct the corresponding
+{file}`Gemfile` manually, run {command}`bundle init`, then add the `gem` lines to it
+verbatim.
+
+Much of the packaging can be done automatically by the
+{file}`nixpkgs/pkgs/servers/web-apps/discourse/update.py`
+script - just add the plugin to the `plugins`
+list in the `update_plugins` function and run
+the script:
+```bash
+./update.py update-plugins
+```
+
+Some plugins provide [site settings](#module-services-discourse-site-settings).
+Their defaults can be configured using [](#opt-services.discourse.siteSettings), just like
+regular site settings. To find the names of these settings, look
+in the `config/settings.yml` file of the plugin
+repo.
+
+For example, to add the [discourse-spoiler-alert](https://github.com/discourse/discourse-spoiler-alert)
+and [discourse-solved](https://github.com/discourse/discourse-solved)
+plugins, and disable `discourse-spoiler-alert`
+by default:
+
+```
+services.discourse = {
+  enable = true;
+  hostname = "discourse.example.com";
+  sslCertificate = "/path/to/ssl_certificate";
+  sslCertificateKey = "/path/to/ssl_certificate_key";
+  admin = {
+    email = "admin@example.com";
+    username = "admin";
+    fullName = "Administrator";
+    passwordFile = "/path/to/password_file";
+  };
+  mail.outgoing = {
+    serverAddress = "smtp.emailprovider.com";
+    port = 587;
+    username = "user@emailprovider.com";
+    passwordFile = "/path/to/smtp_password_file";
+  };
+  mail.incoming.enable = true;
+  plugins = with config.services.discourse.package.plugins; [
+    discourse-spoiler-alert
+    discourse-solved
+  ];
+  siteSettings = {
+    plugins = {
+      spoiler_enabled = false;
+    };
+  };
+  secretKeyBaseFile = "/path/to/secret_key_base_file";
+};
+```
diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.nix b/nixpkgs/nixos/modules/services/web-apps/discourse.nix
index 1e2326d81801..f80eb6b4c7f0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/discourse.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/discourse.nix
@@ -19,14 +19,14 @@ let
   # We only want to create a database if we're actually going to connect to it.
   databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == null;
 
-  tlsEnabled = (cfg.enableACME
+  tlsEnabled = cfg.enableACME
                 || cfg.sslCertificate != null
-                || cfg.sslCertificateKey != null);
+                || cfg.sslCertificateKey != null;
 in
 {
   options = {
     services.discourse = {
-      enable = lib.mkEnableOption "Discourse, an open source discussion platform";
+      enable = lib.mkEnableOption (lib.mdDoc "Discourse, an open source discussion platform");
 
       package = lib.mkOption {
         type = lib.types.package;
@@ -42,11 +42,8 @@ in
 
       hostname = lib.mkOption {
         type = lib.types.str;
-        default = if config.networking.domain != null then
-                    config.networking.fqdn
-                  else
-                    config.networking.hostName;
-        defaultText = lib.literalExpression "config.networking.fqdn";
+        default = config.networking.fqdnOrHostName;
+        defaultText = lib.literalExpression "config.networking.fqdnOrHostName";
         example = "discourse.example.com";
         description = lib.mdDoc ''
           The hostname to serve Discourse on.
@@ -57,20 +54,20 @@ in
         type = with lib.types; nullOr path;
         default = null;
         example = "/run/keys/secret_key_base";
-        description = ''
+        description = lib.mdDoc ''
           The path to a file containing the
-          <literal>secret_key_base</literal> secret.
+          `secret_key_base` secret.
 
-          Discourse uses <literal>secret_key_base</literal> to encrypt
+          Discourse uses `secret_key_base` to encrypt
           the cookie store, which contains session data, and to digest
           user auth tokens.
 
           Needs to be a 64 byte long string of hexadecimal
           characters. You can generate one by running
 
-          <screen>
-          <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file
-          </screen>
+          ```
+          openssl rand -hex 64 >/path/to/secret_key_base_file
+          ```
 
           This should be a string, not a nix path, since nix paths are
           copied into the world-readable nix store.
@@ -100,9 +97,9 @@ in
       enableACME = lib.mkOption {
         type = lib.types.bool;
         default = cfg.sslCertificate == null && cfg.sslCertificateKey == null;
-        defaultText = lib.literalDocBook ''
-          <literal>true</literal>, unless <option>services.discourse.sslCertificate</option>
-          and <option>services.discourse.sslCertificateKey</option> are set.
+        defaultText = lib.literalMD ''
+          `true`, unless {option}`services.discourse.sslCertificate`
+          and {option}`services.discourse.sslCertificateKey` are set.
         '';
         description = lib.mdDoc ''
           Whether an ACME certificate should be used to secure
@@ -121,17 +118,16 @@ in
             max_reqs_per_ip_mode = "warn+block";
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Additional settings to put in the
-          <filename>discourse.conf</filename> file.
+          {file}`discourse.conf` file.
 
           Look in the
-          <link xlink:href="https://github.com/discourse/discourse/blob/master/config/discourse_defaults.conf">discourse_defaults.conf</link>
+          [discourse_defaults.conf](https://github.com/discourse/discourse/blob/master/config/discourse_defaults.conf)
           file in the upstream distribution to find available options.
 
-          Setting an option to <literal>null</literal> means
-          <quote>define variable, but leave right-hand side
-          empty</quote>.
+          Setting an option to `null` means
+          “define variable, but leave right-hand side empty”.
         '';
       };
 
@@ -241,9 +237,9 @@ in
         host = lib.mkOption {
           type = with lib.types; nullOr str;
           default = null;
-          description = ''
-            Discourse database hostname. <literal>null</literal> means <quote>prefer
-            local unix socket connection</quote>.
+          description = lib.mdDoc ''
+            Discourse database hostname. `null` means
+            “prefer local unix socket connection”.
           '';
         };
 
@@ -494,10 +490,8 @@ in
             discourse-github
           ];
         '';
-        description = ''
-          Plugins to install as part of
-          <productname>Discourse</productname>, expressed as a list of
-          derivations.
+        description = lib.mdDoc ''
+          Plugins to install as part of Discourse, expressed as a list of derivations.
         '';
       };
 
@@ -621,6 +615,7 @@ in
       s3_endpoint = null;
       s3_http_continue_timeout = null;
       s3_install_cors_rule = null;
+      s3_asset_cdn_url = null;
 
       max_user_api_reqs_per_minute = 20;
       max_user_api_reqs_per_day = 2880;
@@ -653,6 +648,12 @@ in
       multisite_config_path = "config/multisite.yml";
       enable_long_polling = null;
       long_polling_interval = null;
+      preload_link_header = false;
+      redirect_avatar_requests = false;
+      pg_force_readonly_mode = false;
+      dns_query_timeout_secs = null;
+      regex_timeout_seconds = 2;
+      allow_impersonation = true;
     };
 
     services.redis.servers.discourse =
@@ -801,13 +802,13 @@ in
           "public"
           "sockets"
         ];
-        RuntimeDirectoryMode = 0750;
+        RuntimeDirectoryMode = "0750";
         StateDirectory = map (p: "discourse/" + p) [
           "uploads"
           "backups"
           "tmp"
         ];
-        StateDirectoryMode = 0750;
+        StateDirectoryMode = "0750";
         LogsDirectory = "discourse";
         TimeoutSec = "infinity";
         Restart = "on-failure";
@@ -826,10 +827,10 @@ in
 
     services.nginx = lib.mkIf cfg.nginx.enable {
       enable = true;
-      additionalModules = [ pkgs.nginxModules.brotli ];
 
       recommendedTlsSettings = true;
       recommendedOptimisation = true;
+      recommendedBrotliSettings = true;
       recommendedGzipSettings = true;
       recommendedProxySettings = true;
 
@@ -1017,6 +1018,7 @@ in
         notification_email = cfg.mail.notificationEmailAddress;
         contact_email = cfg.mail.contactEmailAddress;
       };
+      security.force_https = tlsEnabled;
       email = {
         manual_polling_enabled = cfg.mail.incoming.enable;
         reply_by_email_enabled = cfg.mail.incoming.enable;
@@ -1026,8 +1028,8 @@ in
 
     services.postfix = lib.mkIf cfg.mail.incoming.enable {
       enable = true;
-      sslCert = if cfg.sslCertificate != null then cfg.sslCertificate else "";
-      sslKey = if cfg.sslCertificateKey != null then cfg.sslCertificateKey else "";
+      sslCert = lib.optionalString (cfg.sslCertificate != null) cfg.sslCertificate;
+      sslKey = lib.optionalString (cfg.sslCertificateKey != null) cfg.sslCertificateKey;
 
       origin = cfg.hostname;
       relayDomains = [ cfg.hostname ];
@@ -1086,6 +1088,6 @@ in
     ];
   };
 
-  meta.doc = ./discourse.xml;
+  meta.doc = ./discourse.md;
   meta.maintainers = [ lib.maintainers.talyz ];
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.xml b/nixpkgs/nixos/modules/services/web-apps/discourse.xml
deleted file mode 100644
index ad9b65abf51e..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/discourse.xml
+++ /dev/null
@@ -1,355 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-discourse">
- <title>Discourse</title>
- <para>
-   <link xlink:href="https://www.discourse.org/">Discourse</link> is a
-   modern and open source discussion platform.
- </para>
-
- <section xml:id="module-services-discourse-basic-usage">
-   <title>Basic usage</title>
-   <para>
-     A minimal configuration using Let's Encrypt for TLS certificates looks like this:
-<programlisting>
-services.discourse = {
-  <link linkend="opt-services.discourse.enable">enable</link> = true;
-  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
-  admin = {
-    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
-    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
-    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
-    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
-  };
-  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
-};
-<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>
-
-   <para>
-     Provided a proper DNS setup, you'll be able to connect to the
-     instance at <literal>discourse.example.com</literal> and log in
-     using the credentials provided in
-     <literal>services.discourse.admin</literal>.
-   </para>
- </section>
-
- <section xml:id="module-services-discourse-tls">
-   <title>Using a regular TLS certificate</title>
-   <para>
-     To set up TLS using a regular certificate and key on file, use
-     the <xref linkend="opt-services.discourse.sslCertificate" />
-     and <xref linkend="opt-services.discourse.sslCertificateKey" />
-     options:
-
-<programlisting>
-services.discourse = {
-  <link linkend="opt-services.discourse.enable">enable</link> = true;
-  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
-  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
-  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
-  admin = {
-    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
-    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
-    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
-    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
-  };
-  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
-};
-</programlisting>
-
-   </para>
- </section>
-
- <section xml:id="module-services-discourse-database">
-   <title>Database access</title>
-   <para>
-     <productname>Discourse</productname> uses
-     <productname>PostgreSQL</productname> to store most of its
-     data. A database will automatically be enabled and a database
-     and role created unless <xref
-     linkend="opt-services.discourse.database.host" /> is changed from
-     its default of <literal>null</literal> or <xref
-     linkend="opt-services.discourse.database.createLocally" /> is set
-     to <literal>false</literal>.
-   </para>
-
-   <para>
-     External database access can also be configured by setting
-     <xref linkend="opt-services.discourse.database.host" />, <xref
-     linkend="opt-services.discourse.database.username" /> and <xref
-     linkend="opt-services.discourse.database.passwordFile" /> as
-     appropriate. Note that you need to manually create a database
-     called <literal>discourse</literal> (or the name you chose in
-     <xref linkend="opt-services.discourse.database.name" />) and
-     allow the configured database user full access to it.
-   </para>
- </section>
-
- <section xml:id="module-services-discourse-mail">
-   <title>Email</title>
-   <para>
-     In addition to the basic setup, you'll want to configure an SMTP
-     server <productname>Discourse</productname> can use to send user
-     registration and password reset emails, among others. You can
-     also optionally let <productname>Discourse</productname> receive
-     email, which enables people to reply to threads and conversations
-     via email.
-   </para>
-
-   <para>
-     A basic setup which assumes you want to use your configured <link
-     linkend="opt-services.discourse.hostname">hostname</link> as
-     email domain can be done like this:
-
-<programlisting>
-services.discourse = {
-  <link linkend="opt-services.discourse.enable">enable</link> = true;
-  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
-  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
-  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
-  admin = {
-    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
-    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
-    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
-    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
-  };
-  mail.outgoing = {
-    <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
-    <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
-    <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
-    <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
-  };
-  <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
-  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
-};
-</programlisting>
-
-     This assumes you have set up an MX record for the address you've
-     set in <link linkend="opt-services.discourse.hostname">hostname</link> and
-     requires proper SPF, DKIM and DMARC configuration to be done for
-     the domain you're sending from, in order for email to be reliably delivered.
-   </para>
-
-   <para>
-     If you want to use a different domain for your outgoing email
-     (for example <literal>example.com</literal> instead of
-     <literal>discourse.example.com</literal>) you should set
-     <xref linkend="opt-services.discourse.mail.notificationEmailAddress" /> and
-     <xref linkend="opt-services.discourse.mail.contactEmailAddress" /> manually.
-   </para>
-
-   <note>
-     <para>
-       Setup of TLS for incoming email is currently only configured
-       automatically when a regular TLS certificate is used, i.e. when
-       <xref linkend="opt-services.discourse.sslCertificate" /> and
-       <xref linkend="opt-services.discourse.sslCertificateKey" /> are
-       set.
-     </para>
-   </note>
-
- </section>
-
- <section xml:id="module-services-discourse-settings">
-   <title>Additional settings</title>
-   <para>
-     Additional site settings and backend settings, for which no
-     explicit <productname>NixOS</productname> options are provided,
-     can be set in <xref linkend="opt-services.discourse.siteSettings" /> and
-     <xref linkend="opt-services.discourse.backendSettings" /> respectively.
-   </para>
-
-   <section xml:id="module-services-discourse-site-settings">
-     <title>Site settings</title>
-     <para>
-       <quote>Site settings</quote> are the settings that can be
-       changed through the <productname>Discourse</productname>
-       UI. Their <emphasis>default</emphasis> values can be set using
-       <xref linkend="opt-services.discourse.siteSettings" />.
-     </para>
-
-     <para>
-       Settings are expressed as a Nix attribute set which matches the
-       structure of the configuration in
-       <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">config/site_settings.yml</link>.
-       To find a setting's path, you only need to care about the first
-       two levels; i.e. its category (e.g. <literal>login</literal>)
-       and name (e.g. <literal>invite_only</literal>).
-     </para>
-
-     <para>
-       Settings 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.
-     </para>
-   </section>
-
-   <section xml:id="module-services-discourse-backend-settings">
-     <title>Backend settings</title>
-     <para>
-       Settings are expressed as a Nix attribute set which matches the
-       structure of the configuration in
-       <link xlink:href="https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf">config/discourse.conf</link>.
-       Empty parameters can be defined by setting them to
-       <literal>null</literal>.
-     </para>
-   </section>
-
-   <section xml:id="module-services-discourse-settings-example">
-     <title>Example</title>
-     <para>
-       The following example sets the title and description of the
-       <productname>Discourse</productname> instance and enables
-       <productname>GitHub</productname> login in the site settings,
-       and changes a few request limits in the backend settings:
-<programlisting>
-services.discourse = {
-  <link linkend="opt-services.discourse.enable">enable</link> = true;
-  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
-  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
-  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
-  admin = {
-    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
-    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
-    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
-    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
-  };
-  mail.outgoing = {
-    <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
-    <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
-    <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
-    <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
-  };
-  <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
-  <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = {
-    required = {
-      title = "My Cats";
-      site_description = "Discuss My Cats (and be nice plz)";
-    };
-    login = {
-      enable_github_logins = true;
-      github_client_id = "a2f6dfe838cb3206ce20";
-      github_client_secret._secret = /run/keys/discourse_github_client_secret;
-    };
-  };
-  <link linkend="opt-services.discourse.backendSettings">backendSettings</link> = {
-    max_reqs_per_ip_per_minute = 300;
-    max_reqs_per_ip_per_10_seconds = 60;
-    max_asset_reqs_per_ip_per_10_seconds = 250;
-    max_reqs_per_ip_mode = "warn+block";
-  };
-  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
-};
-</programlisting>
-     </para>
-     <para>
-       In the resulting site settings file, the
-       <literal>login.github_client_secret</literal> key will be set
-       to the contents of the
-       <filename>/run/keys/discourse_github_client_secret</filename>
-       file.
-     </para>
-   </section>
- </section>
-  <section xml:id="module-services-discourse-plugins">
-    <title>Plugins</title>
-    <para>
-      You can install <productname>Discourse</productname> plugins
-      using the <xref linkend="opt-services.discourse.plugins" />
-      option. Pre-packaged plugins are provided in
-      <literal>&lt;your_discourse_package_here&gt;.plugins</literal>. If
-      you want the full suite of plugins provided through
-      <literal>nixpkgs</literal>, you can also set the <xref
-      linkend="opt-services.discourse.package" /> option to
-      <literal>pkgs.discourseAllPlugins</literal>.
-    </para>
-
-    <para>
-      Plugins can be built with the
-      <literal>&lt;your_discourse_package_here&gt;.mkDiscoursePlugin</literal>
-      function. Normally, it should suffice to provide a
-      <literal>name</literal> and <literal>src</literal> attribute. If
-      the plugin has Ruby dependencies, however, they need to be
-      packaged in accordance with the <link
-      xlink:href="https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby">Developing
-      with Ruby</link> section of the Nixpkgs manual and the
-      appropriate gem options set in <literal>bundlerEnvArgs</literal>
-      (normally <literal>gemdir</literal> is sufficient). A plugin's
-      Ruby dependencies are listed in its
-      <filename>plugin.rb</filename> file as function calls to
-      <literal>gem</literal>. To construct the corresponding
-      <filename>Gemfile</filename> manually, run <command>bundle
-      init</command>, then add the <literal>gem</literal> lines to it
-      verbatim.
-    </para>
-
-    <para>
-      Much of the packaging can be done automatically by the
-      <filename>nixpkgs/pkgs/servers/web-apps/discourse/update.py</filename>
-      script - just add the plugin to the <literal>plugins</literal>
-      list in the <function>update_plugins</function> function and run
-      the script:
-      <programlisting language="bash">
-./update.py update-plugins
-</programlisting>
-    </para>
-
-    <para>
-      Some plugins provide <link
-      linkend="module-services-discourse-site-settings">site
-      settings</link>. Their defaults can be configured using <xref
-      linkend="opt-services.discourse.siteSettings" />, just like
-      regular site settings. To find the names of these settings, look
-      in the <literal>config/settings.yml</literal> file of the plugin
-      repo.
-    </para>
-
-    <para>
-      For example, to add the <link
-      xlink:href="https://github.com/discourse/discourse-spoiler-alert">discourse-spoiler-alert</link>
-      and <link
-      xlink:href="https://github.com/discourse/discourse-solved">discourse-solved</link>
-      plugins, and disable <literal>discourse-spoiler-alert</literal>
-      by default:
-
-<programlisting>
-services.discourse = {
-  <link linkend="opt-services.discourse.enable">enable</link> = true;
-  <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
-  <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
-  <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
-  admin = {
-    <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
-    <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
-    <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
-    <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
-  };
-  mail.outgoing = {
-    <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
-    <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
-    <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
-    <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
-  };
-  <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
-  <link linkend="opt-services.discourse.mail.incoming.enable">plugins</link> = with config.services.discourse.package.plugins; [
-    discourse-spoiler-alert
-    discourse-solved
-  ];
-  <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = {
-    plugins = {
-      spoiler_enabled = false;
-    };
-  };
-  <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
-};
-</programlisting>
-
-    </para>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/documize.nix b/nixpkgs/nixos/modules/services/web-apps/documize.nix
index 4353e3c24453..f70da0829f44 100644
--- a/nixpkgs/nixos/modules/services/web-apps/documize.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/documize.nix
@@ -12,7 +12,7 @@ let
 
 in {
   options.services.documize = {
-    enable = mkEnableOption "Documize Wiki";
+    enable = mkEnableOption (lib.mdDoc "Documize Wiki");
 
     stateDirectoryName = mkOption {
       type = types.str;
@@ -85,37 +85,24 @@ in {
     dbtype = mkOption {
       type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ];
       default = "postgresql";
-      description = ''
-        Specify the database provider:
-        <simplelist type='inline'>
-          <member><literal>mysql</literal></member>
-          <member><literal>percona</literal></member>
-          <member><literal>mariadb</literal></member>
-          <member><literal>postgresql</literal></member>
-          <member><literal>sqlserver</literal></member>
-        </simplelist>
+      description = lib.mdDoc ''
+        Specify the database provider: `mysql`, `percona`, `mariadb`, `postgresql`, `sqlserver`
       '';
     };
 
     db = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Database specific connection string for example:
-        <itemizedlist>
-        <listitem><para>MySQL/Percona/MariaDB:
-          <literal>user:password@tcp(host:3306)/documize</literal>
-        </para></listitem>
-        <listitem><para>MySQLv8+:
-          <literal>user:password@tcp(host:3306)/documize?allowNativePasswords=true</literal>
-        </para></listitem>
-        <listitem><para>PostgreSQL:
-          <literal>host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable</literal>
-        </para></listitem>
-        <listitem><para>MSSQL:
-          <literal>sqlserver://username:password@localhost:1433?database=Documize</literal> or
-          <literal>sqlserver://sa@localhost/SQLExpress?database=Documize</literal>
-        </para></listitem>
-        </itemizedlist>
+        - MySQL/Percona/MariaDB:
+          `user:password@tcp(host:3306)/documize`
+        - MySQLv8+:
+          `user:password@tcp(host:3306)/documize?allowNativePasswords=true`
+        - PostgreSQL:
+          `host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable`
+        - MSSQL:
+          `sqlserver://username:password@localhost:1433?database=Documize` or
+          `sqlserver://sa@localhost/SQLExpress?database=Documize`
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
index a148dec8199a..9e685c127da7 100644
--- a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
@@ -3,64 +3,184 @@
 with lib;
 
 let
+  inherit (lib.options) showOption showFiles;
+
   cfg = config.services.dokuwiki;
   eachSite = cfg.sites;
   user = "dokuwiki";
   webserver = config.services.${cfg.webserver};
-  stateDir = hostName: "/var/lib/dokuwiki/${hostName}/data";
 
-  dokuwikiAclAuthConfig = hostName: cfg: pkgs.writeText "acl.auth-${hostName}.php" ''
+  mkPhpIni = generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {} " = ";
+  };
+  mkPhpPackage = cfg: cfg.phpPackage.buildEnv {
+    extraConfig = mkPhpIni cfg.phpOptions;
+  };
+
+  dokuwikiAclAuthConfig = hostName: cfg: let
+    inherit (cfg) acl;
+    acl_gen = concatMapStringsSep "\n" (l: "${l.page} \t ${l.actor} \t ${toString l.level}");
+  in pkgs.writeText "acl.auth-${hostName}.php" ''
     # acl.auth.php
     # <?php exit()?>
     #
     # Access Control Lists
     #
-    ${toString cfg.acl}
+    ${if isString acl then acl else acl_gen acl}
   '';
 
-  dokuwikiLocalConfig = hostName: cfg: pkgs.writeText "local-${hostName}.php" ''
-    <?php
-    $conf['savedir'] = '${cfg.stateDir}';
-    $conf['superuser'] = '${toString cfg.superUser}';
-    $conf['useacl'] = '${toString cfg.aclUse}';
-    $conf['disableactions'] = '${cfg.disableActions}';
-    ${toString cfg.extraConfig}
+  mergeConfig = cfg: {
+    useacl = false; # Dokuwiki default
+    savedir = cfg.stateDir;
+  } // cfg.settings;
+
+  writePhpFile = name: text: pkgs.writeTextFile {
+    inherit name;
+    text = "<?php\n${text}";
+    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
+  };
+
+  mkPhpValue = v: let
+    isHasAttr = s: isAttrs v && hasAttr s v;
+  in
+    if isString v then escapeShellArg v
+    # NOTE: If any value contains a , (comma) this will not get escaped
+    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
+    else if isInt v then toString v
+    else if isBool v then toString (if v then 1 else 0)
+    else if isHasAttr "_file" then "trim(file_get_contents(${lib.escapeShellArg v._file}))"
+    else if isHasAttr "_raw" then v._raw
+    else abort "The dokuwiki localConf value ${lib.generators.toPretty {} v} can not be encoded."
+  ;
+
+  mkPhpAttrVals = v: flatten (mapAttrsToList mkPhpKeyVal v);
+  mkPhpKeyVal = k: v: let
+    values = if (isAttrs v && (hasAttr "_file" v || hasAttr "_raw" v )) || !isAttrs v then
+      [" = ${mkPhpValue v};"]
+    else
+      mkPhpAttrVals v;
+  in map (e: "[${escapeShellArg k}]${e}") (flatten values);
+
+  dokuwikiLocalConfig = hostName: cfg: let
+    conf_gen = c: map (v: "$conf${v}") (mkPhpAttrVals c);
+  in writePhpFile "local-${hostName}.php" ''
+    ${concatStringsSep "\n" (conf_gen cfg.mergedConfig)}
   '';
 
-  dokuwikiPluginsLocalConfig = hostName: cfg: pkgs.writeText "plugins.local-${hostName}.php" ''
-    <?php
-    ${cfg.pluginsConfig}
+  dokuwikiPluginsLocalConfig = hostName: cfg: let
+    pc = cfg.pluginsConfig;
+    pc_gen = pc: concatStringsSep "\n" (mapAttrsToList (n: v: "$plugins['${n}'] = ${boolToString v};") pc);
+  in writePhpFile "plugins.local-${hostName}.php" ''
+    ${if isString pc then pc else pc_gen pc}
   '';
 
 
-  pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
-    pname = "dokuwiki-${hostName}";
-    version = src.version;
-    src = cfg.package;
+  pkg = hostName: cfg: cfg.package.combine {
+    inherit (cfg) plugins templates;
+
+    pname = p: "${p.pname}-${hostName}";
+
+    basePackage = cfg.package;
+    localConfig = dokuwikiLocalConfig hostName cfg;
+    pluginsConfig = dokuwikiPluginsLocalConfig hostName cfg;
+    aclConfig = if cfg.settings.useacl && cfg.acl != null then dokuwikiAclAuthConfig hostName cfg else null;
+  };
 
-    installPhase = ''
-      mkdir -p $out
-      cp -r * $out/
+  aclOpts = { ... }: {
+    options = {
 
-      # symlink the dokuwiki config
-      ln -s ${dokuwikiLocalConfig hostName cfg} $out/share/dokuwiki/local.php
+      page = mkOption {
+        type = types.str;
+        description = lib.mdDoc "Page or namespace to restrict";
+        example = "start";
+      };
 
-      # symlink plugins config
-      ln -s ${dokuwikiPluginsLocalConfig hostName cfg} $out/share/dokuwiki/plugins.local.php
+      actor = mkOption {
+        type = types.str;
+        description = lib.mdDoc "User or group to restrict";
+        example = "@external";
+      };
 
-      # symlink acl
-      ln -s ${dokuwikiAclAuthConfig hostName cfg} $out/share/dokuwiki/acl.auth.php
+      level = let
+        available = {
+          "none" = 0;
+          "read" = 1;
+          "edit" = 2;
+          "create" = 4;
+          "upload" = 8;
+          "delete" = 16;
+        };
+      in mkOption {
+        type = types.enum ((attrValues available) ++ (attrNames available));
+        apply = x: if isInt x then x else available.${x};
+        description = lib.mdDoc ''
+          Permission level to restrict the actor(s) to.
+          See <https://www.dokuwiki.org/acl#background_info> for explanation
+        '';
+        example = "read";
+      };
+    };
+  };
 
-      # symlink additional plugin(s) and templates(s)
-      ${concatMapStringsSep "\n" (template: "ln -s ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates}
-      ${concatMapStringsSep "\n" (plugin: "ln -s ${plugin} $out/share/dokuwiki/lib/plugins/${plugin.name}") cfg.plugins}
-    '';
+  # The current implementations of `doRename`,  `mkRenamedOptionModule` do not provide the full options path when used with submodules.
+  # They would only show `settings.useacl' instead of `services.dokuwiki.sites."site1.local".settings.useacl'
+  # The partial re-implementation of these functions is done to help users in debugging by showing the full path.
+  mkRenamed = from: to: { config, options, name, ... }: let
+    pathPrefix = [ "services" "dokuwiki" "sites" name ];
+    fromPath = pathPrefix  ++ from;
+    fromOpt = getAttrFromPath from options;
+    toOp = getAttrsFromPath to config;
+    toPath = pathPrefix ++ to;
+  in {
+    options = setAttrByPath from (mkOption {
+      visible = false;
+      description = lib.mdDoc "Alias of {option}${showOption toPath}";
+      apply = x: builtins.trace "Obsolete option `${showOption fromPath}' is used. It was renamed to ${showOption toPath}" toOp;
+    });
+    config = mkMerge [
+      {
+        warnings = optional fromOpt.isDefined
+          "The option `${showOption fromPath}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption toPath}'.";
+      }
+      (lib.modules.mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt)
+    ];
   };
 
-  siteOpts = { config, lib, name, ... }:
+  siteOpts = { options, config, lib, name, ... }:
     {
+      imports = [
+        (mkRenamed [ "aclUse" ] [ "settings" "useacl" ])
+        (mkRenamed [ "superUser" ] [ "settings" "superuser" ])
+        (mkRenamed [ "disableActions" ] [ "settings"  "disableactions" ])
+        ({ config, options, ... }: let
+          showPath = suffix: lib.options.showOption ([ "services" "dokuwiki" "sites" name ] ++ suffix);
+          replaceExtraConfig = "Please use `${showPath ["settings"]}' to pass structured settings instead.";
+          ecOpt = options.extraConfig;
+          ecPath = showPath [ "extraConfig" ];
+        in {
+          options.extraConfig = mkOption {
+            visible = false;
+            apply = x: throw "The option ${ecPath} can no longer be used since it's been removed.\n${replaceExtraConfig}";
+          };
+          config.assertions = [
+            {
+              assertion = !ecOpt.isDefined;
+              message = "The option definition `${ecPath}' in ${showFiles ecOpt.files} no longer has any effect; please remove it.\n${replaceExtraConfig}";
+            }
+            {
+              assertion = config.mergedConfig.useacl -> (config.acl != null || config.aclFile != null);
+              message = "Either ${showPath [ "acl" ]} or ${showPath [ "aclFile" ]} is mandatory if ${showPath [ "settings" "useacl" ]} is true";
+            }
+            {
+              assertion = config.usersFile != null -> config.mergedConfig.useacl != false;
+              message = "${showPath [ "settings" "useacl" ]} is required when ${showPath [ "usersFile" ]} is set (Currently defined as `${config.usersFile}' in ${showFiles options.usersFile.files}).";
+            }
+          ];
+        })
+      ];
+
       options = {
-        enable = mkEnableOption "DokuWiki web application.";
+        enable = mkEnableOption (lib.mdDoc "DokuWiki web application");
 
         package = mkOption {
           type = types.package;
@@ -76,9 +196,22 @@ let
         };
 
         acl = mkOption {
-          type = types.nullOr types.lines;
+          type = with types; nullOr (listOf (submodule aclOpts));
           default = null;
-          example = "*               @ALL               8";
+          example = literalExpression ''
+            [
+              {
+                page = "start";
+                actor = "@external";
+                level = "read";
+              }
+              {
+                page = "*";
+                actor = "@users";
+                level = "upload";
+              }
+            ]
+          '';
           description = lib.mdDoc ''
             Access Control Lists: see <https://www.dokuwiki.org/acl>
             Mutually exclusive with services.dokuwiki.aclFile
@@ -91,7 +224,7 @@ let
 
         aclFile = mkOption {
           type = with types; nullOr str;
-          default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
+          default = if (config.mergedConfig.useacl && config.acl == null) then "/var/lib/dokuwiki/${name}/acl.auth.php" else null;
           description = lib.mdDoc ''
             Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
             Mutually exclusive with services.dokuwiki.acl which is preferred.
@@ -101,83 +234,56 @@ let
           example = "/var/lib/dokuwiki/${name}/acl.auth.php";
         };
 
-        aclUse = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Necessary for users to log in into the system.
-            Also limits anonymous users. When disabled,
-            everyone is able to create and edit content.
-          '';
-        };
-
         pluginsConfig = mkOption {
-          type = types.lines;
-          default = ''
-            $plugins['authad'] = 0;
-            $plugins['authldap'] = 0;
-            $plugins['authmysql'] = 0;
-            $plugins['authpgsql'] = 0;
-          '';
+          type = with types; attrsOf bool;
+          default = {
+            authad = false;
+            authldap = false;
+            authmysql = false;
+            authpgsql = false;
+          };
           description = lib.mdDoc ''
             List of the dokuwiki (un)loaded plugins.
           '';
         };
 
-        superUser = mkOption {
-          type = types.nullOr types.str;
-          default = "@admin";
-          description = lib.mdDoc ''
-            You can set either a username, a list of usernames (“admin1,admin2”),
-            or the name of a group by prepending an @ char to the groupname
-            Consult documentation <https://www.dokuwiki.org/config:superuser> for further instructions.
-          '';
-        };
-
         usersFile = mkOption {
           type = with types; nullOr str;
-          default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
-          description = ''
+          default = if config.mergedConfig.useacl then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
+          description = lib.mdDoc ''
             Location of the dokuwiki users file. List of users. Format:
-            login:passwordhash:Real Name:email:groups,comma,separated
-            Create passwordHash easily by using:$ mkpasswd -5 password `pwgen 8 1`
-            Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist"/>
+
+                login:passwordhash:Real Name:email:groups,comma,separated
+
+            Create passwordHash easily by using:
+
+                mkpasswd -5 password `pwgen 8 1`
+
+            Example: <https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist>
             '';
           example = "/var/lib/dokuwiki/${name}/users.auth.php";
         };
 
-        disableActions = mkOption {
-          type = types.nullOr types.str;
-          default = "";
-          example = "search,register";
-          description = lib.mdDoc ''
-            Disable individual action modes. Refer to
-            <https://www.dokuwiki.org/config:action_modes>
-            for details on supported values.
-          '';
-        };
-
         plugins = mkOption {
           type = types.listOf types.path;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
                 List of path(s) to respective plugin(s) which are copied from the 'plugin' directory.
-                <note><para>These plugins need to be packaged before use, see example.</para></note>
+
+                ::: {.note}
+                These plugins need to be packaged before use, see example.
+                :::
           '';
           example = literalExpression ''
                 let
-                  # Let's package the icalevents plugin
-                  plugin-icalevents = pkgs.stdenv.mkDerivation {
+                  plugin-icalevents = pkgs.stdenv.mkDerivation rec {
                     name = "icalevents";
-                    # Download the plugin from the dokuwiki site
-                    src = pkgs.fetchurl {
-                      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip";
-                      sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
+                    version = "2017-06-16";
+                    src = pkgs.fetchzip {
+                      stripRoot = false;
+                      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/''${version}/dokuwiki-plugin-icalevents-''${version}.zip";
+                      hash = "sha256-IPs4+qgEfe8AAWevbcCM9PnyI0uoyamtWeg4rEb+9Wc=";
                     };
-                    sourceRoot = ".";
-                    # We need unzip to build this package
-                    buildInputs = [ pkgs.unzip ];
-                    # Installing simply means copying all files to the output directory
                     installPhase = "mkdir -p $out; cp -R * $out/";
                   };
                 # And then pass this theme to the plugin list like this:
@@ -188,25 +294,26 @@ let
         templates = mkOption {
           type = types.listOf types.path;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
                 List of path(s) to respective template(s) which are copied from the 'tpl' directory.
-                <note><para>These templates need to be packaged before use, see example.</para></note>
+
+                ::: {.note}
+                These templates need to be packaged before use, see example.
+                :::
           '';
           example = literalExpression ''
                 let
-                  # Let's package the bootstrap3 theme
-                  template-bootstrap3 = pkgs.stdenv.mkDerivation {
-                    name = "bootstrap3";
-                    # Download the theme from the dokuwiki site
-                    src = pkgs.fetchurl {
-                      url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip";
-                      sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
-                    };
-                    # We need unzip to build this package
-                    buildInputs = [ pkgs.unzip ];
-                    # Installing simply means copying all files to the output directory
-                    installPhase = "mkdir -p $out; cp -R * $out/";
+                  template-bootstrap3 = pkgs.stdenv.mkDerivation rec {
+                  name = "bootstrap3";
+                  version = "2022-07-27";
+                  src = pkgs.fetchFromGitHub {
+                    owner = "giterlizzi";
+                    repo = "dokuwiki-template-bootstrap3";
+                    rev = "v''${version}";
+                    hash = "sha256-B3Yd4lxdwqfCnfmZdp+i/Mzwn/aEuZ0ovagDxuR6lxo=";
                   };
+                  installPhase = "mkdir -p $out; cp -R * $out/";
+                };
                 # And then pass this theme to the template list like this:
                 in [ template-bootstrap3 ]
           '';
@@ -228,26 +335,92 @@ let
           '';
         };
 
-        extraConfig = mkOption {
-          type = types.nullOr types.lines;
-          default = null;
-          example = ''
-            $conf['title'] = 'My Wiki';
-            $conf['userewrite'] = 1;
+        phpPackage = mkOption {
+          type = types.package;
+          relatedPackages = [ "php80" "php81" ];
+          default = pkgs.php81;
+          defaultText = "pkgs.php81";
+          description = lib.mdDoc ''
+            PHP package to use for this dokuwiki site.
           '';
+        };
+
+        phpOptions = mkOption {
+          type = types.attrsOf types.str;
+          default = {};
           description = lib.mdDoc ''
-            DokuWiki configuration. Refer to
-            <https://www.dokuwiki.org/config>
-            for details on supported values.
+            Options for PHP's php.ini file for this dokuwiki site.
+          '';
+          example = literalExpression ''
+          {
+            "opcache.interned_strings_buffer" = "8";
+            "opcache.max_accelerated_files" = "10000";
+            "opcache.memory_consumption" = "128";
+            "opcache.revalidate_freq" = "15";
+            "opcache.fast_shutdown" = "1";
+          }
           '';
         };
 
-      };
+        settings = mkOption {
+          type = types.attrsOf types.anything;
+          default = {
+            useacl = true;
+            superuser = "admin";
+          };
+          description = lib.mdDoc ''
+            Structural DokuWiki configuration.
+            Refer to <https://www.dokuwiki.org/config>
+            for details and supported values.
+            Settings can either be directly set from nix,
+            loaded from a file using `._file` or obtained from any
+            PHP function calls using `._raw`.
+          '';
+          example = literalExpression ''
+            {
+              title = "My Wiki";
+              userewrite = 1;
+              disableactions = [ "register" ]; # Will be concatenated with commas
+              plugin.smtp = {
+                smtp_pass._file = "/var/run/secrets/dokuwiki/smtp_pass";
+                smtp_user._raw = "getenv('DOKUWIKI_SMTP_USER')";
+              };
+            }
+          '';
+        };
 
+        mergedConfig = mkOption {
+          readOnly = true;
+          default = mergeConfig config;
+          defaultText = literalExpression ''
+            {
+              useacl = true;
+            }
+          '';
+          description = lib.mdDoc ''
+            Read only representation of the final configuration.
+          '';
+        };
+
+      # Required for the mkRenamedOptionModule
+      # TODO: Remove me once https://github.com/NixOS/nixpkgs/issues/96006 is fixed
+      # or we don't have any more notes about the removal of extraConfig, ...
+      warnings = mkOption {
+        type = types.listOf types.unspecified;
+        default = [ ];
+        visible = false;
+        internal = true;
+      };
+      assertions = mkOption {
+        type = types.listOf types.unspecified;
+        default = [ ];
+        visible = false;
+        internal = true;
+      };
     };
+  };
 in
 {
-  # interface
   options = {
     services.dokuwiki = {
 
@@ -266,8 +439,8 @@ in
           Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
           See [](#opt-services.nginx.virtualHosts) for further information.
 
-          Further apache2 configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
-          See [](#opt-services.httpd.virtualHosts) for further information.
+          Further caddy configuration can be done by adapting `services.caddy.virtualHosts.<name>`.
+          See [](#opt-services.caddy.virtualHosts) for further information.
         '';
       };
 
@@ -277,29 +450,19 @@ in
   # implementation
   config = mkIf (eachSite != {}) (mkMerge [{
 
-    assertions = flatten (mapAttrsToList (hostName: cfg:
-    [{
-      assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null);
-      message = "Either services.dokuwiki.sites.${hostName}.acl or services.dokuwiki.sites.${hostName}.aclFile is mandatory if aclUse true";
-    }
-    {
-      assertion = cfg.usersFile != null -> cfg.aclUse != false;
-      message = "services.dokuwiki.sites.${hostName}.aclUse must must be true if usersFile is not null";
-    }
-    ]) eachSite);
+    warnings = flatten (mapAttrsToList (_: cfg: cfg.warnings) eachSite);
+
+    assertions = flatten (mapAttrsToList (_: cfg: cfg.assertions) eachSite);
 
     services.phpfpm.pools = mapAttrs' (hostName: cfg: (
       nameValuePair "dokuwiki-${hostName}" {
         inherit user;
         group = webserver.group;
 
-        phpPackage = pkgs.php81;
-        phpEnv = {
-          DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}";
-          DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}";
-        } // optionalAttrs (cfg.usersFile != null) {
+        phpPackage = mkPhpPackage cfg;
+        phpEnv = optionalAttrs (cfg.usersFile != null) {
           DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
-        } //optionalAttrs (cfg.aclUse) {
+        } // optionalAttrs (cfg.mergedConfig.useacl) {
           DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig hostName cfg}" else "${toString cfg.aclFile}";
         };
 
@@ -314,16 +477,17 @@ in
 
   {
     systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
-      "d ${stateDir hostName}/attic 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/cache 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/index 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/locks 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/media 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/media_attic 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/media_meta 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/meta 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/pages 0750 ${user} ${webserver.group} - -"
-      "d ${stateDir hostName}/tmp 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/attic 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/cache 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/index 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/locks 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/log 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/media 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/media_attic 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/media_meta 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/meta 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/pages 0750 ${user} ${webserver.group} - -"
+      "d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -"
     ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist"
     ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist"
     ) eachSite);
@@ -347,7 +511,7 @@ in
           };
 
           "~ ^/data/" = {
-            root = "${stateDir hostName}";
+            root = "${cfg.stateDir}";
             extraConfig = "internal;";
           };
 
@@ -433,5 +597,6 @@ in
     _1000101
     onny
     dandellion
+    e1mo
   ];
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/dolibarr.nix b/nixpkgs/nixos/modules/services/web-apps/dolibarr.nix
new file mode 100644
index 000000000000..453229c130c2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/dolibarr.nix
@@ -0,0 +1,323 @@
+{ config, pkgs, lib, ... }:
+let
+  inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types;
+
+  package = pkgs.dolibarr.override { inherit (cfg) stateDir; };
+
+  cfg = config.services.dolibarr;
+  vhostCfg = lib.optionalAttrs (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
+
+  mkConfigFile = filename: settings:
+    let
+      # hack in special logic for secrets so we read them from a separate file avoiding the nix store
+      secretKeys = [ "force_install_databasepass" "dolibarr_main_db_pass" "dolibarr_main_instance_unique_id" ];
+
+      toStr = k: v:
+        if (any (str: k == str) secretKeys) then v
+        else if isString v then "'${v}'"
+        else if isBool v then boolToString v
+        else if v == null then "null"
+        else toString v
+      ;
+    in
+      pkgs.writeText filename ''
+        <?php
+        ${concatStringsSep "\n" (mapAttrsToList (k: v: "\$${k} = ${toStr k v};") settings)}
+      '';
+
+  # see https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/install/install.forced.sample.php for all possible values
+  install = {
+    force_install_noedit = 2;
+    force_install_main_data_root = "${cfg.stateDir}/documents";
+    force_install_nophpinfo = true;
+    force_install_lockinstall = "444";
+    force_install_distrib = "nixos";
+    force_install_type = "mysqli";
+    force_install_dbserver = cfg.database.host;
+    force_install_port = toString cfg.database.port;
+    force_install_database = cfg.database.name;
+    force_install_databaselogin = cfg.database.user;
+
+    force_install_mainforcehttps = vhostCfg.forceSSL or false;
+    force_install_createuser = false;
+    force_install_dolibarrlogin = null;
+  } // optionalAttrs (cfg.database.passwordFile != null) {
+    force_install_databasepass = ''file_get_contents("${cfg.database.passwordFile}")'';
+  };
+in
+{
+  # interface
+  options.services.dolibarr = {
+    enable = mkEnableOption (lib.mdDoc "dolibarr");
+
+    domain = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc ''
+        Domain name of your server.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "dolibarr";
+      description = lib.mdDoc ''
+        User account under which dolibarr runs.
+
+        ::: {.note}
+        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 dolibarr application starts.
+        :::
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "dolibarr";
+      description = lib.mdDoc ''
+        Group account under which dolibarr runs.
+
+        ::: {.note}
+        If left as the default value this group will automatically be created
+        on system activation, otherwise you are responsible for
+        ensuring the group exists before the dolibarr application starts.
+        :::
+      '';
+    };
+
+    stateDir = mkOption {
+      type = types.str;
+      default = "/var/lib/dolibarr";
+      description = lib.mdDoc ''
+        State and configuration directory dolibarr will use.
+      '';
+    };
+
+    database = {
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "Database host address.";
+      };
+      port = mkOption {
+        type = types.port;
+        default = 3306;
+        description = lib.mdDoc "Database host port.";
+      };
+      name = mkOption {
+        type = types.str;
+        default = "dolibarr";
+        description = lib.mdDoc "Database name.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = "dolibarr";
+        description = lib.mdDoc "Database username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/dolibarr-dbpassword";
+        description = lib.mdDoc "Database password file.";
+      };
+      createLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Create the database and database user locally.";
+      };
+    };
+
+    settings = mkOption {
+      type = with types; (attrsOf (oneOf [ bool int str ]));
+      default = { };
+      description = lib.mdDoc "Dolibarr settings, see <https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/conf/conf.php.example> for details.";
+    };
+
+    nginx = mkOption {
+      type = types.nullOr (types.submodule (
+        lib.recursiveUpdate
+          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
+          {
+            # enable encryption by default,
+            # as sensitive login and Dolibarr (ERP) data should not be transmitted in clear text.
+            options.forceSSL.default = true;
+            options.enableACME.default = true;
+          }
+      ));
+      default = null;
+      example = lib.literalExpression ''
+        {
+          serverAliases = [
+            "dolibarr.''${config.networking.domain}"
+            "erp.''${config.networking.domain}"
+          ];
+          enableACME = false;
+        }
+      '';
+      description = lib.mdDoc ''
+          With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr.
+          Set to {} if you do not need any customization to the virtual host.
+          If enabled, then by default, the {option}`serverName` is
+          `''${domain}`,
+          SSL is active, and certificates are acquired via ACME.
+          If this is set to null (the default), no nginx virtualHost will be configured.
+      '';
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = lib.mdDoc ''
+        Options for the Dolibarr PHP pool. See the documentation on [`php-fpm.conf`](https://www.php.net/manual/en/install.fpm.configuration.php)
+        for details on configuration directives.
+      '';
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable (mkMerge [
+    {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+        message = "services.dolibarr.database.user must match services.dolibarr.user if the database is to be automatically provisioned";
+      }
+    ];
+
+    services.dolibarr.settings = {
+      dolibarr_main_url_root = "https://${cfg.domain}";
+      dolibarr_main_document_root = "${package}/htdocs";
+      dolibarr_main_url_root_alt = "/custom";
+      dolibarr_main_data_root = "${cfg.stateDir}/documents";
+
+      dolibarr_main_db_host = cfg.database.host;
+      dolibarr_main_db_port = toString cfg.database.port;
+      dolibarr_main_db_name = cfg.database.name;
+      dolibarr_main_db_prefix = "llx_";
+      dolibarr_main_db_user = cfg.database.user;
+      dolibarr_main_db_pass = mkIf (cfg.database.passwordFile != null) ''
+        file_get_contents("${cfg.database.passwordFile}")
+      '';
+      dolibarr_main_db_type = "mysqli";
+      dolibarr_main_db_character_set = mkDefault "utf8";
+      dolibarr_main_db_collation = mkDefault "utf8_unicode_ci";
+
+      # Authentication settings
+      dolibarr_main_authentication = mkDefault "dolibarr";
+
+      # Security settings
+      dolibarr_main_prod = true;
+      dolibarr_main_force_https = vhostCfg.forceSSL or false;
+      dolibarr_main_restrict_os_commands = "${pkgs.mariadb}/bin/mysqldump, ${pkgs.mariadb}/bin/mysql";
+      dolibarr_nocsrfcheck = false;
+      dolibarr_main_instance_unique_id = ''
+        file_get_contents("${cfg.stateDir}/dolibarr_main_instance_unique_id")
+      '';
+      dolibarr_mailing_limit_sendbyweb = false;
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group}"
+      "d '${cfg.stateDir}/documents' 0750 ${cfg.user} ${cfg.group}"
+      "f '${cfg.stateDir}/conf.php' 0660 ${cfg.user} ${cfg.group}"
+      "L '${cfg.stateDir}/install.forced.php' - ${cfg.user} ${cfg.group} - ${mkConfigFile "install.forced.php" install}"
+    ];
+
+    services.mysql = mkIf cfg.database.createLocally {
+      enable = mkDefault true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.nginx.enable = mkIf (cfg.nginx != null) true;
+    services.nginx.virtualHosts."${cfg.domain}" = mkIf (cfg.nginx != null) (lib.mkMerge [
+      cfg.nginx
+      ({
+        root = lib.mkForce "${package}/htdocs";
+        locations."/".index = "index.php";
+        locations."~ [^/]\\.php(/|$)" = {
+          extraConfig = ''
+            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
+            fastcgi_pass unix:${config.services.phpfpm.pools.dolibarr.socket};
+          '';
+        };
+      })
+    ]);
+
+    systemd.services."phpfpm-dolibarr".after = mkIf cfg.database.createLocally [ "mysql.service" ];
+    services.phpfpm.pools.dolibarr = {
+      inherit (cfg) user group;
+      phpPackage = pkgs.php.buildEnv {
+        extensions = { enabled, all }: enabled ++ [ all.calendar ];
+        # recommended by dolibarr web application
+        extraConfig = ''
+          session.use_strict_mode = 1
+          session.cookie_samesite = "Lax"
+          ; open_basedir = "${package}/htdocs, ${cfg.stateDir}"
+          allow_url_fopen = 0
+          disable_functions = "pcntl_alarm, pcntl_fork, pcntl_waitpid, pcntl_wait, pcntl_wifexited, pcntl_wifstopped, pcntl_wifsignaled, pcntl_wifcontinued, pcntl_wexitstatus, pcntl_wtermsig, pcntl_wstopsig, pcntl_signal, pcntl_signal_get_handler, pcntl_signal_dispatch, pcntl_get_last_error, pcntl_strerror, pcntl_sigprocmask, pcntl_sigwaitinfo, pcntl_sigtimedwait, pcntl_exec, pcntl_getpriority, pcntl_setpriority, pcntl_async_signals"
+        '';
+      };
+
+      settings = {
+        "listen.mode" = "0660";
+        "listen.owner" = cfg.user;
+        "listen.group" = cfg.group;
+      } // cfg.poolConfig;
+    };
+
+    # there are several challenges with dolibarr and NixOS which we can address here
+    # - the dolibarr installer cannot be entirely automated, though it can partially be by including a file called install.forced.php
+    # - the dolibarr installer requires write access to its config file during installation, though not afterwards
+    # - the dolibarr config file generally holds secrets generated by the installer, though the config file is a php file so we can read and write these secrets from an external file
+    systemd.services.dolibarr-config = {
+      description = "dolibarr configuration file management via NixOS";
+      wantedBy = [ "multi-user.target" ];
+
+      script = ''
+        # extract the 'main instance unique id' secret that the dolibarr installer generated for us, store it in a file for use by our own NixOS generated configuration file
+        ${pkgs.php}/bin/php -r "include '${cfg.stateDir}/conf.php'; file_put_contents('${cfg.stateDir}/dolibarr_main_instance_unique_id', \$dolibarr_main_instance_unique_id);"
+
+        # replace configuration file generated by installer with the NixOS generated configuration file
+        install -m 644 ${mkConfigFile "conf.php" cfg.settings} '${cfg.stateDir}/conf.php'
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        RemainAfterExit = "yes";
+      };
+
+      unitConfig = {
+        ConditionFileNotEmpty = "${cfg.stateDir}/conf.php";
+      };
+    };
+
+    users.users.dolibarr = mkIf (cfg.user == "dolibarr" ) {
+      isSystemUser = true;
+      group = cfg.group;
+    };
+
+    users.groups = optionalAttrs (cfg.group == "dolibarr") {
+      dolibarr = { };
+    };
+  }
+  (mkIf (cfg.nginx != null) {
+    users.users."${config.services.nginx.group}".extraGroups = mkIf (cfg.nginx != null) [ cfg.group ];
+  })
+]);
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/fluidd.nix b/nixpkgs/nixos/modules/services/web-apps/fluidd.nix
index 8d6d48b3dd27..d4b86b9dfb39 100644
--- a/nixpkgs/nixos/modules/services/web-apps/fluidd.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/fluidd.nix
@@ -6,7 +6,7 @@ let
 in
 {
   options.services.fluidd = {
-    enable = mkEnableOption "Fluidd, a Klipper web interface for managing your 3d printer";
+    enable = mkEnableOption (lib.mdDoc "Fluidd, a Klipper web interface for managing your 3d printer");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/web-apps/freshrss.nix b/nixpkgs/nixos/modules/services/web-apps/freshrss.nix
new file mode 100644
index 000000000000..89e29f7ccb51
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/freshrss.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.freshrss;
+
+  poolName = "freshrss";
+in
+{
+  meta.maintainers = with maintainers; [ etu stunkymonkey ];
+
+  options.services.freshrss = {
+    enable = mkEnableOption (mdDoc "FreshRSS feed reader");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.freshrss;
+      defaultText = lib.literalExpression "pkgs.freshrss";
+      description = mdDoc "Which FreshRSS package to use.";
+    };
+
+    defaultUser = mkOption {
+      type = types.str;
+      default = "admin";
+      description = mdDoc "Default username for FreshRSS.";
+      example = "eva";
+    };
+
+    passwordFile = mkOption {
+      type = types.path;
+      description = mdDoc "Password for the defaultUser for FreshRSS.";
+      example = "/run/secrets/freshrss";
+    };
+
+    baseUrl = mkOption {
+      type = types.str;
+      description = mdDoc "Default URL for FreshRSS.";
+      example = "https://freshrss.example.com";
+    };
+
+    language = mkOption {
+      type = types.str;
+      default = "en";
+      description = mdDoc "Default language for FreshRSS.";
+      example = "de";
+    };
+
+    database = {
+      type = mkOption {
+        type = types.enum [ "sqlite" "pgsql" "mysql" ];
+        default = "sqlite";
+        description = mdDoc "Database type.";
+        example = "pgsql";
+      };
+
+      host = mkOption {
+        type = types.nullOr types.str;
+        default = "localhost";
+        description = mdDoc "Database host for FreshRSS.";
+      };
+
+      port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        description = mdDoc "Database port for FreshRSS.";
+        example = 3306;
+      };
+
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = "freshrss";
+        description = mdDoc "Database user for FreshRSS.";
+      };
+
+      passFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = mdDoc "Database password file for FreshRSS.";
+        example = "/run/secrets/freshrss";
+      };
+
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = "freshrss";
+        description = mdDoc "Database name for FreshRSS.";
+      };
+
+      tableprefix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = mdDoc "Database table prefix for FreshRSS.";
+        example = "freshrss";
+      };
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/freshrss";
+      description = mdDoc "Default data folder for FreshRSS.";
+      example = "/mnt/freshrss";
+    };
+
+    virtualHost = mkOption {
+      type = types.nullOr types.str;
+      default = "freshrss";
+      description = mdDoc ''
+        Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+      '';
+    };
+
+    pool = mkOption {
+      type = types.str;
+      default = poolName;
+      description = mdDoc ''
+        Name of the phpfpm pool to use and setup. If not specified, a pool will be created
+        with default values.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "freshrss";
+      description = lib.mdDoc "User under which Freshrss runs.";
+    };
+  };
+
+  config =
+    let
+      defaultServiceConfig = {
+        ReadWritePaths = "${cfg.dataDir}";
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        DeviceAllow = "";
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = 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;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
+        UMask = "0007";
+        Type = "oneshot";
+        User = cfg.user;
+        Group = config.users.users.${cfg.user}.group;
+        StateDirectory = "freshrss";
+        WorkingDirectory = cfg.package;
+      };
+    in
+    mkIf cfg.enable {
+      # Set up a Nginx virtual host.
+      services.nginx = mkIf (cfg.virtualHost != null) {
+        enable = true;
+        virtualHosts.${cfg.virtualHost} = {
+          root = "${cfg.package}/p";
+
+          # php files handling
+          # this regex is mandatory because of the API
+          locations."~ ^.+?\.php(/.*)?$".extraConfig = ''
+            fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
+            fastcgi_split_path_info ^(.+\.php)(/.*)$;
+            # By default, the variable PATH_INFO is not set under PHP-FPM
+            # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var!
+            # NOTE: the separate $path_info variable is required. For more details, see:
+            # https://trac.nginx.org/nginx/ticket/321
+            set $path_info $fastcgi_path_info;
+            fastcgi_param PATH_INFO $path_info;
+            include ${pkgs.nginx}/conf/fastcgi_params;
+            include ${pkgs.nginx}/conf/fastcgi.conf;
+          '';
+
+          locations."/" = {
+            tryFiles = "$uri $uri/ index.php";
+            index = "index.php index.html index.htm";
+          };
+        };
+      };
+
+      # Set up phpfpm pool
+      services.phpfpm.pools = mkIf (cfg.pool == poolName) {
+        ${poolName} = {
+          user = "freshrss";
+          settings = {
+            "listen.owner" = "nginx";
+            "listen.group" = "nginx";
+            "listen.mode" = "0600";
+            "pm" = "dynamic";
+            "pm.max_children" = 32;
+            "pm.max_requests" = 500;
+            "pm.start_servers" = 2;
+            "pm.min_spare_servers" = 2;
+            "pm.max_spare_servers" = 5;
+            "catch_workers_output" = true;
+          };
+          phpEnv = {
+            FRESHRSS_DATA_PATH = "${cfg.dataDir}";
+          };
+        };
+      };
+
+      users.users."${cfg.user}" = {
+        description = "FreshRSS service user";
+        isSystemUser = true;
+        group = "${cfg.user}";
+        home = cfg.dataDir;
+      };
+      users.groups."${cfg.user}" = { };
+
+      systemd.tmpfiles.rules = [
+        "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
+      ];
+
+      systemd.services.freshrss-config =
+        let
+          settingsFlags = concatStringsSep " \\\n    "
+            (mapAttrsToList (k: v: "${k} ${toString v}") {
+              "--default_user" = ''"${cfg.defaultUser}"'';
+              "--auth_type" = ''"form"'';
+              "--base_url" = ''"${cfg.baseUrl}"'';
+              "--language" = ''"${cfg.language}"'';
+              "--db-type" = ''"${cfg.database.type}"'';
+              # The following attributes are optional depending on the type of
+              # database.  Those that evaluate to null on the left hand side
+              # will be omitted.
+              ${if cfg.database.name != null then "--db-base" else null} = ''"${cfg.database.name}"'';
+              ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"'';
+              ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"'';
+              ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"'';
+              ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"'';
+            });
+        in
+        {
+          description = "Set up the state directory for FreshRSS before use";
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = defaultServiceConfig //{
+            Type = "oneshot";
+            User = "freshrss";
+            Group = "freshrss";
+            StateDirectory = "freshrss";
+            WorkingDirectory = cfg.package;
+          };
+          environment = {
+            FRESHRSS_DATA_PATH = cfg.dataDir;
+          };
+
+          script = ''
+            # do installation or reconfigure
+            if test -f ${cfg.dataDir}/config.php; then
+              # reconfigure with settings
+              ./cli/reconfigure.php ${settingsFlags}
+              ./cli/update-user.php --user ${cfg.defaultUser} --password "$(cat ${cfg.passwordFile})"
+            else
+              # check correct folders in data folder
+              ./cli/prepare.php
+              # install with settings
+              ./cli/do-install.php ${settingsFlags}
+              ./cli/create-user.php --user ${cfg.defaultUser} --password "$(cat ${cfg.passwordFile})"
+            fi
+          '';
+        };
+
+      systemd.services.freshrss-updater = {
+        description = "FreshRSS feed updater";
+        after = [ "freshrss-config.service" ];
+        wantedBy = [ "multi-user.target" ];
+        startAt = "*:0/5";
+        environment = {
+          FRESHRSS_DATA_PATH = cfg.dataDir;
+        };
+        serviceConfig = defaultServiceConfig //{
+          ExecStart = "${cfg.package}/app/actualize_script.php";
+        };
+      };
+    };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/galene.nix b/nixpkgs/nixos/modules/services/web-apps/galene.nix
index 2fef43753d79..747b85f94c65 100644
--- a/nixpkgs/nixos/modules/services/web-apps/galene.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/galene.nix
@@ -12,7 +12,7 @@ in
 {
   options = {
     services.galene = {
-      enable = mkEnableOption "Galene Service.";
+      enable = mkEnableOption (lib.mdDoc "Galene Service");
 
       stateDir = mkOption {
         default = defaultstateDir;
@@ -191,7 +191,7 @@ in
           RestrictRealtime = true;
           RestrictSUIDSGID = true;
           SystemCallArchitectures = "native";
-          SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
           UMask = "0077";
         }
       ];
diff --git a/nixpkgs/nixos/modules/services/web-apps/gerrit.nix b/nixpkgs/nixos/modules/services/web-apps/gerrit.nix
index 5b36204ff053..ab2eeea09bdc 100644
--- a/nixpkgs/nixos/modules/services/web-apps/gerrit.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/gerrit.nix
@@ -59,7 +59,7 @@ in
 {
   options = {
     services.gerrit = {
-      enable = mkEnableOption "Gerrit service";
+      enable = mkEnableOption (lib.mdDoc "Gerrit service");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix b/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix
index 9e278b41ad15..8db3a8ef3e81 100644
--- a/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix
@@ -7,7 +7,7 @@ let
 in {
   options = {
     services.gotify = {
-      enable = mkEnableOption "Gotify webserver";
+      enable = mkEnableOption (lib.mdDoc "Gotify webserver");
 
       port = mkOption {
         type = types.port;
diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.md b/nixpkgs/nixos/modules/services/web-apps/grocy.md
new file mode 100644
index 000000000000..62aad4b103df
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/grocy.md
@@ -0,0 +1,66 @@
+# Grocy {#module-services-grocy}
+
+[Grocy](https://grocy.info/) is a web-based self-hosted groceries
+& household management solution for your home.
+
+## Basic usage {#module-services-grocy-basic-usage}
+
+A very basic configuration may look like this:
+```
+{ pkgs, ... }:
+{
+  services.grocy = {
+    enable = true;
+    hostName = "grocy.tld";
+  };
+}
+```
+This configures a simple vhost using [nginx](#opt-services.nginx.enable)
+which listens to `grocy.tld` with fully configured ACME/LE (this can be
+disabled by setting [services.grocy.nginx.enableSSL](#opt-services.grocy.nginx.enableSSL)
+to `false`). After the initial setup the credentials `admin:admin`
+can be used to login.
+
+The application's state is persisted at `/var/lib/grocy/grocy.db` in a
+`sqlite3` database. The migration is applied when requesting the `/`-route
+of the application.
+
+## Settings {#module-services-grocy-settings}
+
+The configuration for `grocy` is located at `/etc/grocy/config.php`.
+By default, the following settings can be defined in the NixOS-configuration:
+```
+{ pkgs, ... }:
+{
+  services.grocy.settings = {
+    # The default currency in the system for invoices etc.
+    # Please note that exchange rates aren't taken into account, this
+    # is just the setting for what's shown in the frontend.
+    currency = "EUR";
+
+    # The display language (and locale configuration) for grocy.
+    culture = "de";
+
+    calendar = {
+      # Whether or not to show the week-numbers
+      # in the calendar.
+      showWeekNumber = true;
+
+      # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday,
+      # 2=Tuesday and so on).
+      firstDayOfWeek = 2;
+    };
+  };
+}
+```
+
+If you want to alter the configuration file on your own, you can do this manually with
+an expression like this:
+```
+{ lib, ... }:
+{
+  environment.etc."grocy/config.php".text = lib.mkAfter ''
+    // Arbitrary PHP code in grocy's configuration file
+  '';
+}
+```
diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.nix b/nixpkgs/nixos/modules/services/web-apps/grocy.nix
index 173dd63ddaa1..688367cafaf5 100644
--- a/nixpkgs/nixos/modules/services/web-apps/grocy.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/grocy.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.grocy;
 in {
   options.services.grocy = {
-    enable = mkEnableOption "grocy";
+    enable = mkEnableOption (lib.mdDoc "grocy");
 
     hostName = mkOption {
       type = types.str;
@@ -117,7 +117,9 @@ in {
 
       # PHP 8.0 is the only version which is supported/tested by upstream:
       # https://github.com/grocy/grocy/blob/v3.3.0/README.md#how-to-install
-      phpPackage = pkgs.php80;
+      # Compatibility with PHP 8.1 is available on their development branch:
+      # https://github.com/grocy/grocy/commit/38a4ad8ec480c29a1bff057b3482fd103b036848
+      phpPackage = pkgs.php81;
 
       inherit (cfg.phpfpm) settings;
 
@@ -167,6 +169,6 @@ in {
 
   meta = {
     maintainers = with maintainers; [ ma27 ];
-    doc = ./grocy.xml;
+    doc = ./grocy.md;
   };
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.xml b/nixpkgs/nixos/modules/services/web-apps/grocy.xml
deleted file mode 100644
index fdf6d00f4b12..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/grocy.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-grocy">
-
-  <title>Grocy</title>
-  <para>
-    <link xlink:href="https://grocy.info/">Grocy</link> is a web-based self-hosted groceries
-    &amp; household management solution for your home.
-  </para>
-
-  <section xml:id="module-services-grocy-basic-usage">
-   <title>Basic usage</title>
-   <para>
-    A very basic configuration may look like this:
-<programlisting>{ pkgs, ... }:
-{
-  services.grocy = {
-    <link linkend="opt-services.grocy.enable">enable</link> = true;
-    <link linkend="opt-services.grocy.hostName">hostName</link> = "grocy.tld";
-  };
-}</programlisting>
-    This configures a simple vhost using <link linkend="opt-services.nginx.enable">nginx</link>
-    which listens to <literal>grocy.tld</literal> with fully configured ACME/LE (this can be
-    disabled by setting <link linkend="opt-services.grocy.nginx.enableSSL">services.grocy.nginx.enableSSL</link>
-    to <literal>false</literal>). After the initial setup the credentials <literal>admin:admin</literal>
-    can be used to login.
-   </para>
-   <para>
-    The application's state is persisted at <literal>/var/lib/grocy/grocy.db</literal> in a
-    <package>sqlite3</package> database. The migration is applied when requesting the <literal>/</literal>-route
-    of the application.
-   </para>
-  </section>
-
-  <section xml:id="module-services-grocy-settings">
-   <title>Settings</title>
-   <para>
-    The configuration for <literal>grocy</literal> is located at <literal>/etc/grocy/config.php</literal>.
-    By default, the following settings can be defined in the NixOS-configuration:
-<programlisting>{ pkgs, ... }:
-{
-  services.grocy.settings = {
-    # The default currency in the system for invoices etc.
-    # Please note that exchange rates aren't taken into account, this
-    # is just the setting for what's shown in the frontend.
-    <link linkend="opt-services.grocy.settings.currency">currency</link> = "EUR";
-
-    # The display language (and locale configuration) for grocy.
-    <link linkend="opt-services.grocy.settings.currency">culture</link> = "de";
-
-    calendar = {
-      # Whether or not to show the week-numbers
-      # in the calendar.
-      <link linkend="opt-services.grocy.settings.calendar.showWeekNumber">showWeekNumber</link> = true;
-
-      # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday,
-      # 2=Tuesday and so on).
-      <link linkend="opt-services.grocy.settings.calendar.firstDayOfWeek">firstDayOfWeek</link> = 2;
-    };
-  };
-}</programlisting>
-   </para>
-   <para>
-    If you want to alter the configuration file on your own, you can do this manually with
-    an expression like this:
-<programlisting>{ lib, ... }:
-{
-  environment.etc."grocy/config.php".text = lib.mkAfter ''
-    // Arbitrary PHP code in grocy's configuration file
-  '';
-}</programlisting>
-   </para>
-  </section>
-
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/healthchecks.nix b/nixpkgs/nixos/modules/services/web-apps/healthchecks.nix
index e58cc6f202be..b3fdb681e2f3 100644
--- a/nixpkgs/nixos/modules/services/web-apps/healthchecks.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/healthchecks.nix
@@ -15,19 +15,19 @@ let
 
   environmentFile = pkgs.writeText "healthchecks-environment" (lib.generators.toKeyValue { } environment);
 
-  healthchecksManageScript = with pkgs; (writeShellScriptBin "healthchecks-manage" ''
+  healthchecksManageScript = pkgs.writeShellScriptBin "healthchecks-manage" ''
+    sudo=exec
     if [[ "$USER" != "${cfg.user}" ]]; then
-        echo "please run as user 'healtchecks'." >/dev/stderr
-        exit 1
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env --preserve-env=PYTHONPATH'
     fi
-    export $(cat ${environmentFile} | xargs);
-    exec ${pkg}/opt/healthchecks/manage.py "$@"
-  '');
+    export $(cat ${environmentFile} | xargs)
+    $sudo ${pkg}/opt/healthchecks/manage.py "$@"
+  '';
 in
 {
   options.services.healthchecks = {
-    enable = mkEnableOption "healthchecks" // {
-      description = ''
+    enable = mkEnableOption (lib.mdDoc "healthchecks") // {
+      description = lib.mdDoc ''
         Enable healthchecks.
         It is expected to be run behind a HTTP reverse proxy.
       '';
@@ -43,28 +43,28 @@ in
     user = mkOption {
       default = defaultUser;
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         User account under which healthchecks runs.
 
-        <note><para>
+        ::: {.note}
         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 healthchecks service starts.
-        </para></note>
+        :::
       '';
     };
 
     group = mkOption {
       default = defaultUser;
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Group account under which healthchecks runs.
 
-        <note><para>
+        ::: {.note}
         If left as the default value this group will automatically be created
         on system activation, otherwise you are responsible for
         ensuring the group exists before the healthchecks service starts.
-        </para></note>
+        :::
       '';
     };
 
@@ -83,28 +83,28 @@ in
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/healthchecks";
-      description = ''
+      description = lib.mdDoc ''
         The directory used to store all data for healthchecks.
 
-        <note><para>
+        ::: {.note}
         If left as the default value this directory will automatically be created before
         the healthchecks server starts, otherwise you are responsible for ensuring the
         directory exists with appropriate ownership and permissions.
-        </para></note>
+        :::
       '';
     };
 
     settings = lib.mkOption {
-      description = ''
-        Environment variables which are read by healthchecks <literal>(local)_settings.py</literal>.
+      description = lib.mdDoc ''
+        Environment variables which are read by healthchecks `(local)_settings.py`.
 
-        Settings which are explictly covered in options bewlow, are type-checked and/or transformed
+        Settings which are explicitly covered in options bewlow, are type-checked and/or transformed
         before added to the environment, everything else is passed as a string.
 
-        See <link xlink:href="">https://healthchecks.io/docs/self_hosted_configuration/</link>
+        See <https://healthchecks.io/docs/self_hosted_configuration/>
         for a full documentation of settings.
 
-        We add two variables to this list inside the packages <literal>local_settings.py.</literal>
+        We add two variables to this list inside the packages `local_settings.py.`
         - STATIC_ROOT to set a state directory for dynamically generated static files.
         - SECRET_KEY_FILE to read SECRET_KEY from a file at runtime and keep it out of /nix/store.
       '';
@@ -163,7 +163,7 @@ in
           WorkingDirectory = cfg.dataDir;
           User = cfg.user;
           Group = cfg.group;
-          EnvironmentFile = environmentFile;
+          EnvironmentFile = [ environmentFile ];
           StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks";
           StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750";
         };
diff --git a/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix b/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
index 348192ea8486..e2014a9b7e35 100644
--- a/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
@@ -32,7 +32,7 @@ in
   ];
 
   options.services.hedgedoc = {
-    enable = mkEnableOption "the HedgeDoc Markdown Editor";
+    enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor");
 
     groups = mkOption {
       type = types.listOf types.str;
@@ -51,7 +51,7 @@ in
     };
 
     settings = let options = {
-      debug = mkEnableOption "debug mode";
+      debug = mkEnableOption (lib.mdDoc "debug mode");
       domain = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -76,7 +76,7 @@ in
         '';
       };
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
         example = 80;
         description = lib.mdDoc ''
@@ -189,9 +189,9 @@ in
       allowAnonymousEdits = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to allow guests to edit existing notes with the `freely' permission,
-          when <option>allowAnonymous</option> is enabled.
+        description = lib.mdDoc ''
+          Whether to allow guests to edit existing notes with the `freely` permission,
+          when {option}`allowAnonymous` is enabled.
         '';
       };
       allowFreeURL = mkOption {
@@ -291,7 +291,8 @@ in
       };
       defaultNotePath = mkOption {
         type = types.nullOr types.str;
-        default = "./public/default.md";
+        default = "${cfg.package}/public/default.md";
+        defaultText = literalExpression "\"\${cfg.package}/public/default.md\"";
         description = lib.mdDoc ''
           Path to the default Note file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -299,7 +300,8 @@ in
       };
       docsPath = mkOption {
         type = types.nullOr types.str;
-        default = "./public/docs";
+        default = "${cfg.package}/public/docs";
+        defaultText = literalExpression "\"\${cfg.package}/public/docs\"";
         description = lib.mdDoc ''
           Path to the docs directory.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -307,7 +309,8 @@ in
       };
       indexPath = mkOption {
         type = types.nullOr types.str;
-        default = "./public/views/index.ejs";
+        default = "${cfg.package}/public/views/index.ejs";
+        defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\"";
         description = lib.mdDoc ''
           Path to the index template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -315,7 +318,8 @@ in
       };
       hackmdPath = mkOption {
         type = types.nullOr types.str;
-        default = "./public/views/hackmd.ejs";
+        default = "${cfg.package}/public/views/hackmd.ejs";
+        defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\"";
         description = lib.mdDoc ''
           Path to the hackmd template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -323,8 +327,8 @@ in
       };
       errorPath = mkOption {
         type = types.nullOr types.str;
-        default = null;
-        defaultText = literalExpression "./public/views/error.ejs";
+        default = "${cfg.package}/public/views/error.ejs";
+        defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\"";
         description = lib.mdDoc ''
           Path to the error template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -332,8 +336,8 @@ in
       };
       prettyPath = mkOption {
         type = types.nullOr types.str;
-        default = null;
-        defaultText = literalExpression "./public/views/pretty.ejs";
+        default = "${cfg.package}/public/views/pretty.ejs";
+        defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\"";
         description = lib.mdDoc ''
           Path to the pretty template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -341,8 +345,8 @@ in
       };
       slidePath = mkOption {
         type = types.nullOr types.str;
-        default = null;
-        defaultText = literalExpression "./public/views/slide.hbs";
+        default = "${cfg.package}/public/views/slide.hbs";
+        defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\"";
         description = lib.mdDoc ''
           Path to the slide template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -351,7 +355,7 @@ in
       uploadsPath = mkOption {
         type = types.str;
         default = "${cfg.workDir}/uploads";
-        defaultText = literalExpression "/var/lib/${name}/uploads";
+        defaultText = literalExpression "\"\${cfg.workDir}/uploads\"";
         description = lib.mdDoc ''
           Path under which uploaded files are saved.
         '';
@@ -449,7 +453,7 @@ in
               '';
             };
             port = mkOption {
-              type = types.int;
+              type = types.port;
               default = 9000;
               description = lib.mdDoc ''
                 Minio listen port.
@@ -620,7 +624,8 @@ in
               '';
             };
             clientSecret = mkOption {
-              type = types.str;
+              type = with types; nullOr str;
+              default = null;
               description = lib.mdDoc ''
                 Specify the OAuth client secret.
               '';
@@ -933,30 +938,38 @@ in
                 Required group names.
               '';
             };
+            providerName = mkOption {
+              type = types.str;
+              default = "";
+              example = "My institution";
+              description = lib.mdDoc ''
+                Optional name to be displayed at login form indicating the SAML provider.
+              '';
+            };
             attribute = {
               id = mkOption {
                 type = types.str;
                 default = "";
-                description = ''
-                  Attribute map for `id'.
-                  Defaults to `NameID' of SAML response.
+                description = lib.mdDoc ''
+                  Attribute map for `id`.
+                  Defaults to `NameID` of SAML response.
                 '';
               };
               username = mkOption {
                 type = types.str;
                 default = "";
-                description = ''
-                  Attribute map for `username'.
-                  Defaults to `NameID' of SAML response.
+                description = lib.mdDoc ''
+                  Attribute map for `username`.
+                  Defaults to `NameID` of SAML response.
                 '';
               };
               email = mkOption {
                 type = types.str;
                 default = "";
-                description = ''
-                  Attribute map for `email'.
-                  Defaults to `NameID' of SAML response if
-                  <option>identifierFormat</option> has
+                description = lib.mdDoc ''
+                  Attribute map for `email`.
+                  Defaults to `NameID` of SAML response if
+                  {option}`identifierFormat` has
                   the default value.
                 '';
               };
@@ -982,27 +995,27 @@ in
       type = with types; nullOr path;
       default = null;
       example = "/var/lib/hedgedoc/hedgedoc.env";
-      description = ''
-        Environment file as defined in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)`.
 
         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.
 
-        <programlisting>
+        ```
           # snippet of HedgeDoc-related config
-          services.hedgedoc.configuration.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
-          services.hedgedoc.configuration.minio.secretKey = "$MINIO_SECRET_KEY";
-        </programlisting>
+          services.hedgedoc.settings.dbURL = "postgres://hedgedoc:\''${DB_PASSWORD}@db-host:5432/hedgedocdb";
+          services.hedgedoc.settings.minio.secretKey = "$MINIO_SECRET_KEY";
+        ```
 
-        <programlisting>
+        ```
           # content of the environment file
           DB_PASSWORD=verysecretdbpassword
           MINIO_SECRET_KEY=verysecretminiokey
-        </programlisting>
+        ```
 
         Note that this file needs to be available on the host on which
-        <literal>HedgeDoc</literal> is running.
+        `HedgeDoc` is running.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix
index 4f02a637cdd8..0fc283ff5219 100644
--- a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix
@@ -5,9 +5,9 @@ let
 in {
   options.services.hledger-web = {
 
-    enable = mkEnableOption "hledger-web service";
+    enable = mkEnableOption (lib.mdDoc "hledger-web service");
 
-    serveApi = mkEnableOption "Serve only the JSON web API, without the web UI.";
+    serveApi = mkEnableOption (lib.mdDoc "Serve only the JSON web API, without the web UI");
 
     host = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
index b96baaec7678..67d235ab4475 100644
--- a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -12,7 +12,7 @@ in {
   meta.maintainers = with maintainers; [ das_j ];
 
   options.services.icingaweb2 = with types; {
-    enable = mkEnableOption "the icingaweb2 web interface";
+    enable = mkEnableOption (lib.mdDoc "the icingaweb2 web interface");
 
     pool = mkOption {
       type = str;
@@ -49,11 +49,11 @@ in {
     };
 
     modules = {
-      doc.enable = mkEnableOption "the icingaweb2 doc module";
-      migrate.enable = mkEnableOption "the icingaweb2 migrate module";
-      setup.enable = mkEnableOption "the icingaweb2 setup module";
-      test.enable = mkEnableOption "the icingaweb2 test module";
-      translation.enable = mkEnableOption "the icingaweb2 translation module";
+      doc.enable = mkEnableOption (lib.mdDoc "the icingaweb2 doc module");
+      migrate.enable = mkEnableOption (lib.mdDoc "the icingaweb2 migrate module");
+      setup.enable = mkEnableOption (lib.mdDoc "the icingaweb2 setup module");
+      test.enable = mkEnableOption (lib.mdDoc "the icingaweb2 test module");
+      translation.enable = mkEnableOption (lib.mdDoc "the icingaweb2 translation module");
     };
 
     modulePackages = mkOption {
diff --git a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
index 0579c602216d..9a848870e9da 100644
--- a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
@@ -66,7 +66,7 @@ in {
             visible = false;
             default = name;
             type = str;
-            description = "Name of this backend";
+            description = lib.mdDoc "Name of this backend";
           };
 
           resource = mkOption {
@@ -98,7 +98,7 @@ in {
             visible = false;
             default = name;
             type = str;
-            description = "Name of this transport";
+            description = lib.mdDoc "Name of this transport";
           };
 
           type = mkOption {
diff --git a/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix
deleted file mode 100644
index c771f0afa231..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix
+++ /dev/null
@@ -1,153 +0,0 @@
-{ config, pkgs, lib, ... }:
-with lib;
-let
-  cfg = config.services.ihatemoney;
-  user = "ihatemoney";
-  group = "ihatemoney";
-  db = "ihatemoney";
-  python3 = config.services.uwsgi.package.python3;
-  pkg = python3.pkgs.ihatemoney;
-  toBool = x: if x then "True" else "False";
-  configFile = pkgs.writeText "ihatemoney.cfg" ''
-        from secrets import token_hex
-        # load a persistent secret key
-        SECRET_KEY_FILE = "/var/lib/ihatemoney/secret_key"
-        SECRET_KEY = ""
-        try:
-          with open(SECRET_KEY_FILE) as f:
-            SECRET_KEY = f.read()
-        except FileNotFoundError:
-          pass
-        if not SECRET_KEY:
-          print("ihatemoney: generating a new secret key")
-          SECRET_KEY = token_hex(50)
-          with open(SECRET_KEY_FILE, "w") as f:
-            f.write(SECRET_KEY)
-        del token_hex
-        del SECRET_KEY_FILE
-
-        # "normal" configuration
-        DEBUG = False
-        SQLALCHEMY_DATABASE_URI = '${
-          if cfg.backend == "sqlite"
-          then "sqlite:////var/lib/ihatemoney/ihatemoney.sqlite"
-          else "postgresql:///${db}"}'
-        SQLALCHEMY_TRACK_MODIFICATIONS = False
-        MAIL_DEFAULT_SENDER = (r"${cfg.defaultSender.name}", r"${cfg.defaultSender.email}")
-        ACTIVATE_DEMO_PROJECT = ${toBool cfg.enableDemoProject}
-        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}
-  '';
-in
-  {
-    options.services.ihatemoney = {
-      enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode";
-      backend = mkOption {
-        type = types.enum [ "sqlite" "postgresql" ];
-        default = "sqlite";
-        description = lib.mdDoc ''
-          The database engine to use for ihatemoney.
-          If `postgresql` is selected, then a database called
-          `${db}` will be created. If you disable this option,
-          it will however not be removed.
-        '';
-      };
-      adminHashedPassword = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc "The hashed password of the administrator. To obtain it, run `ihatemoney generate_password_hash`";
-      };
-      uwsgiConfig = mkOption {
-        type = types.attrs;
-        example = {
-          http = ":8000";
-        };
-        description = lib.mdDoc "Additionnal configuration of the UWSGI vassal running ihatemoney. It should notably specify on which interfaces and ports the vassal should listen.";
-      };
-      defaultSender = {
-        name = mkOption {
-          type = types.str;
-          default = "Budget manager";
-          description = lib.mdDoc "The display name of the sender of ihatemoney emails";
-        };
-        email = mkOption {
-          type = types.str;
-          default = "ihatemoney@${config.networking.hostName}";
-          defaultText = literalExpression ''"ihatemoney@''${config.networking.hostName}"'';
-          description = lib.mdDoc "The email of the sender of ihatemoney emails";
-        };
-      };
-      secureCookie = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc "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 = lib.mdDoc "The URL to a page explaining legal statements about your service, eg. GDPR-related information.";
-      };
-      extraConfig = mkOption {
-        type = types.str;
-        default = "";
-        description = lib.mdDoc "Extra configuration appended to ihatemoney's configuration file. It is a python file, so pay attention to indentation.";
-      };
-    };
-    config = mkIf cfg.enable {
-      services.postgresql = mkIf (cfg.backend == "postgresql") {
-        enable = true;
-        ensureDatabases = [ db ];
-        ensureUsers = [ {
-          name = user;
-          ensurePermissions = {
-            "DATABASE ${db}" = "ALL PRIVILEGES";
-          };
-        } ];
-      };
-      systemd.services.postgresql = mkIf (cfg.backend == "postgresql") {
-        wantedBy = [ "uwsgi.service" ];
-        before = [ "uwsgi.service" ];
-      };
-      systemd.tmpfiles.rules = [
-        "d /var/lib/ihatemoney 770 ${user} ${group}"
-      ];
-      users = {
-        users.${user} = {
-          isSystemUser = true;
-          inherit group;
-        };
-        groups.${group} = {};
-      };
-      services.uwsgi = {
-        enable = true;
-        plugins = [ "python3" ];
-        instance = {
-          type = "emperor";
-          vassals.ihatemoney = {
-            type = "normal";
-            strict = true;
-            immediate-uid = user;
-            immediate-gid = group;
-            # apparently flask uses threads: https://github.com/spiral-project/ihatemoney/commit/c7815e48781b6d3a457eaff1808d179402558f8c
-            enable-threads = true;
-            module = "wsgi:application";
-            chdir = "${pkg}/${pkg.pythonModule.sitePackages}/ihatemoney";
-            env = [ "IHATEMONEY_SETTINGS_FILE_PATH=${configFile}" ];
-            pythonPackages = self: [ self.ihatemoney ];
-          } // cfg.uwsgiConfig;
-        };
-      };
-    };
-  }
-
-
diff --git a/nixpkgs/nixos/modules/services/web-apps/invidious.nix b/nixpkgs/nixos/modules/services/web-apps/invidious.nix
index 0b9d9b03c6ae..61c52ee03dc6 100644
--- a/nixpkgs/nixos/modules/services/web-apps/invidious.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/invidious.nix
@@ -146,12 +146,12 @@ let
 in
 {
   options.services.invidious = {
-    enable = lib.mkEnableOption "Invidious";
+    enable = lib.mkEnableOption (lib.mdDoc "Invidious");
 
     package = lib.mkOption {
       type = types.package;
       default = pkgs.invidious;
-      defaultText = "pkgs.invidious";
+      defaultText = lib.literalExpression "pkgs.invidious";
       description = lib.mdDoc "The Invidious package to use.";
     };
 
@@ -171,7 +171,7 @@ in
       description = lib.mdDoc ''
         A file including Invidious settings.
 
-        It gets merged with the setttings specified in {option}`services.invidious.settings`
+        It gets merged with the settings specified in {option}`services.invidious.settings`
         and can be used to store secrets like `hmac_key` outside of the nix store.
       '';
     };
@@ -246,11 +246,11 @@ in
     nginx.enable = lib.mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         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>,
+        It serves it under the domain specified in {option}`services.invidious.settings.domain` with enabled TLS and ACME.
+        Further configuration can be done through {option}`services.nginx.virtualHosts.''${config.services.invidious.settings.domain}.*`,
         which can also be used to disable AMCE and TLS.
       '';
     };
diff --git a/nixpkgs/nixos/modules/services/web-apps/invoiceplane.nix b/nixpkgs/nixos/modules/services/web-apps/invoiceplane.nix
index 2a936027bd47..8be1fd3055d0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/invoiceplane.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/invoiceplane.nix
@@ -25,6 +25,7 @@ let
     ENCRYPTION_KEY=
     ENCRYPTION_CIPHER=AES-256
     SETUP_COMPLETED=false
+    REMOVE_INDEXPHP=true
   '';
 
   extraConfig = hostName: cfg: pkgs.writeText "extraConfig.php" ''
@@ -36,10 +37,10 @@ let
     version = src.version;
     src = pkgs.invoiceplane;
 
-    patchPhase = ''
+    postPhase = ''
       # Patch index.php file to load additional config file
       substituteInPlace index.php \
-        --replace "require('vendor/autoload.php');" "require('vendor/autoload.php'); \$dotenv = new \Dotenv\Dotenv(__DIR__, 'extraConfig.php'); \$dotenv->load();";
+        --replace "require('vendor/autoload.php');" "require('vendor/autoload.php'); \$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, 'extraConfig.php'); \$dotenv->load();";
     '';
 
     installPhase = ''
@@ -67,13 +68,13 @@ let
     {
       options = {
 
-        enable = mkEnableOption "InvoicePlane web application";
+        enable = mkEnableOption (lib.mdDoc "InvoicePlane web application");
 
         stateDir = mkOption {
           type = types.path;
           default = "/var/lib/invoiceplane/${name}";
           description = lib.mdDoc ''
-            This directory is used for uploads of attachements and cache.
+            This directory is used for uploads of attachments and cache.
             The directory passed here is automatically created and permissions
             adjusted as required.
           '';
@@ -124,9 +125,12 @@ let
         invoiceTemplates = mkOption {
           type = types.listOf types.path;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             List of path(s) to respective template(s) which are copied from the 'invoice_templates/pdf' directory.
-            <note><para>These templates need to be packaged before use, see example.</para></note>
+
+            ::: {.note}
+            These templates need to be packaged before use, see example.
+            :::
           '';
           example = literalExpression ''
             let
@@ -181,6 +185,26 @@ let
           '';
         };
 
+        cron = {
+
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc ''
+              Enable cron service which periodically runs Invoiceplane tasks.
+              Requires key taken from the administration page. Refer to
+              <https://wiki.invoiceplane.com/en/1.0/modules/recurring-invoices>
+              on how to configure it.
+            '';
+          };
+
+          key = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Cron key taken from the administration page.";
+          };
+
+        };
+
       };
 
     };
@@ -221,8 +245,11 @@ in
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
         message = ''services.invoiceplane.sites."${hostName}".database.passwordFile cannot be specified if services.invoiceplane.sites."${hostName}".database.createLocally is set to true.'';
-      }]
-    ) eachSite);
+      }
+      { assertion = cfg.cron.enable -> cfg.cron.key != null;
+        message = ''services.invoiceplane.sites."${hostName}".cron.key must be set in order to use cron service.'';
+      }
+    ]) eachSite);
 
     services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
       enable = true;
@@ -252,6 +279,7 @@ in
   }
 
   {
+
     systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
       "d ${cfg.stateDir} 0750 ${user} ${webserver.group} - -"
       "f ${cfg.stateDir}/ipconfig.php 0750 ${user} ${webserver.group} - -"
@@ -281,6 +309,34 @@ in
       group = webserver.group;
       isSystemUser = true;
     };
+
+  }
+  {
+
+    # Cron service implementation
+
+    systemd.timers = mapAttrs' (hostName: cfg: (
+      nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
+        wantedBy = [ "timers.target" ];
+        timerConfig = {
+          OnBootSec = "5m";
+          OnUnitActiveSec = "5m";
+          Unit = "invoiceplane-cron-${hostName}.service";
+        };
+      })
+    )) eachSite;
+
+    systemd.services =
+      mapAttrs' (hostName: cfg: (
+        nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
+          serviceConfig = {
+            Type = "oneshot";
+            User = user;
+            ExecStart = "${pkgs.curl}/bin/curl --header 'Host: ${hostName}' http://localhost/invoices/cron/recur/${cfg.cron.key}";
+          };
+        })
+    )) eachSite;
+
   }
 
   (mkIf (cfg.webserver == "caddy") {
@@ -289,9 +345,8 @@ in
       virtualHosts = mapAttrs' (hostName: cfg: (
         nameValuePair "http://${hostName}" {
           extraConfig = ''
-            root    * ${pkg hostName cfg}
+            root * ${pkg hostName cfg}
             file_server
-
             php_fastcgi unix/${config.services.phpfpm.pools."invoiceplane-${hostName}".socket}
           '';
         }
@@ -299,6 +354,5 @@ in
     };
   })
 
-
   ]);
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/isso.nix b/nixpkgs/nixos/modules/services/web-apps/isso.nix
index 4c01781a6a2b..1a852ec352f2 100644
--- a/nixpkgs/nixos/modules/services/web-apps/isso.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/isso.nix
@@ -11,19 +11,19 @@ in {
 
   options = {
     services.isso = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         A commenting server similar to Disqus.
 
         Note: The application's author suppose to run isso behind a reverse proxy.
         The embedded solution offered by NixOS is also only suitable for small installations
         below 20 requests per second.
-      '';
+      '');
 
       settings = mkOption {
-        description = ''
-          Configuration for <package>isso</package>.
+        description = lib.mdDoc ''
+          Configuration for `isso`.
 
-          See <link xlink:href="https://posativ.org/isso/docs/configuration/server/">Isso Server Configuration</link>
+          See [Isso Server Configuration](https://posativ.org/isso/docs/configuration/server/)
           for supported values.
         '';
 
@@ -63,6 +63,28 @@ in {
 
         Restart = "on-failure";
         RestartSec = 1;
+
+        # Hardening
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        UMask = "0077";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix b/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix
index c95d8ffd524d..b2e274167164 100644
--- a/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix
@@ -36,7 +36,7 @@ in
       description = lib.mdDoc "Location of Jirafeau storage directory.";
     };
 
-    enable = mkEnableOption "Jirafeau file upload application.";
+    enable = mkEnableOption (lib.mdDoc "Jirafeau file upload application");
 
     extraConfig = mkOption {
       type = types.lines;
@@ -45,12 +45,12 @@ in
         $cfg['style'] = 'courgette';
         $cfg['organisation'] = 'ACME';
       '';
-      description = let
+      description =  let
         documentationLink =
           "https://gitlab.com/mojo42/Jirafeau/-/blob/${cfg.package.version}/lib/config.original.php";
       in
-        ''
-          Jirefeau configuration. Refer to <link xlink:href="${documentationLink}"/> for supported
+        lib.mdDoc ''
+          Jirefeau configuration. Refer to <${documentationLink}> for supported
           values.
         '';
     };
@@ -73,10 +73,10 @@ in
       description = let
         nginxCoreDocumentation = "http://nginx.org/en/docs/http/ngx_http_core_module.html";
       in
-        ''
+        lib.mdDoc ''
           Timeout for reading client request bodies and headers. Refer to
-          <link xlink:href="${nginxCoreDocumentation}#client_body_timeout"/> and
-          <link xlink:href="${nginxCoreDocumentation}#client_header_timeout"/> for accepted values.
+          <${nginxCoreDocumentation}#client_body_timeout> and
+          <${nginxCoreDocumentation}#client_header_timeout> for accepted values.
         '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md
new file mode 100644
index 000000000000..060ef9752650
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.md
@@ -0,0 +1,45 @@
+# Jitsi Meet {#module-services-jitsi-meet}
+
+With Jitsi Meet on NixOS you can quickly configure a complete,
+private, self-hosted video conferencing solution.
+
+## Basic usage {#module-services-jitsi-basic-usage}
+
+A minimal configuration using Let's Encrypt for TLS certificates looks like this:
+```
+{
+  services.jitsi-meet = {
+    enable = true;
+    hostName = "jitsi.example.com";
+  };
+  services.jitsi-videobridge.openFirewall = true;
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+  security.acme.email = "me@example.com";
+  security.acme.acceptTerms = true;
+}
+```
+
+## Configuration {#module-services-jitsi-configuration}
+
+Here is the minimal configuration with additional configurations:
+```
+{
+  services.jitsi-meet = {
+    enable = true;
+    hostName = "jitsi.example.com";
+    config = {
+      enableWelcomePage = false;
+      prejoinPageEnabled = true;
+      defaultLang = "fi";
+    };
+    interfaceConfig = {
+      SHOW_JITSI_WATERMARK = false;
+      SHOW_WATERMARK_FOR_GUESTS = false;
+    };
+  };
+  services.jitsi-videobridge.openFirewall = true;
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+  security.acme.email = "me@example.com";
+  security.acme.acceptTerms = true;
+}
+```
diff --git a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix
index b38a510bb87e..3825b03c2449 100644
--- a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -28,7 +28,7 @@ let
     '');
 
   # Essential config - it's probably not good to have these as option default because
-  # types.attrs doesn't do merging. Let's merge explicitly, can still be overriden if
+  # types.attrs doesn't do merging. Let's merge explicitly, can still be overridden if
   # user desires.
   defaultCfg = {
     hosts = {
@@ -46,7 +46,7 @@ let
 in
 {
   options.services.jitsi-meet = with types; {
-    enable = mkEnableOption "Jitsi Meet - Secure, Simple and Scalable Video Conferences";
+    enable = mkEnableOption (lib.mdDoc "Jitsi Meet - Secure, Simple and Scalable Video Conferences");
 
     hostName = mkOption {
       type = str;
@@ -159,7 +159,7 @@ in
       '';
     };
 
-    caddy.enable = mkEnableOption "Whether to enable caddy reverse proxy to expose jitsi-meet";
+    caddy.enable = mkEnableOption (lib.mdDoc "Whether to enable caddy reverse proxy to expose jitsi-meet");
 
     prosody.enable = mkOption {
       type = bool;
@@ -267,6 +267,7 @@ in
         EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
         SupplementaryGroups = [ "jitsi-meet" ];
       };
+      reloadIfChanged = true;
     };
 
     users.groups.jitsi-meet = {};
@@ -410,11 +411,14 @@ in
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
       bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
       config = mkMerge [{
-        "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true";
+        jicofo.xmpp.service.disable-certificate-verification = true;
+        jicofo.xmpp.client.disable-certificate-verification = 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";
+         jicofo.jibri = {
+           brewery-jid = "JibriBrewery@internal.${cfg.hostName}";
+           pending-timeout = "90";
+         };
       })];
     };
 
@@ -450,6 +454,6 @@ in
     };
   };
 
-  meta.doc = ./jitsi-meet.xml;
+  meta.doc = ./jitsi-meet.md;
   meta.maintainers = lib.teams.jitsi.members;
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml
deleted file mode 100644
index ff44c724adf4..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-jitsi-meet">
- <title>Jitsi Meet</title>
- <para>
-   With Jitsi Meet on NixOS you can quickly configure a complete,
-   private, self-hosted video conferencing solution.
- </para>
-
- <section xml:id="module-services-jitsi-basic-usage">
- <title>Basic usage</title>
-   <para>
-     A minimal configuration using Let's Encrypt for TLS certificates looks like this:
-<programlisting>{
-  services.jitsi-meet = {
-    <link linkend="opt-services.jitsi-meet.enable">enable</link> = true;
-    <link linkend="opt-services.jitsi-meet.enable">hostName</link> = "jitsi.example.com";
-  };
-  <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.defaults.email">security.acme.email</link> = "me@example.com";
-  <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
-}</programlisting>
-   </para>
- </section>
-
- <section xml:id="module-services-jitsi-configuration">
- <title>Configuration</title>
-   <para>
-     Here is the minimal configuration with additional configurations:
-<programlisting>{
-  services.jitsi-meet = {
-    <link linkend="opt-services.jitsi-meet.enable">enable</link> = true;
-    <link linkend="opt-services.jitsi-meet.enable">hostName</link> = "jitsi.example.com";
-    <link linkend="opt-services.jitsi-meet.config">config</link> = {
-      enableWelcomePage = false;
-      prejoinPageEnabled = true;
-      defaultLang = "fi";
-    };
-    <link linkend="opt-services.jitsi-meet.interfaceConfig">interfaceConfig</link> = {
-      SHOW_JITSI_WATERMARK = false;
-      SHOW_WATERMARK_FOR_GUESTS = false;
-    };
-  };
-  <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.defaults.email">security.acme.email</link> = "me@example.com";
-  <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
-}</programlisting>
-   </para>
- </section>
-
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/kasmweb/default.nix b/nixpkgs/nixos/modules/services/web-apps/kasmweb/default.nix
new file mode 100644
index 000000000000..0d78025ecf0f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/kasmweb/default.nix
@@ -0,0 +1,275 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.kasmweb;
+in
+{
+  options.services.kasmweb = {
+    enable = lib.mkEnableOption (lib.mdDoc "kasmweb");
+
+    networkSubnet = lib.mkOption {
+      default = "172.20.0.0/16";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        The network subnet to use for the containers.
+      '';
+    };
+
+    postgres = {
+      user = lib.mkOption {
+        default = "kasmweb";
+        type = lib.types.str;
+        description = lib.mdDoc ''
+          Username to use for the postgres database.
+        '';
+      };
+      password = lib.mkOption {
+        default = "kasmweb";
+        type = lib.types.str;
+        description = lib.mdDoc ''
+          password to use for the postgres database.
+        '';
+      };
+    };
+
+    redisPassword = lib.mkOption {
+      default = "kasmweb";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        password to use for the redis cache.
+      '';
+    };
+
+    defaultAdminPassword = lib.mkOption {
+      default = "kasmweb";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        default admin password to use.
+      '';
+    };
+
+    defaultUserPassword = lib.mkOption {
+      default = "kasmweb";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        default user password to use.
+      '';
+    };
+
+    defaultManagerToken = lib.mkOption {
+      default = "kasmweb";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        default manager token to use.
+      '';
+    };
+
+    defaultGuacToken = lib.mkOption {
+      default = "kasmweb";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        default guac token to use.
+      '';
+    };
+
+    defaultRegistrationToken = lib.mkOption {
+      default = "kasmweb";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        default registration token to use.
+      '';
+    };
+
+    datastorePath = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/kasmweb";
+      description = lib.mdDoc ''
+        The directory used to store all data for kasmweb.
+      '';
+    };
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "0.0.0.0";
+      description = lib.mdDoc ''
+        The address on which kasmweb should listen.
+      '';
+    };
+
+    listenPort = lib.mkOption {
+      type = lib.types.int;
+      default = 443;
+      description = lib.mdDoc ''
+        The port on which kasmweb should listen.
+      '';
+    };
+
+    sslCertificate = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        The SSL certificate to be used for kasmweb.
+      '';
+    };
+
+    sslCertificateKey = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        The SSL certificate's key to be used for kasmweb. Make sure to specify
+        this as a string and not a literal path, so that it is not accidentally
+        included in your nixstore.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.services = {
+      "init-kasmweb" = {
+        wantedBy = [
+          "docker-kasm_db.service"
+        ];
+        before = [
+          "docker-kasm_db.service"
+          "docker-kasm_redis.service"
+          "docker-kasm_db_init.service"
+          "docker-kasm_api.service"
+          "docker-kasm_agent.service"
+          "docker-kasm_manager.service"
+          "docker-kasm_share.service"
+          "docker-kasm_guac.service"
+          "docker-kasm_proxy.service"
+        ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = pkgs.substituteAll {
+            src = ./initialize_kasmweb.sh;
+            isExecutable = true;
+            binPath = lib.makeBinPath [ pkgs.docker pkgs.openssl pkgs.gnused ];
+            runtimeShell = pkgs.runtimeShell;
+            kasmweb = pkgs.kasmweb;
+            postgresUser = cfg.postgres.user;
+            postgresPassword = cfg.postgres.password;
+            inherit (cfg)
+              datastorePath
+              sslCertificate
+              sslCertificateKey
+              redisPassword
+              defaultUserPassword
+              defaultAdminPassword
+              defaultManagerToken
+              defaultRegistrationToken
+              defaultGuacToken;
+          };
+        };
+      };
+    };
+
+    virtualisation = {
+      oci-containers.containers = {
+        kasm_db = {
+          image = "postgres:12-alpine";
+          environment = {
+            POSTGRES_PASSWORD = cfg.postgres.password;
+            POSTGRES_USER = cfg.postgres.user;
+            POSTGRES_DB = "kasm";
+          };
+          volumes = [
+            "${cfg.datastorePath}/conf/database/data.sql:/docker-entrypoint-initdb.d/data.sql"
+            "${cfg.datastorePath}/conf/database/:/tmp/"
+            "kasmweb_db:/var/lib/postgresql/data"
+          ];
+          extraOptions = [ "--network=kasm_default_network" ];
+        };
+        kasm_db_init = {
+          image = "kasmweb/api:${pkgs.kasmweb.version}";
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/:/opt/kasm/current/"
+            "kasmweb_api_data:/tmp"
+          ];
+          dependsOn = [ "kasm_db" ];
+          entrypoint = "/bin/bash";
+          cmd = [ "/opt/kasm/current/init_seeds.sh" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host" ];
+        };
+        kasm_redis = {
+          image = "redis:5-alpine";
+          entrypoint = "/bin/sh";
+          cmd = [
+            "-c"
+            "redis-server --requirepass ${cfg.redisPassword}"
+          ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host" ];
+        };
+        kasm_api = {
+          image = "kasmweb/api:${pkgs.kasmweb.version}";
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/:/opt/kasm/current/"
+            "kasmweb_api_data:/tmp"
+          ];
+          dependsOn = [ "kasm_db_init" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host"  ];
+        };
+        kasm_manager = {
+          image = "kasmweb/manager:${pkgs.kasmweb.version}";
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/:/opt/kasm/current/"
+          ];
+          dependsOn = [ "kasm_db" "kasm_api" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only"];
+        };
+        kasm_agent = {
+          image = "kasmweb/agent:${pkgs.kasmweb.version}";
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/:/opt/kasm/current/"
+            "/var/run/docker.sock:/var/run/docker.sock"
+            "${pkgs.docker}/bin/docker:/usr/bin/docker"
+            "${cfg.datastorePath}/conf/nginx:/etc/nginx/conf.d"
+          ];
+          dependsOn = [ "kasm_manager" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only" ];
+        };
+        kasm_share = {
+          image = "kasmweb/share:${pkgs.kasmweb.version}";
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/:/opt/kasm/current/"
+          ];
+          dependsOn = [ "kasm_db" "kasm_redis" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only" ];
+        };
+        kasm_guac = {
+          image = "kasmweb/kasm-guac:${pkgs.kasmweb.version}";
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/:/opt/kasm/current/"
+          ];
+          dependsOn = [ "kasm_db" "kasm_redis" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host" "--read-only" ];
+        };
+        kasm_proxy = {
+          image = "kasmweb/nginx:latest";
+          ports = [ "${cfg.listenAddress}:${toString cfg.listenPort}:443" ];
+          user = "root:root";
+          volumes = [
+            "${cfg.datastorePath}/conf/nginx:/etc/nginx/conf.d:ro"
+            "${cfg.datastorePath}/certs/kasm_nginx.key:/etc/ssl/private/kasm_nginx.key"
+            "${cfg.datastorePath}/certs/kasm_nginx.crt:/etc/ssl/certs/kasm_nginx.crt"
+            "${cfg.datastorePath}/www:/srv/www:ro"
+            "${cfg.datastorePath}/log/nginx:/var/log/external/nginx"
+            "${cfg.datastorePath}/log/logrotate:/var/log/external/logrotate"
+          ];
+          dependsOn = [ "kasm_manager" "kasm_api" "kasm_agent" "kasm_share"
+          "kasm_guac" ];
+          extraOptions = [ "--network=kasm_default_network" "--userns=host"
+          "--network-alias=proxy"];
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh b/nixpkgs/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh
new file mode 100644
index 000000000000..dbf043b98693
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/kasmweb/initialize_kasmweb.sh
@@ -0,0 +1,114 @@
+#! @runtimeShell@
+export PATH=@binPath@:$PATH
+
+mkdir -p @datastorePath@/log
+chmod -R a+rw @datastorePath@
+
+ln -sf @kasmweb@/bin @datastorePath@
+rm -r @datastorePath@/conf
+cp -r @kasmweb@/conf @datastorePath@
+mkdir -p @datastorePath@/conf/nginx/containers.d
+chmod -R a+rw @datastorePath@/conf
+ln -sf @kasmweb@/www @datastorePath@
+
+
+docker network inspect kasm_default_network >/dev/null || docker network create kasm_default_network --subnet @networkSubnet@
+if docker volume inspect kasmweb_db >/dev/null; then
+    source @datastorePath@/ids.env
+    echo 'echo "skipping database init"' > @datastorePath@/init_seeds.sh
+    echo 'while true; do sleep 10 ; done' >> @datastorePath@/init_seeds.sh
+else
+    API_SERVER_ID=$(cat /proc/sys/kernel/random/uuid)
+    MANAGER_ID=$(cat /proc/sys/kernel/random/uuid)
+    SHARE_ID=$(cat /proc/sys/kernel/random/uuid)
+    SERVER_ID=$(cat /proc/sys/kernel/random/uuid)
+    echo "export API_SERVER_ID=$API_SERVER_ID" > @datastorePath@/ids.env
+    echo "export MANAGER_ID=$MANAGER_ID" >> @datastorePath@/ids.env
+    echo "export SHARE_ID=$SHARE_ID" >> @datastorePath@/ids.env
+    echo "export SERVER_ID=$SERVER_ID" >> @datastorePath@/ids.env
+
+    mkdir -p @datastorePath@/certs
+    openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout @datastorePath@/certs/kasm_nginx.key -out @datastorePath@/certs/kasm_nginx.crt -subj "/C=US/ST=VA/L=None/O=None/OU=DoFu/CN=$(hostname)/emailAddress=none@none.none" 2> /dev/null
+
+    docker volume create kasmweb_db
+    rm @datastorePath@/.done_initing_data
+    cat >@datastorePath@/init_seeds.sh <<EOF
+#!/bin/bash
+if [ ! -e /opt/kasm/current/.done_initing_data ]; then
+  sleep 4
+  /usr/bin/kasm_server.so --initialize-database --cfg \
+    /opt/kasm/current/conf/app/api.app.config.yaml \
+    --populate-production \
+    --seed-file \
+    /opt/kasm/current/conf/database/seed_data/default_properties.yaml \
+    2>&1 | grep -v UserWarning
+  /usr/bin/kasm_server.so --cfg \
+    /opt/kasm/current/conf/app/api.app.config.yaml \
+    --populate-production \
+    --seed-file \
+    /opt/kasm/current/conf/database/seed_data/default_agents.yaml \
+    2>&1 | grep -v UserWarning
+  /usr/bin/kasm_server.so --cfg \
+    /opt/kasm/current/conf/app/api.app.config.yaml \
+    --populate-production \
+    --seed-file \
+    /opt/kasm/current/conf/database/seed_data/default_connection_proxies.yaml \
+    2>&1 | grep -v UserWarning
+  /usr/bin/kasm_server.so --cfg \
+    /opt/kasm/current/conf/app/api.app.config.yaml \
+    --populate-production \
+    --seed-file \
+    /opt/kasm/current/conf/database/seed_data/default_images_amd64.yaml \
+    2>&1 | grep -v UserWarning
+  touch /opt/kasm/current/.done_initing_data
+  while true; do sleep 10 ; done
+else
+ echo "skipping database init"
+  while true; do sleep 10 ; done
+fi
+EOF
+fi
+
+chmod +x @datastorePath@/init_seeds.sh
+chmod a+w @datastorePath@/init_seeds.sh
+
+if [ -e @sslCertificate@ ]; then
+    cp @sslCertificate@ @datastorePath@/certs/kasm_nginx.crt
+    cp @sslCertificateKey@ @datastorePath@/certs/kasm_nginx.key
+fi
+
+sed -i -e "s/username.*/username: @postgresUser@/g" \
+    -e "s/password.*/password: @postgresPassword@/g" \
+    -e "s/host.*db/host: kasm_db/g" \
+    -e "s/ssl: true/ssl: false/g" \
+    -e "s/redisPassword.*/redisPassword: @redisPassword@/g" \
+    -e "s/server_hostname.*/server_hostname: kasm_api/g" \
+    -e "s/server_id.*/server_id: $API_SERVER_ID/g" \
+    -e "s/manager_id.*/manager_id: $MANAGER_ID/g" \
+    -e "s/share_id.*/share_id: $SHARE_ID/g" \
+    @datastorePath@/conf/app/api.app.config.yaml
+
+sed -i -e "s/ token:.*/ token: \"@defaultManagerToken@\"/g" \
+    -e "s/hostnames: \['proxy.*/hostnames: \['kasm_proxy'\]/g" \
+    -e "s/server_id.*/server_id: $SERVER_ID/g" \
+    @datastorePath@/conf/app/agent.app.config.yaml
+
+
+sed -i -e "s/password: admin.*/password: \"@defaultAdminPassword@\"/g" \
+    -e "s/password: user.*/password: \"@defaultUserPassword@\"/g" \
+    -e "s/default-manager-token/@defaultManagerToken@/g" \
+    -e "s/default-registration-token/@defaultRegistrationToken@/g" \
+    -e "s/upstream_auth_address:.*/upstream_auth_address: 'proxy'/g" \
+    @datastorePath@/conf/database/seed_data/default_properties.yaml
+
+sed -i -e "s/GUACTOKEN/@defaultGuacToken@/g" \
+    -e "s/APIHOSTNAME/proxy/g" \
+    @datastorePath@/conf/app/kasmguac.app.config.yaml
+
+sed -i -e "s/GUACTOKEN/@defaultGuacToken@/g" \
+    -e "s/APIHOSTNAME/proxy/g" \
+    @datastorePath@/conf/database/seed_data/default_connection_proxies.yaml
+
+sed -i "s/00000000-0000-0000-0000-000000000000/$SERVER_ID/g" \
+    @datastorePath@/conf/database/seed_data/default_agents.yaml
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/kavita.nix b/nixpkgs/nixos/modules/services/web-apps/kavita.nix
new file mode 100644
index 000000000000..ca9cd01d403d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/kavita.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.kavita;
+in {
+  options.services.kavita = {
+    enable = lib.mkEnableOption (lib.mdDoc "Kavita reading server");
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "kavita";
+      description = lib.mdDoc "User account under which Kavita runs.";
+    };
+
+    package = lib.mkPackageOptionMD pkgs "kavita" { };
+
+    dataDir = lib.mkOption {
+      default = "/var/lib/kavita";
+      type = lib.types.str;
+      description = lib.mdDoc "The directory where Kavita stores its state.";
+    };
+
+    tokenKeyFile = lib.mkOption {
+      type = lib.types.path;
+      description = lib.mdDoc ''
+        A file containing the TokenKey, a secret with at 128+ bits.
+        It can be generated with `head -c 32 /dev/urandom | base64`.
+      '';
+    };
+    port = lib.mkOption {
+      default = 5000;
+      type = lib.types.port;
+      description = lib.mdDoc "Port to bind to.";
+    };
+    ipAdresses = lib.mkOption {
+      default = ["0.0.0.0" "::"];
+      type = lib.types.listOf lib.types.str;
+      description = lib.mdDoc "IP Addresses to bind to. The default is to bind
+      to all IPv4 and IPv6 addresses.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.kavita = {
+      description = "Kavita";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        umask u=rwx,g=rx,o=
+        cat > "${cfg.dataDir}/config/appsettings.json" <<EOF
+        {
+          "TokenKey": "$(cat ${cfg.tokenKeyFile})",
+          "Port": ${toString cfg.port},
+          "IpAddresses": "${lib.concatStringsSep "," cfg.ipAdresses}"
+        }
+        EOF
+      '';
+      serviceConfig = {
+        WorkingDirectory = cfg.dataDir;
+        ExecStart = "${lib.getExe cfg.package}";
+        Restart = "always";
+        User = cfg.user;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}'        0750 ${cfg.user} ${cfg.user} - -"
+      "d '${cfg.dataDir}/config' 0750 ${cfg.user} ${cfg.user} - -"
+    ];
+
+    users = {
+      users.${cfg.user} = {
+        description = "kavita service user";
+        isSystemUser = true;
+        group = cfg.user;
+        home = cfg.dataDir;
+      };
+      groups.${cfg.user} = { };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ misterio77 ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/keycloak.md b/nixpkgs/nixos/modules/services/web-apps/keycloak.md
new file mode 100644
index 000000000000..aa8de40d642b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/keycloak.md
@@ -0,0 +1,141 @@
+# Keycloak {#module-services-keycloak}
+
+[Keycloak](https://www.keycloak.org/) is an
+open source identity and access management server with support for
+[OpenID Connect](https://openid.net/connect/),
+[OAUTH 2.0](https://oauth.net/2/) and
+[SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
+
+## Administration {#module-services-keycloak-admin}
+
+An administrative user with the username
+`admin` is automatically created in the
+`master` realm. Its initial password can be
+configured by setting [](#opt-services.keycloak.initialAdminPassword)
+and defaults to `changeme`. The password is
+not stored safely and should be changed immediately in the
+admin panel.
+
+Refer to the [Keycloak Server Administration Guide](
+  https://www.keycloak.org/docs/latest/server_admin/index.html
+) for information on
+how to administer your Keycloak
+instance.
+
+## Database access {#module-services-keycloak-database}
+
+Keycloak can be used with either PostgreSQL, MariaDB or
+MySQL. Which one is used can be
+configured in [](#opt-services.keycloak.database.type). The selected
+database will automatically be enabled and a database and role
+created unless [](#opt-services.keycloak.database.host) is changed
+from its default of `localhost` or
+[](#opt-services.keycloak.database.createLocally) is set to `false`.
+
+External database access can also be configured by setting
+[](#opt-services.keycloak.database.host),
+[](#opt-services.keycloak.database.name),
+[](#opt-services.keycloak.database.username),
+[](#opt-services.keycloak.database.useSSL) and
+[](#opt-services.keycloak.database.caCert) as
+appropriate. Note that you need to manually create the database
+and allow the configured database user full access to it.
+
+[](#opt-services.keycloak.database.passwordFile)
+must be set to the path to a file containing the password used
+to log in to the database. If [](#opt-services.keycloak.database.host)
+and [](#opt-services.keycloak.database.createLocally)
+are kept at their defaults, the database role
+`keycloak` with that password is provisioned
+on the local database instance.
+
+::: {.warning}
+The path should be provided as a string, not a Nix path, since Nix
+paths are copied into the world readable Nix store.
+:::
+
+## Hostname {#module-services-keycloak-hostname}
+
+The hostname is used to build the public URL used as base for
+all frontend requests and must be configured through
+[](#opt-services.keycloak.settings.hostname).
+
+::: {.note}
+If you're migrating an old Wildfly based Keycloak instance
+and want to keep compatibility with your current clients,
+you'll likely want to set [](#opt-services.keycloak.settings.http-relative-path)
+to `/auth`. See the option description
+for more details.
+:::
+
+[](#opt-services.keycloak.settings.hostname-strict-backchannel)
+determines whether Keycloak should force all requests to go
+through the frontend URL. By default,
+Keycloak allows backend requests to
+instead use its local hostname or IP address and may also
+advertise it to clients through its OpenID Connect Discovery
+endpoint.
+
+For more information on hostname configuration, see the [Hostname
+section of the Keycloak Server Installation and Configuration
+Guide](https://www.keycloak.org/server/hostname).
+
+## Setting up TLS/SSL {#module-services-keycloak-tls}
+
+By default, Keycloak won't accept
+unsecured HTTP connections originating from outside its local
+network.
+
+HTTPS support requires a TLS/SSL certificate and a private key,
+both [PEM formatted](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail).
+Their paths should be set through
+[](#opt-services.keycloak.sslCertificate) and
+[](#opt-services.keycloak.sslCertificateKey).
+
+::: {.warning}
+ The paths should be provided as a strings, not a Nix paths,
+since Nix paths are copied into the world readable Nix store.
+:::
+
+## Themes {#module-services-keycloak-themes}
+
+You can package custom themes and make them visible to
+Keycloak through [](#opt-services.keycloak.themes). See the
+[Themes section of the Keycloak Server Development Guide](
+  https://www.keycloak.org/docs/latest/server_development/#_themes
+) and the description of the aforementioned NixOS option for
+more information.
+
+## Configuration file settings {#module-services-keycloak-settings}
+
+Keycloak server configuration parameters can be set in
+[](#opt-services.keycloak.settings). These correspond
+directly to options in
+{file}`conf/keycloak.conf`. Some of the most
+important parameters are documented as suboptions, the rest can
+be found in the [All
+configuration section of the Keycloak Server Installation and
+Configuration Guide](https://www.keycloak.org/server/all-config).
+
+Options containing secret data should be set to an attribute
+set containing the attribute `_secret` - a
+string pointing to a file containing the value the option
+should be set to. See the description of
+[](#opt-services.keycloak.settings) for an example.
+
+## Example configuration {#module-services-keycloak-example-config}
+
+A basic configuration with some custom settings could look like this:
+```
+services.keycloak = {
+  enable = true;
+  settings = {
+    hostname = "keycloak.example.com";
+    hostname-strict-backchannel = true;
+  };
+  initialAdminPassword = "e6Wcm0RrtegMEHl";  # change on first login
+  sslCertificate = "/run/keys/ssl_cert";
+  sslCertificateKey = "/run/keys/ssl_key";
+  database.passwordFile = "/run/keys/db_password";
+};
+```
diff --git a/nixpkgs/nixos/modules/services/web-apps/keycloak.nix b/nixpkgs/nixos/modules/services/web-apps/keycloak.nix
index b878cb74b52e..a7e4fab8ea28 100644
--- a/nixpkgs/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/keycloak.nix
@@ -20,11 +20,12 @@ let
     mkDefault
     literalExpression
     isAttrs
-    literalDocBook
+    literalMD
     maintainers
     catAttrs
     collect
     splitString
+    hasPrefix
     ;
 
   inherit (builtins)
@@ -165,7 +166,7 @@ in
           mkOption {
             type = port;
             default = dbPorts.${cfg.database.type};
-            defaultText = literalDocBook "default port of selected database";
+            defaultText = literalMD "default port of selected database";
             description = lib.mdDoc ''
               Port of the database to connect to.
             '';
@@ -312,25 +313,24 @@ in
 
             http-relative-path = mkOption {
               type = str;
-              default = "";
+              default = "/";
               example = "/auth";
-              description = ''
-                The path relative to <literal>/</literal> for serving
+              apply = x: if !(hasPrefix "/") x then "/" + x else x;
+              description = lib.mdDoc ''
+                The path relative to `/` for serving
                 resources.
 
-                <note>
-                  <para>
-                    In versions of Keycloak using Wildfly (&lt;17),
-                    this defaulted to <literal>/auth</literal>. If
-                    upgrading from the Wildfly version of Keycloak,
-                    i.e. a NixOS version before 22.05, you'll likely
-                    want to set this to <literal>/auth</literal> to
-                    keep compatibility with your clients.
-
-                    See <link xlink:href="https://www.keycloak.org/migration/migrating-to-quarkus"/>
-                    for more information on migrating from Wildfly to Quarkus.
-                  </para>
-                </note>
+                ::: {.note}
+                In versions of Keycloak using Wildfly (&lt;17),
+                this defaulted to `/auth`. If
+                upgrading from the Wildfly version of Keycloak,
+                i.e. a NixOS version before 22.05, you'll likely
+                want to set this to `/auth` to
+                keep compatibility with your clients.
+
+                See <https://www.keycloak.org/migration/migrating-to-quarkus>
+                for more information on migrating from Wildfly to Quarkus.
+                :::
               '';
             };
 
@@ -366,41 +366,21 @@ in
               type = enum [ "edge" "reencrypt" "passthrough" "none" ];
               default = "none";
               example = "edge";
-              description = ''
+              description = lib.mdDoc ''
                 The proxy address forwarding mode if the server is
                 behind a reverse proxy.
 
-                <variablelist>
-                  <varlistentry>
-                    <term>edge</term>
-                    <listitem>
-                      <para>
-                        Enables communication through HTTP between the
-                        proxy and Keycloak.
-                      </para>
-                    </listitem>
-                  </varlistentry>
-                  <varlistentry>
-                    <term>reencrypt</term>
-                    <listitem>
-                      <para>
-                        Requires communication through HTTPS between the
-                        proxy and Keycloak.
-                      </para>
-                    </listitem>
-                  </varlistentry>
-                  <varlistentry>
-                    <term>passthrough</term>
-                    <listitem>
-                      <para>
-                        Enables communication through HTTP or HTTPS between
-                        the proxy and Keycloak.
-                      </para>
-                    </listitem>
-                  </varlistentry>
-                </variablelist>
-
-                See <link xlink:href="https://www.keycloak.org/server/reverseproxy"/> for more information.
+                - `edge`:
+                  Enables communication through HTTP between the
+                  proxy and Keycloak.
+                - `reencrypt`:
+                  Requires communication through HTTPS between the
+                  proxy and Keycloak.
+                - `passthrough`:
+                  Enables communication through HTTP or HTTPS between
+                  the proxy and Keycloak.
+
+                See <https://www.keycloak.org/server/reverseproxy> for more information.
               '';
             };
           };
@@ -502,6 +482,10 @@ in
             assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null);
             message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL";
           }
+          {
+            assertion = createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true;
+            message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably";
+          }
         ];
 
         environment.systemPackages = [ keycloakBuild ];
@@ -564,7 +548,13 @@ in
             create_role="$(mktemp)"
             trap 'rm -f "$create_role"' EXIT
 
+            # Read the password from the credentials directory and
+            # escape any single quotes by adding additional single
+            # quotes after them, following the rules laid out here:
+            # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
             db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
+            db_password="''${db_password//\'/\'\'}"
+
             echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role"
             psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role"
             psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
@@ -586,8 +576,16 @@ in
           script = ''
             set -o errexit -o pipefail -o nounset -o errtrace
             shopt -s inherit_errexit
+
+            # Read the password from the credentials directory and
+            # escape any single quotes by adding additional single
+            # quotes after them, following the rules laid out here:
+            # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html
             db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
-            ( echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
+            db_password="''${db_password//\'/\'\'}"
+
+            ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';"
+              echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
               echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
               echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
             ) | mysql -N
@@ -636,7 +634,7 @@ in
               Group = "keycloak";
               DynamicUser = true;
               RuntimeDirectory = "keycloak";
-              RuntimeDirectoryMode = 0700;
+              RuntimeDirectoryMode = "0700";
               AmbientCapabilities = "CAP_NET_BIND_SERVICE";
             };
             script = ''
@@ -652,13 +650,18 @@ in
 
               ${secretReplacements}
 
+              # Escape any backslashes in the db parameters, since
+              # they're otherwise unexpectedly read as escape
+              # sequences.
+              sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf
+
             '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
               mkdir -p /run/keycloak/ssl
               cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/
             '' + ''
               export KEYCLOAK_ADMIN=admin
-              export KEYCLOAK_ADMIN_PASSWORD=${cfg.initialAdminPassword}
-              kc.sh start
+              export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword}
+              kc.sh start --optimized
             '';
           };
 
@@ -671,6 +674,6 @@ in
           mkIf createLocalMySQL (mkDefault dbPkg);
       };
 
-  meta.doc = ./keycloak.xml;
+  meta.doc = ./keycloak.md;
   meta.maintainers = [ maintainers.talyz ];
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/keycloak.xml b/nixpkgs/nixos/modules/services/web-apps/keycloak.xml
deleted file mode 100644
index 861756e33ac0..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/keycloak.xml
+++ /dev/null
@@ -1,202 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-keycloak">
- <title>Keycloak</title>
- <para>
-   <link xlink:href="https://www.keycloak.org/">Keycloak</link> is an
-   open source identity and access management server with support for
-   <link xlink:href="https://openid.net/connect/">OpenID
-   Connect</link>, <link xlink:href="https://oauth.net/2/">OAUTH
-   2.0</link> and <link
-   xlink:href="https://en.wikipedia.org/wiki/SAML_2.0">SAML
-   2.0</link>.
- </para>
-   <section xml:id="module-services-keycloak-admin">
-     <title>Administration</title>
-     <para>
-       An administrative user with the username
-       <literal>admin</literal> is automatically created in the
-       <literal>master</literal> realm. Its initial password can be
-       configured by setting <xref linkend="opt-services.keycloak.initialAdminPassword" />
-       and defaults to <literal>changeme</literal>. The password is
-       not stored safely and should be changed immediately in the
-       admin panel.
-     </para>
-
-     <para>
-       Refer to the <link
-       xlink:href="https://www.keycloak.org/docs/latest/server_admin/index.html">
-       Keycloak Server Administration Guide</link> for information on
-       how to administer your <productname>Keycloak</productname>
-       instance.
-     </para>
-   </section>
-
-   <section xml:id="module-services-keycloak-database">
-     <title>Database access</title>
-     <para>
-       <productname>Keycloak</productname> can be used with either
-       <productname>PostgreSQL</productname>,
-       <productname>MariaDB</productname> or
-       <productname>MySQL</productname>. Which one is used can be
-       configured in <xref
-       linkend="opt-services.keycloak.database.type" />. The selected
-       database will automatically be enabled and a database and role
-       created unless <xref
-       linkend="opt-services.keycloak.database.host" /> is changed
-       from its default of <literal>localhost</literal> or <xref
-       linkend="opt-services.keycloak.database.createLocally" /> is
-       set to <literal>false</literal>.
-     </para>
-
-     <para>
-       External database access can also be configured by setting
-       <xref linkend="opt-services.keycloak.database.host" />, <xref
-       linkend="opt-services.keycloak.database.name" />, <xref
-       linkend="opt-services.keycloak.database.username" />, <xref
-       linkend="opt-services.keycloak.database.useSSL" /> and <xref
-       linkend="opt-services.keycloak.database.caCert" /> as
-       appropriate. Note that you need to manually create the database
-       and allow the configured database user full access to it.
-     </para>
-
-     <para>
-       <xref linkend="opt-services.keycloak.database.passwordFile" />
-       must be set to the path to a file containing the password used
-       to log in to the database. If <xref linkend="opt-services.keycloak.database.host" />
-       and <xref linkend="opt-services.keycloak.database.createLocally" />
-       are kept at their defaults, the database role
-       <literal>keycloak</literal> with that password is provisioned
-       on the local database instance.
-     </para>
-
-     <warning>
-       <para>
-         The path should be provided as a string, not a Nix path, since Nix
-         paths are copied into the world readable Nix store.
-       </para>
-     </warning>
-   </section>
-
-   <section xml:id="module-services-keycloak-hostname">
-     <title>Hostname</title>
-     <para>
-       The hostname is used to build the public URL used as base for
-       all frontend requests and must be configured through <xref
-       linkend="opt-services.keycloak.settings.hostname" />.
-     </para>
-
-     <note>
-       <para>
-         If you're migrating an old Wildfly based Keycloak instance
-         and want to keep compatibility with your current clients,
-         you'll likely want to set <xref
-         linkend="opt-services.keycloak.settings.http-relative-path"
-         /> to <literal>/auth</literal>. See the option description
-         for more details.
-       </para>
-     </note>
-
-     <para>
-       <xref linkend="opt-services.keycloak.settings.hostname-strict-backchannel" />
-       determines whether Keycloak should force all requests to go
-       through the frontend URL. By default,
-       <productname>Keycloak</productname> allows backend requests to
-       instead use its local hostname or IP address and may also
-       advertise it to clients through its OpenID Connect Discovery
-       endpoint.
-     </para>
-
-     <para>
-        For more information on hostname configuration, see the <link
-        xlink:href="https://www.keycloak.org/server/hostname">Hostname
-        section of the Keycloak Server Installation and Configuration
-        Guide</link>.
-     </para>
-   </section>
-
-   <section xml:id="module-services-keycloak-tls">
-     <title>Setting up TLS/SSL</title>
-     <para>
-       By default, <productname>Keycloak</productname> won't accept
-       unsecured HTTP connections originating from outside its local
-       network.
-     </para>
-
-     <para>
-       HTTPS support requires a TLS/SSL certificate and a private key,
-       both <link
-       xlink:href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail">PEM
-       formatted</link>. Their paths should be set through <xref
-       linkend="opt-services.keycloak.sslCertificate" /> and <xref
-       linkend="opt-services.keycloak.sslCertificateKey" />.
-     </para>
-
-     <warning>
-       <para>
-         The paths should be provided as a strings, not a Nix paths,
-         since Nix paths are copied into the world readable Nix store.
-       </para>
-     </warning>
-   </section>
-
-   <section xml:id="module-services-keycloak-themes">
-     <title>Themes</title>
-     <para>
-        You can package custom themes and make them visible to
-        Keycloak through <xref linkend="opt-services.keycloak.themes"
-        />. See the <link
-        xlink:href="https://www.keycloak.org/docs/latest/server_development/#_themes">
-        Themes section of the Keycloak Server Development Guide</link>
-        and the description of the aforementioned NixOS option for
-        more information.
-     </para>
-   </section>
-
-   <section xml:id="module-services-keycloak-settings">
-     <title>Configuration file settings</title>
-     <para>
-       Keycloak server configuration parameters can be set in <xref
-       linkend="opt-services.keycloak.settings" />. These correspond
-       directly to options in
-       <filename>conf/keycloak.conf</filename>. Some of the most
-       important parameters are documented as suboptions, the rest can
-       be found in the <link
-       xlink:href="https://www.keycloak.org/server/all-config">All
-       configuration section of the Keycloak Server Installation and
-       Configuration Guide</link>.
-     </para>
-
-     <para>
-       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 description of <xref
-       linkend="opt-services.keycloak.settings" /> for an example.
-     </para>
-   </section>
-
-
-   <section xml:id="module-services-keycloak-example-config">
-     <title>Example configuration</title>
-     <para>
-       A basic configuration with some custom settings could look like this:
-<programlisting>
-services.keycloak = {
-  <link linkend="opt-services.keycloak.enable">enable</link> = true;
-  settings = {
-    <link linkend="opt-services.keycloak.settings.hostname">hostname</link> = "keycloak.example.com";
-    <link linkend="opt-services.keycloak.settings.hostname-strict-backchannel">hostname-strict-backchannel</link> = true;
-  };
-  <link linkend="opt-services.keycloak.initialAdminPassword">initialAdminPassword</link> = "e6Wcm0RrtegMEHl";  # change on first login
-  <link linkend="opt-services.keycloak.sslCertificate">sslCertificate</link> = "/run/keys/ssl_cert";
-  <link linkend="opt-services.keycloak.sslCertificateKey">sslCertificateKey</link> = "/run/keys/ssl_key";
-  <link linkend="opt-services.keycloak.database.passwordFile">database.passwordFile</link> = "/run/keys/db_password";
-};
-</programlisting>
-     </para>
-
-   </section>
- </chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/komga.nix b/nixpkgs/nixos/modules/services/web-apps/komga.nix
index a2809e64a9c3..31f475fc7b04 100644
--- a/nixpkgs/nixos/modules/services/web-apps/komga.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/komga.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.komga = {
-      enable = mkEnableOption "Komga, a free and open source comics/mangas media server";
+      enable = mkEnableOption (lib.mdDoc "Komga, a free and open source comics/mangas media server");
 
       port = mkOption {
         type = types.port;
diff --git a/nixpkgs/nixos/modules/services/web-apps/lemmy.md b/nixpkgs/nixos/modules/services/web-apps/lemmy.md
index e6599cd843e3..faafe096d138 100644
--- a/nixpkgs/nixos/modules/services/web-apps/lemmy.md
+++ b/nixpkgs/nixos/modules/services/web-apps/lemmy.md
@@ -13,13 +13,10 @@ services.lemmy = {
     hostname = "lemmy.union.rocks";
     database.createLocally = true;
   };
-  jwtSecretPath = "/run/secrets/lemmyJwt";
   caddy.enable = true;
 }
 ```
 
-(note that you can use something like agenix to get your secret jwt to the specified path)
-
 this will start the backend on port 8536 and the frontend on port 1234.
 It will expose your instance with a caddy reverse proxy to the hostname you've provided.
 Postgres will be initialized on that same instance automatically.
diff --git a/nixpkgs/nixos/modules/services/web-apps/lemmy.nix b/nixpkgs/nixos/modules/services/web-apps/lemmy.nix
index 3e726149e93d..f48afd98a6c9 100644
--- a/nixpkgs/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/lemmy.nix
@@ -6,18 +6,15 @@ let
 in
 {
   meta.maintainers = with maintainers; [ happysalada ];
-  # Don't edit the docbook xml directly, edit the md and generate it:
-  # `pandoc lemmy.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > lemmy.xml`
-  meta.doc = ./lemmy.xml;
+  meta.doc = ./lemmy.md;
 
-  options.services.lemmy = {
+  imports = [
+    (mkRemovedOptionModule [ "services" "lemmy" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.")
+  ];
 
-    enable = mkEnableOption "lemmy a federated alternative to reddit in rust";
+  options.services.lemmy = {
 
-    jwtSecretPath = mkOption {
-      type = types.path;
-      description = lib.mdDoc "Path to read the jwt secret from.";
-    };
+    enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust");
 
     ui = {
       port = mkOption {
@@ -27,7 +24,9 @@ in
       };
     };
 
-    caddy.enable = mkEnableOption "exposing lemmy with the caddy reverse proxy";
+    caddy.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy");
+
+    database.createLocally = mkEnableOption (lib.mdDoc "creation of database on the instance");
 
     settings = mkOption {
       default = { };
@@ -49,7 +48,7 @@ in
         };
 
         options.federation = {
-          enabled = mkEnableOption "activitypub federation";
+          enabled = mkEnableOption (lib.mdDoc "activitypub federation");
         };
 
         options.captcha = {
@@ -64,18 +63,12 @@ in
             description = lib.mdDoc "The difficultly of the captcha to solve.";
           };
         };
-
-        options.database.createLocally = mkEnableOption "creation of database on the instance";
-
       };
     };
 
   };
 
   config =
-    let
-      localPostgres = (cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql");
-    in
     lib.mkIf cfg.enable {
       services.lemmy.settings = (mapAttrs (name: mkDefault)
         {
@@ -102,8 +95,13 @@ in
         };
       });
 
-      services.postgresql = mkIf localPostgres {
-        enable = mkDefault true;
+      services.postgresql = mkIf cfg.database.createLocally {
+        enable = true;
+        ensureDatabases = [ cfg.settings.database.database ];
+        ensureUsers = [{
+          name = cfg.settings.database.user;
+          ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
+        }];
       };
 
       services.pict-rs.enable = true;
@@ -117,7 +115,7 @@ in
               file_server
             }
             @for_backend {
-              path /api/* /pictrs/* feeds/* nodeinfo/*
+              path /api/* /pictrs/* /feeds/* /nodeinfo/*
             }
             handle @for_backend {
               reverse_proxy 127.0.0.1:${toString cfg.settings.port}
@@ -143,7 +141,7 @@ in
       };
 
       assertions = [{
-        assertion = cfg.settings.database.createLocally -> localPostgres;
+        assertion = cfg.database.createLocally -> cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql";
         message = "if you want to create the database locally, you need to use a local database";
       }];
 
@@ -158,28 +156,21 @@ in
         };
 
         documentation = [
-          "https://join-lemmy.org/docs/en/administration/from_scratch.html"
-          "https://join-lemmy.org/docs"
+          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
+          "https://join-lemmy.org/docs/en/"
         ];
 
         wantedBy = [ "multi-user.target" ];
 
-        after = [ "pict-rs.service" ] ++ lib.optionals cfg.settings.database.createLocally [ "lemmy-postgresql.service" ];
-
-        requires = lib.optionals cfg.settings.database.createLocally [ "lemmy-postgresql.service" ];
+        after = [ "pict-rs.service" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
 
-        # script is needed here since loadcredential is not accessible on ExecPreStart
-        script = ''
-          ${pkgs.coreutils}/bin/install -m 600 ${settingsFormat.generate "config.hjson" cfg.settings} /run/lemmy/config.hjson
-          jwtSecret="$(< $CREDENTIALS_DIRECTORY/jwt_secret )"
-          ${pkgs.jq}/bin/jq ".jwt_secret = \"$jwtSecret\"" /run/lemmy/config.hjson | ${pkgs.moreutils}/bin/sponge /run/lemmy/config.hjson
-          ${pkgs.lemmy-server}/bin/lemmy_server
-        '';
+        requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ];
 
         serviceConfig = {
           DynamicUser = true;
           RuntimeDirectory = "lemmy";
-          LoadCredential = "jwt_secret:${cfg.jwtSecretPath}";
+          ExecStartPre = "${pkgs.coreutils}/bin/install -m 600 ${settingsFormat.generate "config.hjson" cfg.settings} /run/lemmy/config.hjson";
+          ExecStart = "${pkgs.lemmy-server}/bin/lemmy_server";
         };
       };
 
@@ -194,8 +185,8 @@ in
         };
 
         documentation = [
-          "https://join-lemmy.org/docs/en/administration/from_scratch.html"
-          "https://join-lemmy.org/docs"
+          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
+          "https://join-lemmy.org/docs/en/"
         ];
 
         wantedBy = [ "multi-user.target" ];
@@ -210,27 +201,6 @@ in
           ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.lemmy-ui}/dist/js/server.js";
         };
       };
-
-      systemd.services.lemmy-postgresql = mkIf cfg.settings.database.createLocally {
-        description = "Lemmy postgresql db";
-        after = [ "postgresql.service" ];
-        partOf = [ "lemmy.service" ];
-        script = with cfg.settings.database; ''
-          PSQL() {
-            ${config.services.postgresql.package}/bin/psql --port=${toString cfg.settings.database.port} "$@"
-          }
-          # check if the database already exists
-          if ! PSQL -lqt | ${pkgs.coreutils}/bin/cut -d \| -f 1 | ${pkgs.gnugrep}/bin/grep -qw ${database} ; then
-            PSQL -tAc "CREATE ROLE ${user} WITH LOGIN;"
-            PSQL -tAc "CREATE DATABASE ${database} WITH OWNER ${user};"
-          fi
-        '';
-        serviceConfig = {
-          User = config.services.postgresql.superUser;
-          Type = "oneshot";
-          RemainAfterExit = true;
-        };
-      };
     };
 
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/lemmy.xml b/nixpkgs/nixos/modules/services/web-apps/lemmy.xml
deleted file mode 100644
index 0be9fb8aefa9..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/lemmy.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-lemmy">
-  <title>Lemmy</title>
-  <para>
-    Lemmy is a federated alternative to reddit in rust.
-  </para>
-  <section xml:id="module-services-lemmy-quickstart">
-    <title>Quickstart</title>
-    <para>
-      the minimum to start lemmy is
-    </para>
-    <programlisting language="bash">
-services.lemmy = {
-  enable = true;
-  settings = {
-    hostname = &quot;lemmy.union.rocks&quot;;
-    database.createLocally = true;
-  };
-  jwtSecretPath = &quot;/run/secrets/lemmyJwt&quot;;
-  caddy.enable = true;
-}
-</programlisting>
-    <para>
-      (note that you can use something like agenix to get your secret
-      jwt to the specified path)
-    </para>
-    <para>
-      this will start the backend on port 8536 and the frontend on port
-      1234. It will expose your instance with a caddy reverse proxy to
-      the hostname you’ve provided. Postgres will be initialized on that
-      same instance automatically.
-    </para>
-  </section>
-  <section xml:id="module-services-lemmy-usage">
-    <title>Usage</title>
-    <para>
-      On first connection you will be asked to define an admin user.
-    </para>
-  </section>
-  <section xml:id="module-services-lemmy-missing">
-    <title>Missing</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Exposing with nginx is not implemented yet.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          This has been tested using a local database with a unix socket
-          connection. Using different database settings will likely
-          require modifications
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix b/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
index e0995e0b5a44..920e6928ef5c 100644
--- a/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
@@ -32,7 +32,25 @@ in
   # interface
 
   options.services.limesurvey = {
-    enable = mkEnableOption "Limesurvey web application.";
+    enable = mkEnableOption (lib.mdDoc "Limesurvey web application");
+
+    encryptionKey = mkOption {
+      type = types.str;
+      default = "E17687FC77CEE247F0E22BB3ECF27FDE8BEC310A892347EC13013ABA11AA7EB5";
+      description = lib.mdDoc ''
+        This is a 32-byte key used to encrypt variables in the database.
+        You _must_ change this from the default value.
+      '';
+    };
+
+    encryptionNonce = mkOption {
+      type = types.str;
+      default = "1ACC8555619929DB91310BE848025A427B0F364A884FFA77";
+      description = lib.mdDoc ''
+        This is a 24-byte nonce used to encrypt variables in the database.
+        You _must_ change this from the default value.
+      '';
+    };
 
     database = {
       type = mkOption {
@@ -42,6 +60,12 @@ in
         description = lib.mdDoc "Database engine to use.";
       };
 
+      dbEngine = mkOption {
+        type = types.enum [ "MyISAM" "InnoDB" ];
+        default = "InnoDB";
+        description = lib.mdDoc "Database storage engine to use.";
+      };
+
       host = mkOption {
         type = types.str;
         default = "localhost";
@@ -49,7 +73,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = if cfg.database.type == "pgsql" then 5442 else 3306;
         defaultText = literalExpression "3306";
         description = lib.mdDoc "Database host port.";
@@ -180,6 +204,8 @@ in
       config = {
         tempdir = "${stateDir}/tmp";
         uploaddir = "${stateDir}/upload";
+        encryptionnonce = cfg.encryptionNonce;
+        encryptionsecretboxkey = cfg.encryptionKey;
         force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on";
         config.defaultlang = "en";
       };
@@ -200,6 +226,8 @@ in
 
     services.phpfpm.pools.limesurvey = {
       inherit user group;
+      phpPackage = pkgs.php81;
+      phpEnv.DBENGINE = "${cfg.database.dbEngine}";
       phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
       settings = {
         "listen.owner" = config.services.httpd.user;
@@ -256,11 +284,12 @@ in
       wantedBy = [ "multi-user.target" ];
       before = [ "phpfpm-limesurvey.service" ];
       after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.DBENGINE = "${cfg.database.dbEngine}";
       environment.LIMESURVEY_CONFIG = limesurveyConfig;
       script = ''
         # update or install the database as required
-        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
-        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+        ${pkgs.php81}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+        ${pkgs.php81}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
       '';
       serviceConfig = {
         User = user;
diff --git a/nixpkgs/nixos/modules/services/web-apps/mainsail.nix b/nixpkgs/nixos/modules/services/web-apps/mainsail.nix
new file mode 100644
index 000000000000..f335d9b015d4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/mainsail.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.mainsail;
+  moonraker = config.services.moonraker;
+in
+{
+  options.services.mainsail = {
+    enable = mkEnableOption (lib.mdDoc "a modern and responsive user interface for Klipper");
+
+    package = mkOption {
+      type = types.package;
+      description = lib.mdDoc "Mainsail package to be used in the module";
+      default = pkgs.mainsail;
+      defaultText = literalExpression "pkgs.mainsail";
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "Hostname to serve mainsail on";
+    };
+
+    nginx = mkOption {
+      type = types.submodule
+        (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
+      default = { };
+      example = literalExpression ''
+        {
+          serverAliases = [ "mainsail.''${config.networking.domain}" ];
+        }
+      '';
+      description = lib.mdDoc "Extra configuration for the nginx virtual host of mainsail.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.nginx = {
+      enable = true;
+      upstreams.mainsail-apiserver.servers."${moonraker.address}:${toString moonraker.port}" = { };
+      virtualHosts."${cfg.hostName}" = mkMerge [
+        cfg.nginx
+        {
+          root = mkForce "${cfg.package}/share/mainsail";
+          locations = {
+            "/" = {
+              index = "index.html";
+              tryFiles = "$uri $uri/ /index.html";
+            };
+            "/index.html".extraConfig = ''
+              add_header Cache-Control "no-store, no-cache, must-revalidate";
+            '';
+            "/websocket" = {
+              proxyWebsockets = true;
+              proxyPass = "http://mainsail-apiserver/websocket";
+            };
+            "~ ^/(printer|api|access|machine|server)/" = {
+              proxyWebsockets = true;
+              proxyPass = "http://mainsail-apiserver$request_uri";
+            };
+          };
+        }
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/mastodon.nix b/nixpkgs/nixos/modules/services/web-apps/mastodon.nix
index d0594ff74192..2ad6cd6aae19 100644
--- a/nixpkgs/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/mastodon.nix
@@ -1,7 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ lib, pkgs, config, options, ... }:
 
 let
   cfg = config.services.mastodon;
+  opt = options.services.mastodon;
+
   # We only want to create a database if we're actually going to connect to it.
   databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
 
@@ -23,7 +25,6 @@ let
     REDIS_HOST = cfg.redis.host;
     REDIS_PORT = toString(cfg.redis.port);
     DB_HOST = cfg.database.host;
-    DB_PORT = toString(cfg.database.port);
     DB_NAME = cfg.database.name;
     LOCAL_DOMAIN = cfg.localDomain;
     SMTP_SERVER = cfg.smtp.host;
@@ -37,7 +38,8 @@ let
 
     TRUSTED_PROXY_IP = cfg.trustedProxy;
   }
-  // (if cfg.smtp.authenticate then { SMTP_LOGIN  = cfg.smtp.user; } else {})
+  // lib.optionalAttrs (cfg.database.host != "/run/postgresql" && cfg.database.port != null) { DB_PORT = toString cfg.database.port; }
+  // lib.optionalAttrs cfg.smtp.authenticate { SMTP_LOGIN  = cfg.smtp.user; }
   // cfg.extraConfig;
 
   systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
@@ -46,6 +48,8 @@ let
     # User and group
     User = cfg.user;
     Group = cfg.group;
+    # Working directory
+    WorkingDirectory = cfg.package;
     # State directory and mode
     StateDirectory = "mastodon";
     StateDirectoryMode = "0750";
@@ -92,56 +96,90 @@ let
       ] else []
     ) env))));
 
-  mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" ''
+  mastodonTootctl = let
+    sourceExtraEnv = lib.concatMapStrings (p: "source ${p}\n") cfg.extraEnvFiles;
+  in pkgs.writeShellScriptBin "mastodon-tootctl" ''
     set -a
     export RAILS_ROOT="${cfg.package}"
     source "${envFile}"
     source /var/lib/mastodon/.secrets_env
-    eval -- "\$@"
+    ${sourceExtraEnv}
+
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env'
+    fi
+    $sudo ${cfg.package}/bin/tootctl "$@"
   '';
 
+  sidekiqUnits = lib.attrsets.mapAttrs' (name: processCfg:
+    lib.nameValuePair "mastodon-sidekiq-${name}" (let
+      jobClassArgs = toString (builtins.map (c: "-q ${c}") processCfg.jobClasses);
+      jobClassLabel = toString ([""] ++ processCfg.jobClasses);
+      threads = toString (if processCfg.threads == null then cfg.sidekiqThreads else processCfg.threads);
+    in {
+      after = [ "network.target" "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+      requires = [ "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+      description = "Mastodon sidekiq${jobClassLabel}";
+      wantedBy = [ "mastodon.target" ];
+      environment = env // {
+        PORT = toString(cfg.sidekiqPort);
+        DB_POOL = threads;
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/sidekiq ${jobClassArgs} -c ${threads} -r ${cfg.package}";
+        Restart = "always";
+        RestartSec = 20;
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
+        WorkingDirectory = cfg.package;
+        # System Call Filtering
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
+      } // cfgService;
+      path = with pkgs; [ file imagemagick ffmpeg ];
+    })
+  ) cfg.sidekiqProcesses;
+
 in {
 
   options = {
     services.mastodon = {
-      enable = lib.mkEnableOption "Mastodon, a federated social network server";
+      enable = lib.mkEnableOption (lib.mdDoc "Mastodon, a federated social network server");
 
       configureNginx = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Configure nginx as a reverse proxy for mastodon.
           Note that this makes some assumptions on your setup, and sets settings that will
           affect other virtualHosts running on your nginx instance, if any.
           Alternatively you can configure a reverse-proxy of your choice to serve these paths:
 
-          <literal>/ -> $(nix-instantiate --eval '&lt;nixpkgs&gt;' -A mastodon.outPath)/public</literal>
+          `/ -> $(nix-instantiate --eval '<nixpkgs>' -A mastodon.outPath)/public`
 
-          <literal>/ -> 127.0.0.1:{{ webPort }} </literal>(If there was no file in the directory above.)
+          `/ -> 127.0.0.1:{{ webPort }} `(If there was no file in the directory above.)
 
-          <literal>/system/ -> /var/lib/mastodon/public-system/</literal>
+          `/system/ -> /var/lib/mastodon/public-system/`
 
-          <literal>/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}</literal>
+          `/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}`
 
           Make sure that websockets are forwarded properly. You might want to set up caching
           of some requests. Take a look at mastodon's provided nginx configuration at
-          <literal>https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf</literal>.
+          `https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf`.
         '';
         type = lib.types.bool;
         default = false;
       };
 
       user = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           User under which mastodon runs. If it is set to "mastodon",
           that user will be created, otherwise it should be set to the
-          name of a user created elsewhere.  In both cases,
-          <package>mastodon</package> and a package containing only
-          the shell script <literal>mastodon-env</literal> will be added to
-          the user's package set. To run a command from
-          <package>mastodon</package> such as <literal>tootctl</literal>
-          with the environment configured by this module use
-          <literal>mastodon-env</literal>, as in:
-
-          <literal>mastodon-env tootctl accounts create newuser --email newuser@example.com</literal>
+          name of a user created elsewhere.
+          In both cases, the `mastodon` package will be added to the user's package set
+          and a tootctl wrapper to system packages that switches to the configured account
+          and load the right environment.
         '';
         type = lib.types.str;
         default = "mastodon";
@@ -190,12 +228,53 @@ in {
         type = lib.types.port;
         default = 55002;
       };
+
       sidekiqThreads = lib.mkOption {
-        description = lib.mdDoc "Worker threads used by the mastodon-sidekiq service.";
+        description = lib.mdDoc "Worker threads used by the mastodon-sidekiq-all service. If `sidekiqProcesses` is configured and any processes specify null `threads`, this value is used.";
         type = lib.types.int;
         default = 25;
       };
 
+      sidekiqProcesses = lib.mkOption {
+        description = lib.mdDoc "How many Sidekiq processes should be used to handle background jobs, and which job classes they handle. *Read the [upstream documentation](https://docs.joinmastodon.org/admin/scaling/#sidekiq) before configuring this!*";
+        type = with lib.types; attrsOf (submodule {
+          options = {
+            jobClasses = lib.mkOption {
+              type = listOf (enum [ "default" "push" "pull" "mailers" "scheduler" "ingress" ]);
+              description = lib.mdDoc "If not empty, which job classes should be executed by this process. *Only one process should handle the 'scheduler' class. If left empty, this process will handle the 'scheduler' class.*";
+            };
+            threads = lib.mkOption {
+              type = nullOr int;
+              description = lib.mdDoc "Number of threads this process should use for executing jobs. If null, the configured `sidekiqThreads` are used.";
+            };
+          };
+        });
+        default = {
+          all = {
+            jobClasses = [ ];
+            threads = null;
+          };
+        };
+        example = {
+          all = {
+            jobClasses = [ ];
+            threads = null;
+          };
+          ingress = {
+            jobClasses = [ "ingress" ];
+            threads = 5;
+          };
+          default = {
+            jobClasses = [ "default" ];
+            threads = 10;
+          };
+          push-pull = {
+            jobClasses = [ "push" "pull" ];
+            threads = 5;
+          };
+        };
+      };
+
       vapidPublicKeyFile = lib.mkOption {
         description = lib.mdDoc ''
           Path to file containing the public key used for Web Push
@@ -313,8 +392,13 @@ in {
         };
 
         port = lib.mkOption {
-          type = lib.types.int;
-          default = 5432;
+          type = lib.types.nullOr lib.types.port;
+          default = if cfg.database.createLocally then null else 5432;
+          defaultText = lib.literalExpression ''
+            if config.${opt.database.createLocally}
+            then null
+            else 5432
+          '';
           description = lib.mdDoc "Database host port.";
         };
 
@@ -332,8 +416,8 @@ in {
 
         passwordFile = lib.mkOption {
           type = lib.types.nullOr lib.types.path;
-          default = "/var/lib/mastodon/secrets/db-password";
-          example = "/run/keys/mastodon-db-password";
+          default = null;
+          example = "/var/lib/mastodon/secrets/db-password";
           description = lib.mdDoc ''
             A file containing the password corresponding to
             {option}`database.user`.
@@ -372,17 +456,19 @@ in {
         };
 
         user = lib.mkOption {
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "mastodon@example.com";
           description = lib.mdDoc "SMTP login name.";
-          type = lib.types.str;
         };
 
         passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/mastodon/secrets/smtp-password";
           description = lib.mdDoc ''
             Path to file containing the SMTP password.
           '';
-          default = "/var/lib/mastodon/secrets/smtp-password";
-          example = "/run/keys/mastodon-smtp-password";
-          type = lib.types.str;
         };
       };
 
@@ -418,6 +504,15 @@ in {
         '';
       };
 
+      extraEnvFiles = lib.mkOption {
+        type = with lib.types; listOf path;
+        default = [];
+        description = lib.mdDoc ''
+          Extra environment files to pass to all mastodon services. Useful for passing down environmental secrets.
+        '';
+        example = [ "/etc/mastodon/s3config.env" ];
+      };
+
       automaticMigrations = lib.mkOption {
         type = lib.types.bool;
         default = true;
@@ -425,17 +520,91 @@ in {
           Do automatic database migrations.
         '';
       };
+
+      mediaAutoRemove = {
+        enable = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          example = false;
+          description = lib.mdDoc ''
+            Automatically remove remote media attachments and preview cards older than the configured amount of days.
+
+            Recommended in https://docs.joinmastodon.org/admin/setup/.
+          '';
+        };
+
+        startAt = lib.mkOption {
+          type = lib.types.str;
+          default = "daily";
+          example = "hourly";
+          description = lib.mdDoc ''
+            How often to remove remote media.
+
+            The format is described in {manpage}`systemd.time(7)`.
+          '';
+        };
+
+        olderThanDays = lib.mkOption {
+          type = lib.types.int;
+          default = 30;
+          example = 14;
+          description = lib.mdDoc ''
+            How old remote media needs to be in order to be removed.
+          '';
+        };
+      };
     };
   };
 
-  config = lib.mkIf cfg.enable {
+  config = lib.mkIf cfg.enable (lib.mkMerge [{
     assertions = [
       {
         assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
-        message = ''For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user and services.mastodon.database.user must be identical.'';
+        message = ''
+          For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer
+            authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user
+            and services.mastodon.database.user must be identical.
+        '';
+      }
+      {
+        assertion = !databaseActuallyCreateLocally -> (cfg.database.host != "/run/postgresql");
+        message = ''
+          <option>services.mastodon.database.host</option> needs to be set if
+            <option>services.mastodon.database.createLocally</option> is not enabled.
+        '';
+      }
+      {
+        assertion = cfg.smtp.authenticate -> (cfg.smtp.user != null);
+        message = ''
+          <option>services.mastodon.smtp.user</option> needs to be set if
+            <option>services.mastodon.smtp.authenticate</option> is enabled.
+        '';
+      }
+      {
+        assertion = cfg.smtp.authenticate -> (cfg.smtp.passwordFile != null);
+        message = ''
+          <option>services.mastodon.smtp.passwordFile</option> needs to be set if
+            <option>services.mastodon.smtp.authenticate</option> is enabled.
+        '';
+      }
+      {
+        assertion = 1 ==
+          (lib.count (x: x)
+            (lib.mapAttrsToList
+              (_: v: builtins.elem "scheduler" v.jobClasses || v.jobClasses == [ ])
+              cfg.sidekiqProcesses));
+        message = "There must be exactly one Sidekiq queue in services.mastodon.sidekiqProcesses with jobClass \"scheduler\".";
       }
     ];
 
+    environment.systemPackages = [ mastodonTootctl ];
+
+    systemd.targets.mastodon = {
+      description = "Target for all Mastodon services";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+    };
+
     systemd.services.mastodon-init-dirs = {
       script = ''
         umask 077
@@ -460,26 +629,35 @@ in {
         OTP_SECRET="$(cat ${cfg.otpSecretFile})"
         VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})"
         VAPID_PUBLIC_KEY="$(cat ${cfg.vapidPublicKeyFile})"
+      '' + lib.optionalString (cfg.database.passwordFile != null) ''
         DB_PASS="$(cat ${cfg.database.passwordFile})"
-      '' + (if cfg.smtp.authenticate then ''
+      '' + lib.optionalString cfg.smtp.authenticate ''
         SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
-      '' else "") + ''
+      '' + ''
         EOF
       '';
       environment = env;
       serviceConfig = {
         Type = "oneshot";
-        WorkingDirectory = cfg.package;
+        SyslogIdentifier = "mastodon-init-dirs";
         # System Call Filtering
         SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
       } // cfgService;
 
       after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
     };
 
     systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
-      script = ''
+      script = lib.optionalString (!databaseActuallyCreateLocally) ''
+        umask 077
+
+        export PGPASSFILE
+        PGPASSFILE=$(mktemp)
+        cat > $PGPASSFILE <<EOF
+        ${cfg.database.host}:${toString cfg.database.port}:${cfg.database.name}:${cfg.database.user}:$(cat ${cfg.database.passwordFile})
+        EOF
+
+      '' + ''
         if [ `psql ${cfg.database.name} -c \
                 "select count(*) from pg_class c \
                 join pg_namespace s on s.oid = c.relnamespace \
@@ -490,26 +668,37 @@ in {
         else
           rails db:migrate
         fi
+      '' +  lib.optionalString (!databaseActuallyCreateLocally) ''
+        rm $PGPASSFILE
+        unset PGPASSFILE
       '';
       path = [ cfg.package pkgs.postgresql ];
-      environment = env;
+      environment = env // lib.optionalAttrs (!databaseActuallyCreateLocally) {
+        PGHOST = cfg.database.host;
+        PGUSER = cfg.database.user;
+      };
       serviceConfig = {
         Type = "oneshot";
-        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
         WorkingDirectory = cfg.package;
         # System Call Filtering
         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" ];
+      after = [ "network.target" "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
+      requires = [ "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
     };
 
     systemd.services.mastodon-streaming = {
-      after = [ "network.target" ]
-        ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
-        ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
+      after = [ "network.target" "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+      requires = [ "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+      wantedBy = [ "mastodon.target" ];
       description = "Mastodon streaming";
-      wantedBy = [ "multi-user.target" ];
       environment = env // (if cfg.enableUnixSocket
         then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
         else { PORT = toString(cfg.streamingPort); }
@@ -518,7 +707,7 @@ in {
         ExecStart = "${cfg.package}/run-streaming.sh";
         Restart = "always";
         RestartSec = 20;
-        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
         WorkingDirectory = cfg.package;
         # Runtime directory and mode
         RuntimeDirectory = "mastodon-streaming";
@@ -529,11 +718,14 @@ in {
     };
 
     systemd.services.mastodon-web = {
-      after = [ "network.target" ]
-        ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
-        ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
+      after = [ "network.target" "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+      requires = [ "mastodon-init-dirs.service" ]
+        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+      wantedBy = [ "mastodon.target" ];
       description = "Mastodon web";
-      wantedBy = [ "multi-user.target" ];
       environment = env // (if cfg.enableUnixSocket
         then { SOCKET = "/run/mastodon-web/web.socket"; }
         else { PORT = toString(cfg.webPort); }
@@ -542,7 +734,7 @@ in {
         ExecStart = "${cfg.package}/bin/puma -C config/puma.rb";
         Restart = "always";
         RestartSec = 20;
-        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
         WorkingDirectory = cfg.package;
         # Runtime directory and mode
         RuntimeDirectory = "mastodon-web";
@@ -553,26 +745,20 @@ in {
       path = with pkgs; [ file imagemagick ffmpeg ];
     };
 
-    systemd.services.mastodon-sidekiq = {
-      after = [ "network.target" ]
-        ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
-        ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
-      description = "Mastodon sidekiq";
-      wantedBy = [ "multi-user.target" ];
-      environment = env // {
-        PORT = toString(cfg.sidekiqPort);
-        DB_POOL = toString cfg.sidekiqThreads;
-      };
+    systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable {
+      description = "Mastodon media auto remove";
+      environment = env;
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/sidekiq -c ${toString cfg.sidekiqThreads} -r ${cfg.package}";
-        Restart = "always";
-        RestartSec = 20;
-        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
-        WorkingDirectory = cfg.package;
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
+        Type = "oneshot";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
       } // cfgService;
-      path = with pkgs; [ file imagemagick ffmpeg ];
+      script = let
+        olderThanDays = toString cfg.mediaAutoRemove.olderThanDays;
+      in ''
+        ${cfg.package}/bin/tootctl media remove --days=${olderThanDays}
+        ${cfg.package}/bin/tootctl preview_cards remove --days=${olderThanDays}
+      '';
+      startAt = cfg.mediaAutoRemove.startAt;
     };
 
     services.nginx = lib.mkIf cfg.configureNginx {
@@ -580,8 +766,9 @@ in {
       recommendedProxySettings = true; # required for redirections to work
       virtualHosts."${cfg.localDomain}" = {
         root = "${cfg.package}/public/";
-        forceSSL = true; # mastodon only supports https
-        enableACME = true;
+        # mastodon only supports https, but you can override this if you offload tls elsewhere.
+        forceSSL = lib.mkDefault true;
+        enableACME = lib.mkDefault true;
 
         locations."/system/".alias = "/var/lib/mastodon/public-system/";
 
@@ -629,11 +816,13 @@ in {
           inherit (cfg) group;
         };
       })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv ])
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package pkgs.imagemagick ])
     ];
 
     users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
-  };
+  }
+  { systemd.services = sidekiqUnits; }
+  ]);
 
   meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml b/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml
deleted file mode 100644
index 69d1170e4523..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-matomo">
- <title>Matomo</title>
- <para>
-  Matomo is a real-time web analytics application. This module configures
-  php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
- </para>
- <para>
-  An automatic setup is not suported by Matomo, so you need to configure Matomo
-  itself in the browser-based Matomo setup.
- </para>
- <section xml:id="module-services-matomo-database-setup">
-  <title>Database Setup</title>
-
-  <para>
-   You also need to configure a MariaDB or MySQL database and -user for Matomo
-   yourself, and enter those credentials in your browser. You can use
-   passwordless database authentication via the UNIX_SOCKET authentication
-   plugin with the following SQL commands:
-<programlisting>
-# For MariaDB
-INSTALL PLUGIN unix_socket SONAME 'auth_socket';
-CREATE DATABASE matomo;
-CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
-GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-
-# For MySQL
-INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
-CREATE DATABASE matomo;
-CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
-GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-</programlisting>
-   Then fill in <literal>matomo</literal> as database user and database name,
-   and leave the password field blank. This authentication works by allowing
-   only the <literal>matomo</literal> unix user to authenticate as the
-   <literal>matomo</literal> database user (without needing a password), but no
-   other users. For more information on passwordless login, see
-   <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
-  </para>
-
-  <para>
-   Of course, you can use password based authentication as well, e.g. when the
-   database is not on the same host.
-  </para>
- </section>
- <section xml:id="module-services-matomo-archive-processing">
-  <title>Archive Processing</title>
-
-  <para>
-   This module comes with the systemd service
-   <literal>matomo-archive-processing.service</literal> and a timer that
-   automatically triggers archive processing every hour. This means that you
-   can safely
-   <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">
-   disable browser triggers for Matomo archiving </link> at
-   <literal>Administration > System > General Settings</literal>.
-  </para>
-
-  <para>
-   With automatic archive processing, you can now also enable to
-   <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">
-   delete old visitor logs </link> at <literal>Administration > System >
-   Privacy</literal>, but make sure that you run <literal>systemctl start
-   matomo-archive-processing.service</literal> at least once without errors if
-   you have already collected data before, so that the reports get archived
-   before the source data gets deleted.
-  </para>
- </section>
- <section xml:id="module-services-matomo-backups">
-  <title>Backup</title>
-
-  <para>
-   You only need to take backups of your MySQL database and the
-   <filename>/var/lib/matomo/config/config.ini.php</filename> file. Use a user
-   in the <literal>matomo</literal> group or root to access the file. For more
-   information, see
-   <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
-  </para>
- </section>
- <section xml:id="module-services-matomo-issues">
-  <title>Issues</title>
-
-  <itemizedlist>
-   <listitem>
-    <para>
-     Matomo will warn you that the JavaScript tracker is not writable. This is
-     because it's located in the read-only nix store. You can safely ignore
-     this, unless you need a plugin that needs JavaScript tracker access.
-    </para>
-   </listitem>
-  </itemizedlist>
- </section>
- <section xml:id="module-services-matomo-other-web-servers">
-  <title>Using other Web Servers than nginx</title>
-
-  <para>
-   You can use other web servers by forwarding calls for
-   <filename>index.php</filename> and <filename>piwik.php</filename> to the
-   <literal><link linkend="opt-services.phpfpm.pools._name_.socket">services.phpfpm.pools.&lt;name&gt;.socket</link></literal> fastcgi unix socket. You can use
-   the nginx configuration in the module code as a reference to what else
-   should be configured.
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/matomo.md b/nixpkgs/nixos/modules/services/web-apps/matomo.md
new file mode 100644
index 000000000000..e750c0c14775
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo.md
@@ -0,0 +1,77 @@
+# Matomo {#module-services-matomo}
+
+Matomo is a real-time web analytics application. This module configures
+php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
+
+An automatic setup is not supported by Matomo, so you need to configure Matomo
+itself in the browser-based Matomo setup.
+
+## Database Setup {#module-services-matomo-database-setup}
+
+You also need to configure a MariaDB or MySQL database and -user for Matomo
+yourself, and enter those credentials in your browser. You can use
+passwordless database authentication via the UNIX_SOCKET authentication
+plugin with the following SQL commands:
+```
+# For MariaDB
+INSTALL PLUGIN unix_socket SONAME 'auth_socket';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+
+# For MySQL
+INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+```
+Then fill in `matomo` as database user and database name,
+and leave the password field blank. This authentication works by allowing
+only the `matomo` unix user to authenticate as the
+`matomo` database user (without needing a password), but no
+other users. For more information on passwordless login, see
+<https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/>.
+
+Of course, you can use password based authentication as well, e.g. when the
+database is not on the same host.
+
+## Archive Processing {#module-services-matomo-archive-processing}
+
+This module comes with the systemd service
+`matomo-archive-processing.service` and a timer that
+automatically triggers archive processing every hour. This means that you
+can safely
+[disable browser triggers for Matomo archiving](
+https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour
+) at
+`Administration > System > General Settings`.
+
+With automatic archive processing, you can now also enable to
+[delete old visitor logs](https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs)
+at `Administration > System > Privacy`, but make sure that you run `systemctl start
+matomo-archive-processing.service` at least once without errors if
+you have already collected data before, so that the reports get archived
+before the source data gets deleted.
+
+## Backup {#module-services-matomo-backups}
+
+You only need to take backups of your MySQL database and the
+{file}`/var/lib/matomo/config/config.ini.php` file. Use a user
+in the `matomo` group or root to access the file. For more
+information, see
+<https://matomo.org/faq/how-to-install/faq_138/>.
+
+## Issues {#module-services-matomo-issues}
+
+  - Matomo will warn you that the JavaScript tracker is not writable. This is
+    because it's located in the read-only nix store. You can safely ignore
+    this, unless you need a plugin that needs JavaScript tracker access.
+
+## Using other Web Servers than nginx {#module-services-matomo-other-web-servers}
+
+You can use other web servers by forwarding calls for
+{file}`index.php` and {file}`piwik.php` to the
+[`services.phpfpm.pools.<name>.socket`](#opt-services.phpfpm.pools._name_.socket)
+fastcgi unix socket. You can use
+the nginx configuration in the module code as a reference to what else
+should be configured.
diff --git a/nixpkgs/nixos/modules/services/web-apps/matomo.nix b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
index 80c4db1263e1..eadf8b62b977 100644
--- a/nixpkgs/nixos/modules/services/web-apps/matomo.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
@@ -12,8 +12,6 @@ let
   phpExecutionUnit = "phpfpm-${pool}";
   databaseService = "mysql.service";
 
-  fqdn = if config.networking.domain != null then config.networking.fqdn else config.networking.hostName;
-
 in {
   imports = [
     (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ])
@@ -53,8 +51,8 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "lighttpd";
-        description = ''
-          Name of the web server user that forwards requests to <option>services.phpfpm.pools.&lt;name&gt;.socket</option> the fastcgi socket for Matomo if the nginx
+        description = lib.mdDoc ''
+          Name of the web server user that forwards requests to {option}`services.phpfpm.pools.<name>.socket` the fastcgi socket for Matomo if the nginx
           option is not used. Either this option or the nginx option is mandatory.
           If you want to use another webserver than nginx, you need to set this to that server's user
           and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket.
@@ -77,11 +75,9 @@ in {
 
       hostname = mkOption {
         type = types.str;
-        default = "${user}.${fqdn}";
+        default = "${user}.${config.networking.fqdnOrHostName}";
         defaultText = literalExpression ''
-          if config.${options.networking.domain} != null
-          then "${user}.''${config.${options.networking.fqdn}}"
-          else "${user}.''${config.${options.networking.hostName}}"
+          "${user}.''${config.${options.networking.fqdnOrHostName}}"
         '';
         example = "matomo.yourdomain.org";
         description = lib.mdDoc ''
@@ -178,7 +174,7 @@ in {
           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
+            # keeping tmp around between upgrades seems to bork stuff, so delete it
             rm -rf ${dataDir}/tmp
           fi
         elif [ -e ${dataDir}/tmp ]; then
@@ -329,7 +325,7 @@ in {
   };
 
   meta = {
-    doc = ./matomo-doc.xml;
+    doc = ./matomo.md;
     maintainers = with lib.maintainers; [ florianjacob ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
index 6e9e2abcaa8c..db5122e79f00 100644
--- a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
@@ -101,12 +101,12 @@ in
 {
   options = {
     services.mattermost = {
-      enable = mkEnableOption "Mattermost chat server";
+      enable = mkEnableOption (lib.mdDoc "Mattermost chat server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.mattermost;
-        defaultText = "pkgs.mattermost";
+        defaultText = lib.literalExpression "pkgs.mattermost";
         description = lib.mdDoc "Mattermost derivation to use.";
       };
 
@@ -170,7 +170,7 @@ in
         type = types.attrs;
         default = { };
         description = lib.mdDoc ''
-          Addtional configuration options as Nix attribute set in config.json schema.
+          Additional configuration options as Nix attribute set in config.json schema.
         '';
       };
 
@@ -184,6 +184,22 @@ in
           .tar.gz files.
         '';
       };
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          Environment file (see {manpage}`systemd.exec(5)`
+          "EnvironmentFile=" section for the syntax) which sets config options
+          for mattermost (see [the mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)).
+
+          Settings defined in the environment file will overwrite settings
+          set via nix or via the {option}`services.mattermost.extraConfig`
+          option.
+
+          Useful for setting config options without their value ending up in the
+          (world-readable) nix store, e.g. for a database password.
+        '';
+      };
 
       localDatabaseCreate = mkOption {
         type = types.bool;
@@ -234,11 +250,11 @@ in
       };
 
       matterircd = {
-        enable = mkEnableOption "Mattermost IRC bridge";
+        enable = mkEnableOption (lib.mdDoc "Mattermost IRC bridge");
         package = mkOption {
           type = types.package;
           default = pkgs.matterircd;
-          defaultText = "pkgs.matterircd";
+          defaultText = lib.literalExpression "pkgs.matterircd";
           description = lib.mdDoc "matterircd derivation to use.";
         };
         parameters = mkOption {
@@ -321,6 +337,7 @@ in
           Restart = "always";
           RestartSec = "10";
           LimitNOFILE = "49152";
+          EnvironmentFile = cfg.environmentFile;
         };
         unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
       };
diff --git a/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
index 01083eff612b..21c587694c6e 100644
--- a/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
@@ -8,7 +8,8 @@ let
   cfg = config.services.mediawiki;
   fpm = config.services.phpfpm.pools.mediawiki;
   user = "mediawiki";
-  group = config.services.httpd.group;
+  group = if cfg.webserver == "apache" then config.services.httpd.group else "mediawiki";
+
   cacheDir = "/var/cache/mediawiki";
   stateDir = "/var/lib/mediawiki";
 
@@ -35,7 +36,7 @@ let
   };
 
   mediawikiScripts = pkgs.runCommand "mediawiki-scripts" {
-    buildInputs = [ pkgs.makeWrapper ];
+    nativeBuildInputs = [ pkgs.makeWrapper ];
     preferLocalBuild = true;
   } ''
     mkdir -p $out/bin
@@ -46,6 +47,15 @@ let
     done
   '';
 
+  dbAddr = if cfg.database.socket == null then
+    "${cfg.database.host}:${toString cfg.database.port}"
+  else if cfg.database.type == "mysql" then
+    "${cfg.database.host}:${cfg.database.socket}"
+  else if cfg.database.type == "postgres" then
+    "${cfg.database.socket}"
+  else
+    throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}";
+
   mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
     <?php
       # Protect against web entry
@@ -64,7 +74,7 @@ let
       $wgScriptPath = "";
 
       ## The protocol and server name to use in fully-qualified URLs
-      $wgServer = "${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}";
+      $wgServer = "${cfg.url}";
 
       ## The URL path to static resources (images, scripts, etc.)
       $wgResourceBasePath = $wgScriptPath;
@@ -78,8 +88,7 @@ let
       $wgEnableEmail = true;
       $wgEnableUserEmail = true; # UPO
 
-      $wgEmergencyContact = "${if cfg.virtualHost.adminAddr != null then cfg.virtualHost.adminAddr else config.services.httpd.adminAddr}";
-      $wgPasswordSender = $wgEmergencyContact;
+      $wgPasswordSender = "${cfg.passwordSender}";
 
       $wgEnotifUserTalk = false; # UPO
       $wgEnotifWatchlist = false; # UPO
@@ -87,7 +96,8 @@ let
 
       ## Database settings
       $wgDBtype = "${cfg.database.type}";
-      $wgDBserver = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}";
+      $wgDBserver = "${dbAddr}";
+      $wgDBport = "${toString cfg.database.port}";
       $wgDBname = "${cfg.database.name}";
       $wgDBuser = "${cfg.database.user}";
       ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
@@ -129,7 +139,7 @@ let
 
       ## Set $wgCacheDirectory to a writable directory on the web server
       ## to make your wiki go slightly faster. The directory should not
-      ## be publically accessible from the web.
+      ## be publicly accessible from the web.
       $wgCacheDirectory = "${cacheDir}";
 
       # Site language code, should be one of the list in ./languages/data/Names.php
@@ -171,7 +181,7 @@ in
   options = {
     services.mediawiki = {
 
-      enable = mkEnableOption "MediaWiki";
+      enable = mkEnableOption (lib.mdDoc "MediaWiki");
 
       package = mkOption {
         type = types.package;
@@ -180,6 +190,16 @@ in
         description = lib.mdDoc "Which MediaWiki package to use.";
       };
 
+      finalPackage = mkOption {
+        type = types.package;
+        readOnly = true;
+        default = pkg;
+        defaultText = literalExpression "pkg";
+        description = lib.mdDoc ''
+          The final package used by the module. This is the package that will have extensions and skins installed.
+        '';
+      };
+
       name = mkOption {
         type = types.str;
         default = "MediaWiki";
@@ -187,6 +207,22 @@ in
         description = lib.mdDoc "Name of the wiki.";
       };
 
+      url = mkOption {
+        type = types.str;
+        default = if cfg.webserver == "apache" then
+            "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}"
+          else
+            "http://localhost";
+        defaultText = literalExpression ''
+          if cfg.webserver == "apache" then
+            "''${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://''${cfg.httpd.virtualHost.hostName}"
+          else
+            "http://localhost";
+        '';
+        example = "https://wiki.example.org";
+        description = lib.mdDoc "URL of the wiki.";
+      };
+
       uploadsDir = mkOption {
         type = types.nullOr types.path;
         default = "${stateDir}/uploads";
@@ -202,6 +238,24 @@ in
         example = "/run/keys/mediawiki-password";
       };
 
+      passwordSender = mkOption {
+        type = types.str;
+        default =
+          if cfg.webserver == "apache" then
+            if cfg.httpd.virtualHost.adminAddr != null then
+              cfg.httpd.virtualHost.adminAddr
+            else
+              config.services.httpd.adminAddr else "root@localhost";
+        defaultText = literalExpression ''
+          if cfg.webserver == "apache" then
+            if cfg.httpd.virtualHost.adminAddr != null then
+              cfg.httpd.virtualHost.adminAddr
+            else
+              config.services.httpd.adminAddr else "root@localhost"
+        '';
+        description = lib.mdDoc "Contact address for password reset.";
+      };
+
       skins = mkOption {
         default = {};
         type = types.attrsOf types.path;
@@ -231,6 +285,12 @@ in
         '';
       };
 
+      webserver = mkOption {
+        type = types.enum [ "apache" "none" ];
+        default = "apache";
+        description = lib.mdDoc "Webserver to use.";
+      };
+
       database = {
         type = mkOption {
           type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
@@ -246,7 +306,8 @@ in
 
         port = mkOption {
           type = types.port;
-          default = 3306;
+          default = if cfg.database.type == "mysql" then 3306 else 5432;
+          defaultText = literalExpression "3306";
           description = lib.mdDoc "Database host port.";
         };
 
@@ -286,14 +347,19 @@ in
 
         socket = mkOption {
           type = types.nullOr types.path;
-          default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null;
+          default = if (cfg.database.type == "mysql" && cfg.database.createLocally) then
+              "/run/mysqld/mysqld.sock"
+            else if (cfg.database.type == "postgres" && cfg.database.createLocally) then
+              "/run/postgresql"
+            else
+              null;
           defaultText = literalExpression "/run/mysqld/mysqld.sock";
           description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
 
         createLocally = mkOption {
           type = types.bool;
-          default = cfg.database.type == "mysql";
+          default = cfg.database.type == "mysql" || cfg.database.type == "postgres";
           defaultText = literalExpression "true";
           description = lib.mdDoc ''
             Create the database and database user locally.
@@ -302,7 +368,7 @@ in
         };
       };
 
-      virtualHost = mkOption {
+      httpd.virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
         example = literalExpression ''
           {
@@ -350,12 +416,16 @@ in
     };
   };
 
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "mediawiki" "virtualHost" ] [ "services" "mediawiki" "httpd" "virtualHost" ])
+  ];
+
   # implementation
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
-        message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'";
+      { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres");
+        message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'";
       }
       { assertion = cfg.database.createLocally -> cfg.database.user == user;
         message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
@@ -374,50 +444,64 @@ in
       Vector = "${cfg.package}/share/mediawiki/skins/Vector";
     };
 
-    services.mysql = mkIf cfg.database.createLocally {
+    services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) {
       enable = true;
       package = mkDefault pkgs.mariadb;
       ensureDatabases = [ cfg.database.name ];
-      ensureUsers = [
-        { name = cfg.database.user;
-          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
-        }
-      ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+      }];
+    };
+
+    services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; };
+      }];
     };
 
     services.phpfpm.pools.mediawiki = {
       inherit user group;
       phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
-      settings = {
+      settings = (if (cfg.webserver == "apache") then {
         "listen.owner" = config.services.httpd.user;
         "listen.group" = config.services.httpd.group;
-      } // cfg.poolConfig;
+      } else {
+        "listen.owner" = user;
+        "listen.group" = group;
+      }) // cfg.poolConfig;
     };
 
-    services.httpd = {
+    services.httpd = lib.mkIf (cfg.webserver == "apache") {
       enable = true;
       extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${pkg}/share/mediawiki";
-        extraConfig = ''
-          <Directory "${pkg}/share/mediawiki">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-
-            Require all granted
-            DirectoryIndex index.php
-            AllowOverride All
-          </Directory>
-        '' + optionalString (cfg.uploadsDir != null) ''
-          Alias "/images" "${cfg.uploadsDir}"
-          <Directory "${cfg.uploadsDir}">
-            Require all granted
-          </Directory>
-        '';
-      } ];
+      virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [
+        cfg.httpd.virtualHost
+        {
+          documentRoot = mkForce "${pkg}/share/mediawiki";
+          extraConfig = ''
+            <Directory "${pkg}/share/mediawiki">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+
+              Require all granted
+              DirectoryIndex index.php
+              AllowOverride All
+            </Directory>
+          '' + optionalString (cfg.uploadsDir != null) ''
+            Alias "/images" "${cfg.uploadsDir}"
+            <Directory "${cfg.uploadsDir}">
+              Require all granted
+            </Directory>
+          '';
+        }
+      ];
     };
 
     systemd.tmpfiles.rules = [
@@ -431,7 +515,8 @@ in
     systemd.services.mediawiki-init = {
       wantedBy = [ "multi-user.target" ];
       before = [ "phpfpm-mediawiki.service" ];
-      after = optional cfg.database.createLocally "mysql.service";
+      after = optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service"
+              ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.service";
       script = ''
         if ! test -e "${stateDir}/secret.key"; then
           tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key
@@ -442,13 +527,14 @@ in
         ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
           --confpath /tmp \
           --scriptpath / \
-          --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \
+          --dbserver "${dbAddr}" \
           --dbport ${toString cfg.database.port} \
           --dbname ${cfg.database.name} \
           ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \
           --dbuser ${cfg.database.user} \
           ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
           --passfile ${cfg.passwordFile} \
+          --dbtype ${cfg.database.type} \
           ${cfg.name} \
           admin
 
@@ -463,12 +549,14 @@ in
       };
     };
 
-    systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service";
+    systemd.services.httpd.after = optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"
+      ++ optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service";
 
     users.users.${user} = {
       group = group;
       isSystemUser = true;
     };
+    users.groups.${group} = {};
 
     environment.systemPackages = [ mediawikiScripts ];
   };
diff --git a/nixpkgs/nixos/modules/services/web-apps/miniflux.nix b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
index 55e3664bee68..7cc8ce10ffe0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
@@ -19,7 +19,14 @@ in
 {
   options = {
     services.miniflux = {
-      enable = mkEnableOption "miniflux and creates a local postgres database for it";
+      enable = mkEnableOption (lib.mdDoc "miniflux and creates a local postgres database for it");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.miniflux;
+        defaultText = literalExpression "pkgs.miniflux";
+        description = lib.mdDoc "Miniflux package to use.";
+      };
 
       config = mkOption {
         type = types.attrsOf types.str;
@@ -89,7 +96,7 @@ in
       after = [ "network.target" "postgresql.service" "miniflux-dbsetup.service" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.miniflux}/bin/miniflux";
+        ExecStart = "${cfg.package}/bin/miniflux";
         User = dbUser;
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
@@ -116,12 +123,12 @@ in
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
         UMask = "0077";
       };
 
       environment = cfg.config;
     };
-    environment.systemPackages = [ pkgs.miniflux ];
+    environment.systemPackages = [ cfg.package ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/monica.nix b/nixpkgs/nixos/modules/services/web-apps/monica.nix
new file mode 100644
index 000000000000..2bff42f7ffa4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/monica.nix
@@ -0,0 +1,468 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.services.monica;
+  monica = pkgs.monica.override {
+    dataDir = cfg.dataDir;
+  };
+  db = cfg.database;
+  mail = cfg.mail;
+
+  user = cfg.user;
+  group = cfg.group;
+
+  # shell script for local administration
+  artisan = pkgs.writeScriptBin "monica" ''
+    #! ${pkgs.runtimeShell}
+    cd ${monica}
+    sudo() {
+      if [[ "$USER" != ${user} ]]; then
+        exec /run/wrappers/bin/sudo -u ${user} "$@"
+      else
+        exec "$@"
+      fi
+    }
+    sudo ${pkgs.php}/bin/php artisan "$@"
+  '';
+
+  tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
+in {
+  options.services.monica = {
+    enable = mkEnableOption (lib.mdDoc "monica");
+
+    user = mkOption {
+      default = "monica";
+      description = lib.mdDoc "User monica runs as.";
+      type = types.str;
+    };
+
+    group = mkOption {
+      default = "monica";
+      description = lib.mdDoc "Group monica runs as.";
+      type = types.str;
+    };
+
+    appKeyFile = mkOption {
+      description = lib.mdDoc ''
+        A file containing the Laravel APP_KEY - a 32 character long,
+        base64 encoded key used for encryption where needed. Can be
+        generated with <code>head -c 32 /dev/urandom | base64</code>.
+      '';
+      example = "/run/keys/monica-appkey";
+      type = types.path;
+    };
+
+    hostname = lib.mkOption {
+      type = lib.types.str;
+      default =
+        if config.networking.domain != null
+        then config.networking.fqdn
+        else config.networking.hostName;
+      defaultText = lib.literalExpression "config.networking.fqdn";
+      example = "monica.example.com";
+      description = lib.mdDoc ''
+        The hostname to serve monica on.
+      '';
+    };
+
+    appURL = mkOption {
+      description = lib.mdDoc ''
+        The root URL that you want to host monica on. All URLs in monica will be generated using this value.
+        If you change this in the future you may need to run a command to update stored URLs in the database.
+        Command example: <code>php artisan monica:update-url https://old.example.com https://new.example.com</code>
+      '';
+      default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
+      defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
+      example = "https://example.com";
+      type = types.str;
+    };
+
+    dataDir = mkOption {
+      description = lib.mdDoc "monica data directory";
+      default = "/var/lib/monica";
+      type = types.path;
+    };
+
+    database = {
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "Database host address.";
+      };
+      port = mkOption {
+        type = types.port;
+        default = 3306;
+        description = lib.mdDoc "Database host port.";
+      };
+      name = mkOption {
+        type = types.str;
+        default = "monica";
+        description = lib.mdDoc "Database name.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = user;
+        defaultText = lib.literalExpression "user";
+        description = lib.mdDoc "Database username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/monica-dbpassword";
+        description = lib.mdDoc ''
+          A file containing the password corresponding to
+          <option>database.user</option>.
+        '';
+      };
+      createLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Create the database and database user locally.";
+      };
+    };
+
+    mail = {
+      driver = mkOption {
+        type = types.enum ["smtp" "sendmail"];
+        default = "smtp";
+        description = lib.mdDoc "Mail driver to use.";
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "Mail host address.";
+      };
+      port = mkOption {
+        type = types.port;
+        default = 1025;
+        description = lib.mdDoc "Mail host port.";
+      };
+      fromName = mkOption {
+        type = types.str;
+        default = "monica";
+        description = lib.mdDoc "Mail \"from\" name.";
+      };
+      from = mkOption {
+        type = types.str;
+        default = "mail@monica.com";
+        description = lib.mdDoc "Mail \"from\" email.";
+      };
+      user = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "monica";
+        description = lib.mdDoc "Mail username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/monica-mailpassword";
+        description = lib.mdDoc ''
+          A file containing the password corresponding to
+          <option>mail.user</option>.
+        '';
+      };
+      encryption = mkOption {
+        type = with types; nullOr (enum ["tls"]);
+        default = null;
+        description = lib.mdDoc "SMTP encryption mechanism to use.";
+      };
+    };
+
+    maxUploadSize = mkOption {
+      type = types.str;
+      default = "18M";
+      example = "1G";
+      description = lib.mdDoc "The maximum size for uploads (e.g. images).";
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [str int bool]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = lib.mdDoc ''
+        Options for the monica PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
+      '';
+    };
+
+    nginx = mkOption {
+      type = types.submodule (
+        recursiveUpdate
+        (import ../web-servers/nginx/vhost-options.nix {inherit config lib;}) {}
+      );
+      default = {};
+      example = ''
+        {
+          serverAliases = [
+            "monica.''${config.networking.domain}"
+          ];
+          # To enable encryption and let let's encrypt take care of certificate
+          forceSSL = true;
+          enableACME = true;
+        }
+      '';
+      description = lib.mdDoc ''
+        With this option, you can customize the nginx virtualHost settings.
+      '';
+    };
+
+    config = mkOption {
+      type = with types;
+        attrsOf
+        (nullOr
+          (either
+            (oneOf [
+              bool
+              int
+              port
+              path
+              str
+            ])
+            (submodule {
+              options = {
+                _secret = mkOption {
+                  type = nullOr str;
+                  description = lib.mdDoc ''
+                    The path to a file containing the value the
+                    option should be set to in the final
+                    configuration file.
+                  '';
+                };
+              };
+            })));
+      default = {};
+      example = ''
+        {
+          ALLOWED_IFRAME_HOSTS = "https://example.com";
+          WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf";
+          AUTH_METHOD = "oidc";
+          OIDC_NAME = "MyLogin";
+          OIDC_DISPLAY_NAME_CLAIMS = "name";
+          OIDC_CLIENT_ID = "monica";
+          OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
+          OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
+          OIDC_ISSUER_DISCOVER = true;
+        }
+      '';
+      description = lib.mdDoc ''
+        monica configuration options to set in the
+        <filename>.env</filename> file.
+
+        Refer to <link xlink:href="https://github.com/monicahq/monica"/>
+        for details on supported values.
+
+        Settings 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>.env</filename> file, the
+        <literal>OIDC_CLIENT_SECRET</literal> key will be set to the
+        contents of the <filename>/run/keys/oidc_secret</filename>
+        file.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = db.createLocally -> db.user == user;
+        message = "services.monica.database.user must be set to ${user} if services.monica.database.createLocally is set true.";
+      }
+      {
+        assertion = db.createLocally -> db.passwordFile == null;
+        message = "services.monica.database.passwordFile cannot be specified if services.monica.database.createLocally is set to true.";
+      }
+    ];
+
+    services.monica.config = {
+      APP_ENV = "production";
+      APP_KEY._secret = cfg.appKeyFile;
+      APP_URL = cfg.appURL;
+      DB_HOST = db.host;
+      DB_PORT = db.port;
+      DB_DATABASE = db.name;
+      DB_USERNAME = db.user;
+      MAIL_DRIVER = mail.driver;
+      MAIL_FROM_NAME = mail.fromName;
+      MAIL_FROM = mail.from;
+      MAIL_HOST = mail.host;
+      MAIL_PORT = mail.port;
+      MAIL_USERNAME = mail.user;
+      MAIL_ENCRYPTION = mail.encryption;
+      DB_PASSWORD._secret = db.passwordFile;
+      MAIL_PASSWORD._secret = mail.passwordFile;
+      APP_SERVICES_CACHE = "/run/monica/cache/services.php";
+      APP_PACKAGES_CACHE = "/run/monica/cache/packages.php";
+      APP_CONFIG_CACHE = "/run/monica/cache/config.php";
+      APP_ROUTES_CACHE = "/run/monica/cache/routes-v7.php";
+      APP_EVENTS_CACHE = "/run/monica/cache/events.php";
+      SESSION_SECURE_COOKIE = tlsEnabled;
+    };
+
+    environment.systemPackages = [artisan];
+
+    services.mysql = mkIf db.createLocally {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [db.name];
+      ensureUsers = [
+        {
+          name = db.user;
+          ensurePermissions = {"${db.name}.*" = "ALL PRIVILEGES";};
+        }
+      ];
+    };
+
+    services.phpfpm.pools.monica = {
+      inherit user group;
+      phpOptions = ''
+        log_errors = on
+        post_max_size = ${cfg.maxUploadSize}
+        upload_max_filesize = ${cfg.maxUploadSize}
+      '';
+      settings = {
+        "listen.mode" = "0660";
+        "listen.owner" = user;
+        "listen.group" = group;
+      } // cfg.poolConfig;
+    };
+
+    services.nginx = {
+      enable = mkDefault true;
+      recommendedTlsSettings = true;
+      recommendedOptimisation = true;
+      recommendedGzipSettings = true;
+      recommendedBrotliSettings = true;
+      recommendedProxySettings = true;
+      virtualHosts.${cfg.hostname} = mkMerge [
+        cfg.nginx
+        {
+          root = mkForce "${monica}/public";
+          locations = {
+            "/" = {
+              index = "index.php";
+              tryFiles = "$uri $uri/ /index.php?$query_string";
+            };
+            "~ \.php$".extraConfig = ''
+              fastcgi_pass unix:${config.services.phpfpm.pools."monica".socket};
+            '';
+            "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
+              extraConfig = "expires 365d;";
+            };
+          };
+        }
+      ];
+    };
+
+    systemd.services.monica-setup = {
+      description = "Preparation tasks for monica";
+      before = ["phpfpm-monica.service"];
+      after = optional db.createLocally "mysql.service";
+      wantedBy = ["multi-user.target"];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = user;
+        UMask = 077;
+        WorkingDirectory = "${monica}";
+        RuntimeDirectory = "monica/cache";
+        RuntimeDirectoryMode = 0700;
+      };
+      path = [pkgs.replace-secret];
+      script = let
+        isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+        monicaEnvVars = lib.generators.toKeyValue {
+          mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
+            mkValueString = v:
+              with builtins;
+                if isInt v
+                then toString v
+                else if isString v
+                then v
+                else if true == v
+                then "true"
+                else if false == v
+                then "false"
+                else if isSecret v
+                then hashString "sha256" v._secret
+                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+          };
+        };
+        secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
+        mkSecretReplacement = file: ''
+          replace-secret ${escapeShellArgs [(builtins.hashString "sha256" file) file "${cfg.dataDir}/.env"]}
+        '';
+        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+        filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{} null])) cfg.config;
+        monicaEnv = pkgs.writeText "monica.env" (monicaEnvVars filteredConfig);
+      in ''
+        # error handling
+        set -euo pipefail
+
+        # create .env file
+        install -T -m 0600 -o ${user} ${monicaEnv} "${cfg.dataDir}/.env"
+        ${secretReplacements}
+        if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
+          sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
+        fi
+
+        # migrate & seed db
+        ${pkgs.php}/bin/php artisan key:generate --force
+        ${pkgs.php}/bin/php artisan setup:production -v --force
+      '';
+    };
+
+    systemd.services.monica-scheduler = {
+      description = "Background tasks for monica";
+      startAt = "minutely";
+      after = ["monica-setup.service"];
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        WorkingDirectory = "${monica}";
+        ExecStart = "${pkgs.php}/bin/php ${monica}/artisan schedule:run -v";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.dataDir}                            0710 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public                     0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads             0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage                    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/app                0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/fonts              0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework          0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/cache    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/views    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/logs               0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/uploads            0700 ${user} ${group} - -"
+    ];
+
+    users = {
+      users = mkIf (user == "monica") {
+        monica = {
+          inherit group;
+          isSystemUser = true;
+        };
+        "${config.services.nginx.user}".extraGroups = [group];
+      };
+      groups = mkIf (group == "monica") {
+        monica = {};
+      };
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/moodle.nix b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
index 6d1a9839ca1f..b617e9a59379 100644
--- a/nixpkgs/nixos/modules/services/web-apps/moodle.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
@@ -56,13 +56,15 @@ let
   mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
-  phpExt = pkgs.php81.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 opcache ]);
+  phpExt = pkgs.php81.buildEnv {
+    extensions = { all, ... }: with all; [ iconv mbstring curl openssl tokenizer soap ctype zip gd simplexml dom intl sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter opcache exif sodium ];
+    extraConfig = "max_input_vars = 5000";
+  };
 in
 {
   # interface
   options.services.moodle = {
-    enable = mkEnableOption "Moodle web application";
+    enable = mkEnableOption (lib.mdDoc "Moodle web application");
 
     package = mkOption {
       type = types.package;
@@ -94,7 +96,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         description = lib.mdDoc "Database host port.";
         default = {
           mysql = 3306;
@@ -138,7 +140,7 @@ in
       createLocally = mkOption {
         type = types.bool;
         default = true;
-        description = "Create the database and database user locally.";
+        description = lib.mdDoc "Create the database and database user locally.";
       };
     };
 
@@ -230,6 +232,7 @@ in
       phpOptions = ''
         zend_extension = opcache.so
         opcache.enable = 1
+        max_input_vars = 5000
       '';
       settings = {
         "listen.owner" = config.services.httpd.user;
diff --git a/nixpkgs/nixos/modules/services/web-apps/netbox.nix b/nixpkgs/nixos/modules/services/web-apps/netbox.nix
index 2826e57f2c77..0ecb20e8c2c0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/netbox.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/netbox.nix
@@ -4,49 +4,24 @@ with lib;
 
 let
   cfg = config.services.netbox;
+  pythonFmt = pkgs.formats.pythonVars {};
   staticDir = cfg.dataDir + "/static";
-  configFile = pkgs.writeTextFile {
-    name = "configuration.py";
-    text = ''
-      STATIC_ROOT = '${staticDir}'
-      ALLOWED_HOSTS = ['*']
-      DATABASE = {
-        'NAME': 'netbox',
-        'USER': 'netbox',
-        'HOST': '/run/postgresql',
-      }
-
-      # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
-      # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
-      # to use two separate database IDs.
-      REDIS = {
-          'tasks': {
-              'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=0',
-              'SSL': False,
-          },
-          'caching': {
-              'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=1',
-              'SSL': False,
-          }
-      }
-
-      with open("${cfg.secretKeyFile}", "r") as file:
-          SECRET_KEY = file.readline()
-
-      ${optionalString cfg.enableLdap "REMOTE_AUTH_BACKEND = 'netbox.authentication.LDAPBackend'"}
-
-      ${cfg.extraConfig}
-    '';
+
+  settingsFile = pythonFmt.generate "netbox-settings.py" cfg.settings;
+  extraConfigFile = pkgs.writeTextFile {
+    name = "netbox-extraConfig.py";
+    text = cfg.extraConfig;
   };
-  pkg = (pkgs.netbox.overrideAttrs (old: {
+  configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
+
+  pkg = (cfg.package.overrideAttrs (old: {
     installPhase = old.installPhase + ''
       ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
     '' + optionalString cfg.enableLdap ''
-      ln -s ${ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
+      ln -s ${cfg.ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
     '';
   })).override {
-    plugins = ps: ((cfg.plugins ps)
-      ++ optional cfg.enableLdap [ ps.django-auth-ldap ]);
+    inherit (cfg) plugins;
   };
   netboxManageScript = with pkgs; (writeScriptBin "netbox-manage" ''
     #!${stdenv.shell}
@@ -67,6 +42,30 @@ in {
       '';
     };
 
+    settings = lib.mkOption {
+      description = lib.mdDoc ''
+        Configuration options to set in `configuration.py`.
+        See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
+      '';
+
+      default = { };
+
+      type = lib.types.submodule {
+        freeformType = pythonFmt.type;
+
+        options = {
+          ALLOWED_HOSTS = lib.mkOption {
+            type = with lib.types; listOf str;
+            default = ["*"];
+            description = lib.mdDoc ''
+              A list of valid fully-qualified domain names (FQDNs) and/or IP
+              addresses that can be used to reach the NetBox service.
+            '';
+          };
+        };
+      };
+    };
+
     listenAddress = mkOption {
       type = types.str;
       default = "[::1]";
@@ -75,6 +74,17 @@ in {
       '';
     };
 
+    package = mkOption {
+      type = types.package;
+      default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+      defaultText = literalExpression ''
+        if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+      '';
+      description = lib.mdDoc ''
+        NetBox package to use.
+      '';
+    };
+
     port = mkOption {
       type = types.port;
       default = 8001;
@@ -114,7 +124,7 @@ in {
       default = "";
       description = lib.mdDoc ''
         Additional lines of configuration appended to the `configuration.py`.
-        See the [documentation](https://netbox.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
+        See the [documentation](https://docs.netbox.dev/en/stable/configuration/) for more possible options.
       '';
     };
 
@@ -132,13 +142,94 @@ in {
       type = types.path;
       default = "";
       description = lib.mdDoc ''
-        Path to the Configuration-File for LDAP-Authentification, will be loaded as `ldap_config.py`.
+        Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
         See the [documentation](https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration) for possible options.
       '';
+      example = ''
+        import ldap
+        from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+        AUTH_LDAP_SERVER_URI = "ldaps://ldap.example.com/"
+
+        AUTH_LDAP_USER_SEARCH = LDAPSearch(
+            "ou=accounts,ou=posix,dc=example,dc=com",
+            ldap.SCOPE_SUBTREE,
+            "(uid=%(user)s)",
+        )
+
+        AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+            "ou=groups,ou=posix,dc=example,dc=com",
+            ldap.SCOPE_SUBTREE,
+            "(objectClass=posixGroup)",
+        )
+        AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+
+        # Mirror LDAP group assignments.
+        AUTH_LDAP_MIRROR_GROUPS = True
+
+        # For more granular permissions, we can map LDAP groups to Django groups.
+        AUTH_LDAP_FIND_GROUP_PERMS = True
+      '';
     };
   };
 
   config = mkIf cfg.enable {
+    services.netbox = {
+      plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+      settings = {
+        STATIC_ROOT = staticDir;
+        MEDIA_ROOT = "${cfg.dataDir}/media";
+        REPORTS_ROOT = "${cfg.dataDir}/reports";
+        SCRIPTS_ROOT = "${cfg.dataDir}/scripts";
+
+        DATABASE = {
+          NAME = "netbox";
+          USER = "netbox";
+          HOST = "/run/postgresql";
+        };
+
+        # Redis database settings. Redis is used for caching and for queuing
+        # background tasks such as webhook events. A separate configuration
+        # exists for each. Full connection details are required in both
+        # sections, and it is strongly recommended to use two separate database
+        # IDs.
+        REDIS = {
+            tasks = {
+                URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=0";
+                SSL = false;
+            };
+            caching =  {
+                URL = "unix://${config.services.redis.servers.netbox.unixSocket}?db=1";
+                SSL = false;
+            };
+        };
+
+        REMOTE_AUTH_BACKEND = lib.mkIf cfg.enableLdap "netbox.authentication.LDAPBackend";
+
+        LOGGING = lib.mkDefault {
+          version = 1;
+
+          formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
+
+          handlers.console = {
+            class = "logging.StreamHandler";
+            formatter = "precise";
+          };
+
+          # log to console/systemd instead of file
+          root = {
+            level = "INFO";
+            handlers = [ "console" ];
+          };
+        };
+      };
+
+      extraConfig = ''
+        with open("${cfg.secretKeyFile}", "r") as file:
+            SECRET_KEY = file.readline()
+      '';
+    };
+
     services.redis.servers.netbox.enable = true;
 
     services.postgresql = {
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud-notify_push.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud-notify_push.nix
new file mode 100644
index 000000000000..759daa0c50dc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud-notify_push.nix
@@ -0,0 +1,123 @@
+{ config, options, lib, pkgs, ... }:
+
+let
+  cfg = config.services.nextcloud.notify_push;
+  cfgN = config.services.nextcloud;
+in
+{
+  options.services.nextcloud.notify_push = {
+    enable = lib.mkEnableOption (lib.mdDoc "Notify push");
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.nextcloud-notify_push;
+      defaultText = lib.literalMD "pkgs.nextcloud-notify_push";
+      description = lib.mdDoc "Which package to use for notify_push";
+    };
+
+    socketPath = lib.mkOption {
+      type = lib.types.str;
+      default = "/run/nextcloud-notify_push/sock";
+      description = lib.mdDoc "Socket path to use for notify_push";
+    };
+
+    logLevel = lib.mkOption {
+      type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ];
+      default = "error";
+      description = lib.mdDoc "Log level";
+    };
+
+    bendDomainToLocalhost = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to add an entry to `/etc/hosts` for the configured nextcloud domain to point to `localhost` and add `localhost `to nextcloud's `trusted_proxies` config option.
+
+        This is useful when nextcloud's domain is not a static IP address and when the reverse proxy cannot be bypassed because the backend connection is done via unix socket.
+      '';
+    };
+  } // (
+    lib.genAttrs [
+      "dbtype"
+      "dbname"
+      "dbuser"
+      "dbpassFile"
+      "dbhost"
+      "dbport"
+      "dbtableprefix"
+    ] (
+      opt: options.services.nextcloud.config.${opt} // {
+        default = config.services.nextcloud.config.${opt};
+        defaultText = "config.services.nextcloud.config.${opt}";
+      }
+    )
+  );
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.nextcloud-notify_push = let
+      nextcloudUrl = "http${lib.optionalString cfgN.https "s"}://${cfgN.hostName}";
+    in {
+      description = "Push daemon for Nextcloud clients";
+      documentation = [ "https://github.com/nextcloud/notify_push" ];
+      after = [
+        "phpfpm-nextcloud.service"
+        "redis-nextcloud.service"
+      ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        NEXTCLOUD_URL = nextcloudUrl;
+        SOCKET_PATH = cfg.socketPath;
+        DATABASE_PREFIX = cfg.dbtableprefix;
+        LOG = cfg.logLevel;
+      };
+      postStart = ''
+        ${cfgN.occ}/bin/nextcloud-occ notify_push:setup ${nextcloudUrl}/push
+      '';
+      script = let
+        dbType = if cfg.dbtype == "pgsql" then "postgresql" else cfg.dbtype;
+        dbUser = lib.optionalString (cfg.dbuser != null) cfg.dbuser;
+        dbPass = lib.optionalString (cfg.dbpassFile != null) ":$DATABASE_PASSWORD";
+        isSocket = lib.hasPrefix "/" (toString cfg.dbhost);
+        dbHost = lib.optionalString (cfg.dbhost != null) (if
+          isSocket then
+            if dbType == "postgresql" then "?host=${cfg.dbhost}" else
+            if dbType == "mysql" then "?socket=${cfg.dbhost}" else throw "unsupported dbtype"
+          else
+            "@${cfg.dbhost}");
+        dbName = lib.optionalString (cfg.dbname != null) "/${cfg.dbname}";
+        dbUrl = "${dbType}://${dbUser}${dbPass}${lib.optionalString (!isSocket) dbHost}${dbName}${lib.optionalString isSocket dbHost}";
+      in lib.optionalString (dbPass != "") ''
+        export DATABASE_PASSWORD="$(<"${cfg.dbpassFile}")"
+      '' + ''
+        export DATABASE_URL="${dbUrl}"
+        ${cfg.package}/bin/notify_push '${cfgN.datadir}/config/config.php'
+      '';
+      serviceConfig = {
+        User = "nextcloud";
+        Group = "nextcloud";
+        RuntimeDirectory = [ "nextcloud-notify_push" ];
+        Restart = "on-failure";
+        RestartSec = "5s";
+      };
+    };
+
+    networking.hosts = lib.mkIf cfg.bendDomainToLocalhost {
+      "127.0.0.1" = [ cfgN.hostName ];
+      "::1" = [ cfgN.hostName ];
+    };
+
+    services = lib.mkMerge [
+      {
+        nginx.virtualHosts.${cfgN.hostName}.locations."^~ /push/" = {
+          proxyPass = "http://unix:${cfg.socketPath}";
+          proxyWebsockets = true;
+          recommendedProxySettings = true;
+        };
+      }
+
+      (lib.mkIf cfg.bendDomainToLocalhost {
+        nextcloud.extraOptions.trusted_proxies = [ "127.0.0.1" "::1" ];
+      })
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.md b/nixpkgs/nixos/modules/services/web-apps/nextcloud.md
new file mode 100644
index 000000000000..5be81a18dfec
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.md
@@ -0,0 +1,227 @@
+# Nextcloud {#module-services-nextcloud}
+
+[Nextcloud](https://nextcloud.com/) is an open-source,
+self-hostable cloud platform. The server setup can be automated using
+[services.nextcloud](#opt-services.nextcloud.enable). A
+desktop client is packaged at `pkgs.nextcloud-client`.
+
+The current default by NixOS is `nextcloud26` which is also the latest
+major version available.
+
+## Basic usage {#module-services-nextcloud-basic-usage}
+
+Nextcloud is a PHP-based application which requires an HTTP server
+([`services.nextcloud`](#opt-services.nextcloud.enable)
+and optionally supports
+[`services.nginx`](#opt-services.nginx.enable)).
+
+For the database, you can set
+[`services.nextcloud.config.dbtype`](#opt-services.nextcloud.config.dbtype) to
+either `sqlite` (the default), `mysql`, or `pgsql`. The simplest is `sqlite`,
+which will be automatically created and managed by the application. For the
+last two, you can easily create a local database by setting
+[`services.nextcloud.database.createLocally`](#opt-services.nextcloud.database.createLocally)
+to `true`, Nextcloud will automatically be configured to connect to it through
+socket.
+
+A very basic configuration may look like this:
+```
+{ pkgs, ... }:
+{
+  services.nextcloud = {
+    enable = true;
+    hostName = "nextcloud.tld";
+    database.createLocally = true;
+    config = {
+      dbtype = "pgsql";
+      adminpassFile = "/path/to/admin-pass-file";
+    };
+  };
+
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+}
+```
+
+The `hostName` option is used internally to configure an HTTP
+server using [`PHP-FPM`](https://php-fpm.org/)
+and `nginx`. The `config` attribute set is
+used by the imperative installer and all values are written to an additional file
+to ensure that changes can be applied by changing the module's options.
+
+In case the application serves multiple domains (those are checked with
+[`$_SERVER['HTTP_HOST']`](http://php.net/manual/en/reserved.variables.server.php))
+it's needed to add them to
+[`services.nextcloud.config.extraTrustedDomains`](#opt-services.nextcloud.config.extraTrustedDomains).
+
+Auto updates for Nextcloud apps can be enabled using
+[`services.nextcloud.autoUpdateApps`](#opt-services.nextcloud.autoUpdateApps.enable).
+
+## Common problems {#module-services-nextcloud-pitfalls-during-upgrade}
+
+  - **General notes.**
+    Unfortunately Nextcloud appears to be very stateful when it comes to
+    managing its own configuration. The config file lives in the home directory
+    of the `nextcloud` user (by default
+    `/var/lib/nextcloud/config/config.php`) and is also used to
+    track several states of the application (e.g., whether installed or not).
+
+     All configuration parameters are also stored in
+    {file}`/var/lib/nextcloud/config/override.config.php` which is generated by
+    the module and linked from the store to ensure that all values from
+    {file}`config.php` can be modified by the module.
+    However {file}`config.php` manages the application's state and shouldn't be
+    touched manually because of that.
+
+    ::: {.warning}
+    Don't delete {file}`config.php`! This file
+    tracks the application's state and a deletion can cause unwanted
+    side-effects!
+    :::
+
+    ::: {.warning}
+    Don't rerun `nextcloud-occ maintenance:install`!
+    This command tries to install the application
+    and can cause unwanted side-effects!
+    :::
+  - **Multiple version upgrades.**
+    Nextcloud doesn't allow to move more than one major-version forward. E.g., if you're on
+    `v16`, you cannot upgrade to `v18`, you need to upgrade to
+    `v17` first. This is ensured automatically as long as the
+    [stateVersion](#opt-system.stateVersion) is declared properly. In that case
+    the oldest version available (one major behind the one from the previous NixOS
+    release) will be selected by default and the module will generate a warning that reminds
+    the user to upgrade to latest Nextcloud *after* that deploy.
+  - **`Error: Command "upgrade" is not defined.`**
+    This error usually occurs if the initial installation
+    ({command}`nextcloud-occ maintenance:install`) has failed. After that, the application
+    is not installed, but the upgrade is attempted to be executed. Further context can
+    be found in [NixOS/nixpkgs#111175](https://github.com/NixOS/nixpkgs/issues/111175).
+
+    First of all, it makes sense to find out what went wrong by looking at the logs
+    of the installation via {command}`journalctl -u nextcloud-setup` and try to fix
+    the underlying issue.
+
+    - If this occurs on an *existing* setup, this is most likely because
+      the maintenance mode is active. It can be deactivated by running
+      {command}`nextcloud-occ maintenance:mode --off`. It's advisable though to
+      check the logs first on why the maintenance mode was activated.
+    - ::: {.warning}
+      Only perform the following measures on
+      *freshly installed instances!*
+      :::
+
+      A re-run of the installer can be forced by *deleting*
+      {file}`/var/lib/nextcloud/config/config.php`. This is the only time
+      advisable because the fresh install doesn't have any state that can be lost.
+      In case that doesn't help, an entire re-creation can be forced via
+      {command}`rm -rf ~nextcloud/`.
+
+  - **Server-side encryption.**
+    Nextcloud supports [server-side encryption (SSE)](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html).
+    This is not an end-to-end encryption, but can be used to encrypt files that will be persisted
+    to external storage such as S3. Please note that this won't work anymore when using OpenSSL 3
+    for PHP's openssl extension and **Nextcloud 25 or older** because this is implemented using the
+    legacy cipher RC4. For Nextcloud26 this isn't relevant anymore, because Nextcloud has an RC4 implementation
+    written in native PHP and thus doesn't need `ext-openssl` for that anymore.
+    If [](#opt-system.stateVersion) is *above* `22.05`,
+    this is disabled by default. To turn it on again and for further information please refer to
+    [](#opt-services.nextcloud.enableBrokenCiphersForSSE).
+
+## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-nextcloud-httpd}
+
+By default, `nginx` is used as reverse-proxy for `nextcloud`.
+However, it's possible to use e.g. `httpd` by explicitly disabling
+`nginx` using [](#opt-services.nginx.enable) and fixing the
+settings `listen.owner` &amp; `listen.group` in the
+[corresponding `phpfpm` pool](#opt-services.phpfpm.pools).
+
+An exemplary configuration may look like this:
+```
+{ config, lib, pkgs, ... }: {
+  services.nginx.enable = false;
+  services.nextcloud = {
+    enable = true;
+    hostName = "localhost";
+
+    /* further, required options */
+  };
+  services.phpfpm.pools.nextcloud.settings = {
+    "listen.owner" = config.services.httpd.user;
+    "listen.group" = config.services.httpd.group;
+  };
+  services.httpd = {
+    enable = true;
+    adminAddr = "webmaster@localhost";
+    extraModules = [ "proxy_fcgi" ];
+    virtualHosts."localhost" = {
+      documentRoot = config.services.nextcloud.package;
+      extraConfig = ''
+        <Directory "${config.services.nextcloud.package}">
+          <FilesMatch "\.php$">
+            <If "-f %{REQUEST_FILENAME}">
+              SetHandler "proxy:unix:${config.services.phpfpm.pools.nextcloud.socket}|fcgi://localhost/"
+            </If>
+          </FilesMatch>
+          <IfModule mod_rewrite.c>
+            RewriteEngine On
+            RewriteBase /
+            RewriteRule ^index\.php$ - [L]
+            RewriteCond %{REQUEST_FILENAME} !-f
+            RewriteCond %{REQUEST_FILENAME} !-d
+            RewriteRule . /index.php [L]
+          </IfModule>
+          DirectoryIndex index.php
+          Require all granted
+          Options +FollowSymLinks
+        </Directory>
+      '';
+    };
+  };
+}
+```
+
+## Installing Apps and PHP extensions {#installing-apps-php-extensions-nextcloud}
+
+Nextcloud apps are installed statefully through the web interface.
+Some apps may require extra PHP extensions to be installed.
+This can be configured with the [](#opt-services.nextcloud.phpExtraExtensions) setting.
+
+Alternatively, extra apps can also be declared with the [](#opt-services.nextcloud.extraApps) setting.
+When using this setting, apps can no longer be managed statefully because this can lead to Nextcloud updating apps
+that are managed by Nix. If you want automatic updates it is recommended that you use web interface to install apps.
+
+## Maintainer information {#module-services-nextcloud-maintainer-info}
+
+As stated in the previous paragraph, we must provide a clean upgrade-path for Nextcloud
+since it cannot move more than one major version forward on a single upgrade. This chapter
+adds some notes how Nextcloud updates should be rolled out in the future.
+
+While minor and patch-level updates are no problem and can be done directly in the
+package-expression (and should be backported to supported stable branches after that),
+major-releases should be added in a new attribute (e.g. Nextcloud `v19.0.0`
+should be available in `nixpkgs` as `pkgs.nextcloud19`).
+To provide simple upgrade paths it's generally useful to backport those as well to stable
+branches. As long as the package-default isn't altered, this won't break existing setups.
+After that, the versioning-warning in the `nextcloud`-module should be
+updated to make sure that the
+[package](#opt-services.nextcloud.package)-option selects the latest version
+on fresh setups.
+
+If major-releases will be abandoned by upstream, we should check first if those are needed
+in NixOS for a safe upgrade-path before removing those. In that case we should keep those
+packages, but mark them as insecure in an expression like this (in
+`<nixpkgs/pkgs/servers/nextcloud/default.nix>`):
+```
+/* ... */
+{
+  nextcloud17 = generic {
+    version = "17.0.x";
+    sha256 = "0000000000000000000000000000000000000000000000000000";
+    eol = true;
+  };
+}
+```
+
+Ideally we should make sure that it's possible to jump two NixOS versions forward:
+i.e. the warnings and the logic in the module should guard a user to upgrade from a
+Nextcloud on e.g. 19.09 to a Nextcloud on 20.09.
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
index feee7494a71a..a8142cf42d75 100644
--- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
@@ -13,7 +13,12 @@ let
   phpPackage = cfg.phpPackage.buildEnv {
     extensions = { enabled, all }:
       (with all;
-        enabled
+        # disable default openssl extension
+        (lib.filter (e: e.pname != "php-openssl") enabled)
+        # use OpenSSL 1.1 for RC4 Nextcloud encryption if user
+        # has acknowledged the brokenness of the ciphers (RC4).
+        # TODO: remove when https://github.com/nextcloud/server/issues/32003 is fixed.
+        ++ (if cfg.enableBrokenCiphersForSSE then [ cfg.phpPackage.extensions.openssl-legacy ] else [ cfg.phpPackage.extensions.openssl ])
         ++ optional cfg.enableImagemagick imagick
         # Optionally enabled depending on caching settings
         ++ optional cfg.caching.apcu apcu
@@ -52,6 +57,9 @@ let
 
   inherit (config.system) stateVersion;
 
+  mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
+
 in {
 
   imports = [
@@ -71,15 +79,49 @@ in {
         * setting `listen.owner` & `listen.group` in the phpfpm-pool to a different value
 
       Further details about this can be found in the `Nextcloud`-section of the NixOS-manual
-      (which can be openend e.g. by running `nixos-help`).
+      (which can be opened e.g. by running `nixos-help`).
     '')
     (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] ''
-      Use services.nextcloud.nginx.enableImagemagick instead.
+      Use services.nextcloud.enableImagemagick instead.
     '')
   ];
 
   options.services.nextcloud = {
-    enable = mkEnableOption "nextcloud";
+    enable = mkEnableOption (lib.mdDoc "nextcloud");
+
+    enableBrokenCiphersForSSE = mkOption {
+      type = types.bool;
+      default = versionOlder stateVersion "22.11";
+      defaultText = literalExpression "versionOlder system.stateVersion \"22.11\"";
+      description = lib.mdDoc ''
+        This option enables using the OpenSSL PHP extension linked against OpenSSL 1.1
+        rather than latest OpenSSL (≥ 3), this is not recommended unless you need
+        it for server-side encryption (SSE). SSE uses the legacy RC4 cipher which is
+        considered broken for several years now. See also [RFC7465](https://datatracker.ietf.org/doc/html/rfc7465).
+
+        This cipher has been disabled in OpenSSL ≥ 3 and requires
+        a specific legacy profile to re-enable it.
+
+        If you deploy Nextcloud using OpenSSL ≥ 3 for PHP and have
+        server-side encryption configured, you will not be able to access
+        your files anymore. Enabling this option can restore access to your files.
+        Upon testing we didn't encounter any data corruption when turning
+        this on and off again, but this cannot be guaranteed for
+        each Nextcloud installation.
+
+        It is `true` by default for systems with a [](#opt-system.stateVersion) below
+        `22.11` to make sure that existing installations won't break on update. On newer
+        NixOS systems you have to explicitly enable it on your own.
+
+        Please note that this only provides additional value when using
+        external storage such as S3 since it's not an end-to-end encryption.
+        If this is not the case,
+        it is advised to [disable server-side encryption](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption) and set this to `false`.
+
+        In the future, Nextcloud may move to AES-256-GCM, by then,
+        this option will be removed.
+      '';
+    };
     hostName = mkOption {
       type = types.str;
       description = lib.mdDoc "FQDN for the nextcloud instance.";
@@ -148,6 +190,15 @@ in {
       default = 2;
       description = lib.mdDoc "Log level value between 0 (DEBUG) and 4 (FATAL).";
     };
+    logType = mkOption {
+      type = types.enum [ "errorlog" "file" "syslog" "systemd" ];
+      default = "syslog";
+      description = lib.mdDoc ''
+        Logging backend to use.
+        systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions.
+        See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details.
+      '';
+    };
     https = mkOption {
       type = types.bool;
       default = false;
@@ -156,7 +207,7 @@ in {
     package = mkOption {
       type = types.package;
       description = lib.mdDoc "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud23" "nextcloud24" ];
+      relatedPackages = [ "nextcloud25" "nextcloud26" ];
     };
     phpPackage = mkOption {
       type = types.package;
@@ -254,17 +305,21 @@ in {
       '';
     };
 
+    fastcgiTimeout = mkOption {
+      type = types.int;
+      default = 120;
+      description = lib.mdDoc ''
+        FastCGI timeout for database connection in seconds.
+      '';
+    };
+
     database = {
 
       createLocally = mkOption {
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Create the database and database user locally. Only available for
-          mysql database.
-          Note that this option will use the latest version of MariaDB which
-          is not officially supported by Nextcloud. As for now a workaround
-          is used to also support MariaDB version >= 10.6.
+          Create the database and database user locally.
         '';
       };
 
@@ -296,12 +351,15 @@ in {
       };
       dbhost = mkOption {
         type = types.nullOr types.str;
-        default = "localhost";
+        default =
+          if pgsqlLocal then "/run/postgresql"
+          else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock"
+          else "localhost";
+        defaultText = "localhost";
         description = lib.mdDoc ''
-          Database host.
-
-          Note: for using Unix authentication with PostgreSQL, this should be
-          set to `/run/postgresql`.
+          Database host or socket path. Defaults to the correct unix socket
+          instead if `services.nextcloud.database.createLocally` is true and
+          `services.nextcloud.config.dbtype` is either `pgsql` or `mysql`.
         '';
       };
       dbport = mkOption {
@@ -332,7 +390,7 @@ in {
         default = [];
         description = lib.mdDoc ''
           Trusted domains, from which the nextcloud installation will be
-          acessible.  You don't need to add
+          accessible.  You don't need to add
           `services.nextcloud.hostname` here.
         '';
       };
@@ -363,31 +421,31 @@ in {
         default = null;
         type = types.nullOr types.str;
         example = "DE";
-        description = ''
-          <warning>
-           <para>This option exists since Nextcloud 21! If older versions are used,
-            this will throw an eval-error!</para>
-          </warning>
+        description = lib.mdDoc ''
+          ::: {.warning}
+          This option exists since Nextcloud 21! If older versions are used,
+          this will throw an eval-error!
+          :::
 
-          <link xlink:href="https://www.iso.org/iso-3166-country-codes.html">ISO 3611-1</link>
+          [ISO 3611-1](https://www.iso.org/iso-3166-country-codes.html)
           country codes for automatic phone-number detection without a country code.
 
-          With e.g. <literal>DE</literal> set, the <literal>+49</literal> can be omitted for
+          With e.g. `DE` set, the `+49` can be omitted for
           phone-numbers.
         '';
       };
 
       objectstore = {
         s3 = {
-          enable = mkEnableOption ''
+          enable = mkEnableOption (lib.mdDoc ''
             S3 object storage as primary storage.
 
             This mounts a bucket on an Amazon S3 object storage or compatible
             implementation into the virtual filesystem.
 
             Further details about this feature can be found in the
-            <link xlink:href="https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html">upstream documentation</link>.
-          '';
+            [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html).
+          '');
           bucket = mkOption {
             type = types.str;
             example = "nextcloud";
@@ -458,20 +516,54 @@ in {
               `http://hostname.domain/bucket` instead.
             '';
           };
+          sseCKeyFile = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/nextcloud-objectstore-s3-sse-c-key";
+            description = lib.mdDoc ''
+              If provided this is the full path to a file that contains the key
+              to enable [server-side encryption with customer-provided keys][1]
+              (SSE-C).
+
+              The file must contain a random 32-byte key encoded as a base64
+              string, e.g. generated with the command
+
+              ```
+              openssl rand 32 | base64
+              ```
+
+              Must be readable by user `nextcloud`.
+
+              [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
+            '';
+          };
         };
       };
     };
 
-    enableImagemagick = mkEnableOption ''
+    enableImagemagick = mkEnableOption (lib.mdDoc ''
         the ImageMagick module for PHP.
         This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
         You may want to disable it for increased security. In that case, previews will still be available
         for some images (e.g. JPEG and PNG).
-        See <link xlink:href="https://github.com/nextcloud/server/issues/13099"/>.
-    '' // {
+        See <https://github.com/nextcloud/server/issues/13099>.
+    '') // {
       default = true;
     };
 
+    configureRedis = lib.mkOption {
+      type = lib.types.bool;
+      default = config.services.nextcloud.notify_push.enable;
+      defaultText = literalExpression "config.services.nextcloud.notify_push.enable";
+      description = lib.mdDoc ''
+        Whether to configure nextcloud to use the recommended redis settings for small instances.
+
+        ::: {.note}
+        The `notify_push` app requires redis to be configured. If this option is turned off, this must be configured manually.
+        :::
+      '';
+    };
+
     caching = {
       apcu = mkOption {
         type = types.bool;
@@ -511,39 +603,37 @@ in {
         type = with types; either str (listOf str);
         default = "05:00:00";
         example = "Sun 14:00:00";
-        description = ''
-          When to run the update. See `systemd.services.&lt;name&gt;.startAt`.
+        description = lib.mdDoc ''
+          When to run the update. See `systemd.services.<name>.startAt`.
         '';
       };
     };
     occ = mkOption {
       type = types.package;
       default = occ;
-      defaultText = literalDocBook "generated script";
+      defaultText = literalMD "generated script";
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         The nextcloud-occ program preconfigured to target this Nextcloud instance.
       '';
     };
-    globalProfiles = mkEnableOption "global profiles" // {
-      description = ''
-        Makes user-profiles globally available under <literal>nextcloud.tld/u/user.name</literal>.
+    globalProfiles = mkEnableOption (lib.mdDoc "global profiles") // {
+      description = lib.mdDoc ''
+        Makes user-profiles globally available under `nextcloud.tld/u/user.name`.
         Even though it's enabled by default in Nextcloud, it must be explicitly enabled
         here because it has the side-effect that personal information is even accessible to
         unauthenticated users by default.
 
-        By default, the following properties are set to <quote>Show to everyone</quote>
+        By default, the following properties are set to “Show to everyone”
         if this flag is enabled:
-        <itemizedlist>
-        <listitem><para>About</para></listitem>
-        <listitem><para>Full name</para></listitem>
-        <listitem><para>Headline</para></listitem>
-        <listitem><para>Organisation</para></listitem>
-        <listitem><para>Profile picture</para></listitem>
-        <listitem><para>Role</para></listitem>
-        <listitem><para>Twitter</para></listitem>
-        <listitem><para>Website</para></listitem>
-        </itemizedlist>
+        - About
+        - Full name
+        - Headline
+        - Organisation
+        - Profile picture
+        - Role
+        - Twitter
+        - Website
 
         Only has an effect in Nextcloud 23 and later.
       '';
@@ -569,10 +659,10 @@ in {
     secretFile = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Secret options which will be appended to nextcloud's config.php file (written as JSON, in the same
-        form as the <xref linkend="opt-services.nextcloud.extraOptions"/> option), for example
-        <programlisting>{"redis":{"password":"secret"}}</programlisting>.
+        form as the [](#opt-services.nextcloud.extraOptions) option), for example
+        `{"redis":{"password":"secret"}}`.
       '';
     };
 
@@ -598,7 +688,7 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     { warnings = let
-        latest = 24;
+        latest = 26;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -613,39 +703,35 @@ in {
             `services.nextcloud.package`.
           '';
 
-        # FIXME(@Ma27) remove as soon as nextcloud properly supports
-        # mariadb >=10.6.
-        isUnsupportedMariadb =
-          # 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
-          && config.services.mysql.enable
-          # We're using MariaDB
-          && (getName config.services.mysql.package) == "mariadb-server"
-          # MariaDB is at least 10.6 and thus not supported
-          && (versionAtLeast (getVersion config.services.mysql.package) "10.6");
-
       in (optional (cfg.poolConfig != null) ''
           Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
           Please migrate your configuration to config.services.nextcloud.poolSettings.
         '')
-        ++ (optional (versionOlder cfg.package.version "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 (versionOlder cfg.package.version "24") (upgradeWarning 23 "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
+        ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
+        ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
+        ++ (optional cfg.enableBrokenCiphersForSSE ''
+          You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud.
+          This is only necessary if you're using Nextcloud's server-side encryption.
+          Please keep in mind that it's using the broken RC4 cipher.
 
-            * Switch to `pkgs.mysql`
-            * Downgrade MariaDB to at least 10.5
-            * Work around Nextcloud's problems by specifying `innodb_read_only_compressed=0`
+          If you don't use that feature, you can switch to OpenSSL 3 and get
+          rid of this warning by declaring
 
-            For further context, please read
-            https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/15
-          '');
+            services.nextcloud.enableBrokenCiphersForSSE = false;
+
+          If you need to use server-side encryption you can ignore this warning.
+          Otherwise you'd have to disable server-side encryption first in order
+          to be able to safely disable this option and get rid of this warning.
+          See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this.
+
+          For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470
+        '')
+        ++ (optional (cfg.enableBrokenCiphersForSSE && versionAtLeast cfg.package.version "26") ''
+          Nextcloud26 supports RC4 without requiring legacy OpenSSL, so
+          `services.nextcloud.enableBrokenCiphersForSSE` can be set to `false`.
+        '');
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -655,24 +741,32 @@ in {
               nextcloud defined in an overlay, please set `services.nextcloud.package` to
               `pkgs.nextcloud`.
             ''
-          else if versionOlder stateVersion "22.05" then nextcloud22
-          else nextcloud24
+          else if versionOlder stateVersion "22.11" then nextcloud24
+          else if versionOlder stateVersion "23.05" then nextcloud25
+          else nextcloud26
         );
 
       services.nextcloud.phpPackage =
-        if versionOlder cfg.package.version "24" then pkgs.php80
-        # FIXME: Use PHP 8.1 with Nextcloud 24 and higher, once issues like this one are fixed:
-        #
-        # https://github.com/nextcloud/twofactor_totp/issues/1192
-        #
-        # else if versionOlder cfg.package.version "24" then pkgs.php80
-        # else pkgs.php81;
-        else pkgs.php80;
+        if versionOlder cfg.package.version "26" then pkgs.php81
+        else pkgs.php82;
     }
 
     { assertions = [
-      { assertion = cfg.database.createLocally -> cfg.config.dbtype == "mysql";
-        message = ''services.nextcloud.config.dbtype must be set to mysql if services.nextcloud.database.createLocally is set to true.'';
+      { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null;
+        message = ''
+          Using `services.nextcloud.database.createLocally` with database
+          password authentication is no longer supported.
+
+          If you use an external database (or want to use password auth for any
+          other reason), set `services.nextcloud.database.createLocally` to
+          `false`. The database won't be managed for you (use `services.mysql`
+          if you want to set it up).
+
+          If you want this module to manage your nextcloud database for you,
+          unset `services.nextcloud.config.dbpassFile` and
+          `services.nextcloud.config.dbhost` to use socket authentication
+          instead of password.
+        '';
       }
     ]; }
 
@@ -694,7 +788,7 @@ in {
 
         nextcloud-setup = let
           c = cfg.config;
-          writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
+          writePhpArray = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
           requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable;
           objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable ''
             'objectstore' => [
@@ -709,6 +803,7 @@ in {
                 'use_ssl' => ${boolToString s3.useSsl},
                 ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
                 'use_path_style' => ${boolToString s3.usePathStyle},
+                ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
               ],
             ]
           '';
@@ -759,7 +854,7 @@ in {
               'datadirectory' => '${datadir}/data',
               'skeletondirectory' => '${cfg.skeletonDirectory}',
               ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
-              'log_type' => 'syslog',
+              'log_type' => '${cfg.logType}',
               'loglevel' => '${builtins.toString cfg.logLevel}',
               ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"}
               ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
@@ -774,8 +869,8 @@ in {
                 ''
               }
               'dbtype' => '${c.dbtype}',
-              'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
-              'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
+              'trusted_domains' => ${writePhpArray ([ cfg.hostName ] ++ c.extraTrustedDomains)},
+              'trusted_proxies' => ${writePhpArray (c.trustedProxies)},
               ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"}
               ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"}
               ${objectstoreConfig}
@@ -815,9 +910,9 @@ in {
               ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
               ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
               ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
-              "--database-pass" = "\$${dbpass.arg}";
+              "--database-pass" = "\"\$${dbpass.arg}\"";
               "--admin-user" = ''"${c.adminuser}"'';
-              "--admin-pass" = "\$${adminpass.arg}";
+              "--admin-pass" = "\"\$${adminpass.arg}\"";
               "--data-dir" = ''"${datadir}/data"'';
             });
           in ''
@@ -835,6 +930,8 @@ in {
         in {
           wantedBy = [ "multi-user.target" ];
           before = [ "phpfpm-nextcloud.service" ];
+          after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+          requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
           path = [ occ ];
           script = ''
             ${optionalString (c.dbpassFile != null) ''
@@ -894,6 +991,9 @@ in {
           '';
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
+          # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
+          # an automatic creation of the database user.
+          environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
         };
         nextcloud-cron = {
           after = [ "nextcloud-setup.service" ];
@@ -937,7 +1037,7 @@ in {
 
       environment.systemPackages = [ occ ];
 
-      services.mysql = lib.mkIf cfg.database.createLocally {
+      services.mysql = lib.mkIf mysqlLocal {
         enable = true;
         package = lib.mkDefault pkgs.mariadb;
         ensureDatabases = [ cfg.config.dbname ];
@@ -945,22 +1045,34 @@ in {
           name = cfg.config.dbuser;
           ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
         }];
-        # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6,
-        # this is a workaround.
-        # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22
-        settings = mkIf (versionOlder cfg.package.version "24") {
-          mysqld = {
-            innodb_read_only_compressed = 0;
+      };
+
+      services.postgresql = mkIf pgsqlLocal {
+        enable = true;
+        ensureDatabases = [ cfg.config.dbname ];
+        ensureUsers = [{
+          name = cfg.config.dbuser;
+          ensurePermissions = { "DATABASE ${cfg.config.dbname}" = "ALL PRIVILEGES"; };
+        }];
+      };
+
+      services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
+        enable = true;
+        user = "nextcloud";
+      };
+
+      services.nextcloud = lib.mkIf cfg.configureRedis {
+        caching.redis = true;
+        extraOptions = {
+          memcache = {
+            distributed = ''\OC\Memcache\Redis'';
+            locking = ''\OC\Memcache\Redis'';
+          };
+          redis = {
+            host = config.services.redis.servers.nextcloud.unixSocket;
+            port = 0;
           };
         };
-        initialScript = pkgs.writeText "mysql-init" ''
-          CREATE USER '${cfg.config.dbname}'@'localhost' IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
-          CREATE DATABASE IF NOT EXISTS ${cfg.config.dbname};
-          GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
-            CREATE TEMPORARY TABLES ON ${cfg.config.dbname}.* TO '${cfg.config.dbuser}'@'localhost'
-            IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
-          FLUSH privileges;
-        '';
       };
 
       services.nginx.enable = mkDefault true;
@@ -1032,7 +1144,7 @@ in {
               fastcgi_pass unix:${fpm.socket};
               fastcgi_intercept_errors on;
               fastcgi_request_buffering off;
-              fastcgi_read_timeout 120s;
+              fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
             '';
           };
           "~ \\.(?:css|js|woff2?|svg|gif|map)$".extraConfig = ''
@@ -1054,7 +1166,7 @@ in {
           ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
             add_header X-Content-Type-Options nosniff;
             add_header X-XSS-Protection "1; mode=block";
-            add_header X-Robots-Tag none;
+            add_header X-Robots-Tag "noindex, nofollow";
             add_header X-Download-Options noopen;
             add_header X-Permitted-Cross-Domain-Policies none;
             add_header X-Frame-Options sameorigin;
@@ -1082,5 +1194,5 @@ in {
     }
   ]);
 
-  meta.doc = ./nextcloud.xml;
+  meta.doc = ./nextcloud.md;
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
deleted file mode 100644
index b46f34420a70..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
+++ /dev/null
@@ -1,291 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-nextcloud">
- <title>Nextcloud</title>
- <para>
-  <link xlink:href="https://nextcloud.com/">Nextcloud</link> is an open-source,
-  self-hostable cloud platform. The server setup can be automated using
-  <link linkend="opt-services.nextcloud.enable">services.nextcloud</link>. A
-  desktop client is packaged at <literal>pkgs.nextcloud-client</literal>.
- </para>
- <para>
-  The current default by NixOS is <package>nextcloud24</package> which is also the latest
-  major version available.
- </para>
- <section xml:id="module-services-nextcloud-basic-usage">
-  <title>Basic usage</title>
-
-  <para>
-   Nextcloud is a PHP-based application which requires an HTTP server
-   (<literal><link linkend="opt-services.nextcloud.enable">services.nextcloud</link></literal>
-   optionally supports
-   <literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>)
-   and a database (it's recommended to use
-   <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>).
-  </para>
-
-  <para>
-   A very basic configuration may look like this:
-<programlisting>{ pkgs, ... }:
-{
-  services.nextcloud = {
-    <link linkend="opt-services.nextcloud.enable">enable</link> = true;
-    <link linkend="opt-services.nextcloud.hostName">hostName</link> = "nextcloud.tld";
-    config = {
-      <link linkend="opt-services.nextcloud.config.dbtype">dbtype</link> = "pgsql";
-      <link linkend="opt-services.nextcloud.config.dbuser">dbuser</link> = "nextcloud";
-      <link linkend="opt-services.nextcloud.config.dbhost">dbhost</link> = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
-      <link linkend="opt-services.nextcloud.config.dbname">dbname</link> = "nextcloud";
-      <link linkend="opt-services.nextcloud.config.adminpassFile">adminpassFile</link> = "/path/to/admin-pass-file";
-      <link linkend="opt-services.nextcloud.config.adminuser">adminuser</link> = "root";
-    };
-  };
-
-  services.postgresql = {
-    <link linkend="opt-services.postgresql.enable">enable</link> = true;
-    <link linkend="opt-services.postgresql.ensureDatabases">ensureDatabases</link> = [ "nextcloud" ];
-    <link linkend="opt-services.postgresql.ensureUsers">ensureUsers</link> = [
-     { name = "nextcloud";
-       ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
-     }
-    ];
-  };
-
-  # ensure that postgres is running *before* running the setup
-  systemd.services."nextcloud-setup" = {
-    requires = ["postgresql.service"];
-    after = ["postgresql.service"];
-  };
-
-  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
-}</programlisting>
-  </para>
-
-  <para>
-   The <literal>hostName</literal> option is used internally to configure an HTTP
-   server using <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal>
-   and <literal>nginx</literal>. The <literal>config</literal> attribute set is
-   used by the imperative installer and all values are written to an additional file
-   to ensure that changes can be applied by changing the module's options.
-  </para>
-
-  <para>
-   In case the application serves multiple domains (those are checked with
-   <literal><link xlink:href="http://php.net/manual/en/reserved.variables.server.php">$_SERVER['HTTP_HOST']</link></literal>)
-   it's needed to add them to
-   <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>.
-  </para>
-
-  <para>
-   Auto updates for Nextcloud apps can be enabled using
-   <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>.
-</para>
-
- </section>
-
- <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
-  <title>Common problems</title>
-  <itemizedlist>
-   <listitem>
-    <formalpara>
-     <title>General notes</title>
-     <para>
-      Unfortunately Nextcloud appears to be very stateful when it comes to
-      managing its own configuration. The config file lives in the home directory
-      of the <literal>nextcloud</literal> user (by default
-      <literal>/var/lib/nextcloud/config/config.php</literal>) and is also used to
-      track several states of the application (e.g., whether installed or not).
-     </para>
-    </formalpara>
-    <para>
-     All configuration parameters are also stored in
-     <filename>/var/lib/nextcloud/config/override.config.php</filename> which is generated by
-     the module and linked from the store to ensure that all values from
-     <filename>config.php</filename> can be modified by the module.
-     However <filename>config.php</filename> manages the application's state and shouldn't be
-     touched manually because of that.
-    </para>
-    <warning>
-     <para>Don't delete <filename>config.php</filename>! This file
-     tracks the application's state and a deletion can cause unwanted
-     side-effects!</para>
-    </warning>
-
-    <warning>
-     <para>Don't rerun <literal>nextcloud-occ
-     maintenance:install</literal>! This command tries to install the application
-     and can cause unwanted side-effects!</para>
-    </warning>
-   </listitem>
-   <listitem>
-    <formalpara>
-     <title>Multiple version upgrades</title>
-     <para>
-      Nextcloud doesn't allow to move more than one major-version forward. E.g., if you're on
-      <literal>v16</literal>, you cannot upgrade to <literal>v18</literal>, you need to upgrade to
-      <literal>v17</literal> first. This is ensured automatically as long as the
-      <link linkend="opt-system.stateVersion">stateVersion</link> is declared properly. In that case
-      the oldest version available (one major behind the one from the previous NixOS
-      release) will be selected by default and the module will generate a warning that reminds
-      the user to upgrade to latest Nextcloud <emphasis>after</emphasis> that deploy.
-     </para>
-    </formalpara>
-   </listitem>
-   <listitem>
-    <formalpara>
-     <title><literal>Error: Command "upgrade" is not defined.</literal></title>
-     <para>
-      This error usually occurs if the initial installation
-      (<command>nextcloud-occ maintenance:install</command>) has failed. After that, the application
-      is not installed, but the upgrade is attempted to be executed. Further context can
-      be found in <link xlink:href="https://github.com/NixOS/nixpkgs/issues/111175">NixOS/nixpkgs#111175</link>.
-     </para>
-    </formalpara>
-    <para>
-     First of all, it makes sense to find out what went wrong by looking at the logs
-     of the installation via <command>journalctl -u nextcloud-setup</command> and try to fix
-     the underlying issue.
-    </para>
-    <itemizedlist>
-     <listitem>
-      <para>
-       If this occurs on an <emphasis>existing</emphasis> setup, this is most likely because
-       the maintenance mode is active. It can be deactivated by running
-       <command>nextcloud-occ maintenance:mode --off</command>. It's advisable though to
-       check the logs first on why the maintenance mode was activated.
-      </para>
-     </listitem>
-     <listitem>
-      <warning><para>Only perform the following measures on
-      <emphasis>freshly installed instances!</emphasis></para></warning>
-      <para>
-       A re-run of the installer can be forced by <emphasis>deleting</emphasis>
-       <filename>/var/lib/nextcloud/config/config.php</filename>. This is the only time
-       advisable because the fresh install doesn't have any state that can be lost.
-       In case that doesn't help, an entire re-creation can be forced via
-       <command>rm -rf ~nextcloud/</command>.
-      </para>
-     </listitem>
-    </itemizedlist>
-   </listitem>
-  </itemizedlist>
- </section>
-
- <section xml:id="module-services-nextcloud-httpd">
-  <title>Using an alternative webserver as reverse-proxy (e.g. <literal>httpd</literal>)</title>
-  <para>
-   By default, <package>nginx</package> is used as reverse-proxy for <package>nextcloud</package>.
-   However, it's possible to use e.g. <package>httpd</package> by explicitly disabling
-   <package>nginx</package> using <xref linkend="opt-services.nginx.enable" /> and fixing the
-   settings <literal>listen.owner</literal> &amp; <literal>listen.group</literal> in the
-   <link linkend="opt-services.phpfpm.pools">corresponding <literal>phpfpm</literal> pool</link>.
-  </para>
-  <para>
-   An exemplary configuration may look like this:
-<programlisting>{ config, lib, pkgs, ... }: {
-  <link linkend="opt-services.nginx.enable">services.nginx.enable</link> = false;
-  services.nextcloud = {
-    <link linkend="opt-services.nextcloud.enable">enable</link> = true;
-    <link linkend="opt-services.nextcloud.hostName">hostName</link> = "localhost";
-
-    /* further, required options */
-  };
-  <link linkend="opt-services.phpfpm.pools._name_.settings">services.phpfpm.pools.nextcloud.settings</link> = {
-    "listen.owner" = config.services.httpd.user;
-    "listen.group" = config.services.httpd.group;
-  };
-  services.httpd = {
-    <link linkend="opt-services.httpd.enable">enable</link> = true;
-    <link linkend="opt-services.httpd.adminAddr">adminAddr</link> = "webmaster@localhost";
-    <link linkend="opt-services.httpd.extraModules">extraModules</link> = [ "proxy_fcgi" ];
-    virtualHosts."localhost" = {
-      <link linkend="opt-services.httpd.virtualHosts._name_.documentRoot">documentRoot</link> = config.services.nextcloud.package;
-      <link linkend="opt-services.httpd.virtualHosts._name_.extraConfig">extraConfig</link> = ''
-        &lt;Directory "${config.services.nextcloud.package}"&gt;
-          &lt;FilesMatch "\.php$"&gt;
-            &lt;If "-f %{REQUEST_FILENAME}"&gt;
-              SetHandler "proxy:unix:${config.services.phpfpm.pools.nextcloud.socket}|fcgi://localhost/"
-            &lt;/If&gt;
-          &lt;/FilesMatch&gt;
-          &lt;IfModule mod_rewrite.c&gt;
-            RewriteEngine On
-            RewriteBase /
-            RewriteRule ^index\.php$ - [L]
-            RewriteCond %{REQUEST_FILENAME} !-f
-            RewriteCond %{REQUEST_FILENAME} !-d
-            RewriteRule . /index.php [L]
-          &lt;/IfModule&gt;
-          DirectoryIndex index.php
-          Require all granted
-          Options +FollowSymLinks
-        &lt;/Directory&gt;
-      '';
-    };
-  };
-}</programlisting>
-  </para>
- </section>
-
- <section xml:id="installing-apps-php-extensions-nextcloud">
-  <title>Installing Apps and PHP extensions</title>
-
-  <para>
-   Nextcloud apps are installed statefully through the web interface.
-
-   Some apps may require extra PHP extensions to be installed.
-   This can be configured with the <xref linkend="opt-services.nextcloud.phpExtraExtensions" /> setting.
-  </para>
-
-  <para>
-   Alternatively, extra apps can also be declared with the <xref linkend="opt-services.nextcloud.extraApps" /> setting.
-   When using this setting, apps can no longer be managed statefully because this can lead to Nextcloud updating apps
-   that are managed by Nix. If you want automatic updates it is recommended that you use web interface to install apps.
-  </para>
- </section>
-
- <section xml:id="module-services-nextcloud-maintainer-info">
-  <title>Maintainer information</title>
-
-  <para>
-   As stated in the previous paragraph, we must provide a clean upgrade-path for Nextcloud
-   since it cannot move more than one major version forward on a single upgrade. This chapter
-   adds some notes how Nextcloud updates should be rolled out in the future.
-  </para>
-
-  <para>
-   While minor and patch-level updates are no problem and can be done directly in the
-   package-expression (and should be backported to supported stable branches after that),
-   major-releases should be added in a new attribute (e.g. Nextcloud <literal>v19.0.0</literal>
-   should be available in <literal>nixpkgs</literal> as <literal>pkgs.nextcloud19</literal>).
-   To provide simple upgrade paths it's generally useful to backport those as well to stable
-   branches. As long as the package-default isn't altered, this won't break existing setups.
-   After that, the versioning-warning in the <literal>nextcloud</literal>-module should be
-   updated to make sure that the
-   <link linkend="opt-services.nextcloud.package">package</link>-option selects the latest version
-   on fresh setups.
-  </para>
-
-  <para>
-   If major-releases will be abandoned by upstream, we should check first if those are needed
-   in NixOS for a safe upgrade-path before removing those. In that case we shold keep those
-   packages, but mark them as insecure in an expression like this (in
-   <literal>&lt;nixpkgs/pkgs/servers/nextcloud/default.nix&gt;</literal>):
-<programlisting>/* ... */
-{
-  nextcloud17 = generic {
-    version = "17.0.x";
-    sha256 = "0000000000000000000000000000000000000000000000000000";
-    eol = true;
-  };
-}</programlisting>
-  </para>
-
-  <para>
-   Ideally we should make sure that it's possible to jump two NixOS versions forward:
-   i.e. the warnings and the logic in the module should guard a user to upgrade from a
-   Nextcloud on e.g. 19.09 to a Nextcloud on 20.09.
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/nexus.nix b/nixpkgs/nixos/modules/services/web-apps/nexus.nix
index cfa137e77d25..1f4a758b87ea 100644
--- a/nixpkgs/nixos/modules/services/web-apps/nexus.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/nexus.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.nexus = {
-      enable = mkEnableOption "Sonatype Nexus3 OSS service";
+      enable = mkEnableOption (lib.mdDoc "Sonatype Nexus3 OSS service");
 
       package = mkOption {
         type = types.package;
@@ -93,7 +93,7 @@ in
           '''
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Options for the JVM written to `nexus.jvmopts`.
           Please refer to the docs (https://help.sonatype.com/repomanager3/installation/configuring-the-runtime-environment)
           for further information.
diff --git a/nixpkgs/nixos/modules/services/web-apps/nifi.nix b/nixpkgs/nixos/modules/services/web-apps/nifi.nix
index e3f30c710e0c..f643e24d81d9 100644
--- a/nixpkgs/nixos/modules/services/web-apps/nifi.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/nifi.nix
@@ -27,7 +27,7 @@ let
 in {
   options = {
     services.nifi = {
-      enable = lib.mkEnableOption "Apache NiFi";
+      enable = lib.mkEnableOption (lib.mdDoc "Apache NiFi");
 
       package = lib.mkOption {
         type = lib.types.package;
diff --git a/nixpkgs/nixos/modules/services/web-apps/node-red.nix b/nixpkgs/nixos/modules/services/web-apps/node-red.nix
index e5b0998d3c41..f4d4ad9681a6 100644
--- a/nixpkgs/nixos/modules/services/web-apps/node-red.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/node-red.nix
@@ -17,7 +17,7 @@ let
 in
 {
   options.services.node-red = {
-    enable = mkEnableOption "the Node-RED service";
+    enable = mkEnableOption (lib.mdDoc "the Node-RED service");
 
     package = mkOption {
       default = pkgs.nodePackages.node-red;
diff --git a/nixpkgs/nixos/modules/services/web-apps/onlyoffice.nix b/nixpkgs/nixos/modules/services/web-apps/onlyoffice.nix
index 15fc3b03a834..3494f2fa21f0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/onlyoffice.nix
@@ -7,9 +7,9 @@ let
 in
 {
   options.services.onlyoffice = {
-    enable = mkEnableOption "OnlyOffice DocumentServer";
+    enable = mkEnableOption (lib.mdDoc "OnlyOffice DocumentServer");
 
-    enableExampleServer = mkEnableOption "OnlyOffice example server";
+    enableExampleServer = mkEnableOption (lib.mdDoc "OnlyOffice example server");
 
     hostname = mkOption {
       type = types.str;
@@ -29,7 +29,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.onlyoffice-documentserver;
-      defaultText = "pkgs.onlyoffice-documentserver";
+      defaultText = lib.literalExpression "pkgs.onlyoffice-documentserver";
       description = lib.mdDoc "Which package to use for the OnlyOffice instance.";
     };
 
@@ -54,7 +54,7 @@ in
     postgresName = mkOption {
       type = types.str;
       default = "onlyoffice";
-      description = lib.mdDoc "The name of databse OnlyOffice should user.";
+      description = lib.mdDoc "The name of database OnlyOffice should user.";
     };
 
     postgresPasswordFile = mkOption {
@@ -229,6 +229,9 @@ in
             cp -r ${cfg.package}/etc/onlyoffice/documentserver/* /run/onlyoffice/config/
             chmod u+w /run/onlyoffice/config/default.json
 
+            # Allow members of the onlyoffice group to serve files under /var/lib/onlyoffice/documentserver/App_Data
+            chmod g+x /var/lib/onlyoffice/documentserver
+
             cp /run/onlyoffice/config/default.json{,.orig}
 
             # for a mapping of environment variables from the docker container to json options see
@@ -252,7 +255,10 @@ in
               .rabbitmq.url = "${cfg.rabbitmqUrl}"
               ' /run/onlyoffice/config/default.json | sponge /run/onlyoffice/config/default.json
 
-            if ! psql -d onlyoffice -c "SELECT 'task_result'::regclass;" >/dev/null; then
+            if psql -d onlyoffice -c "SELECT 'task_result'::regclass;" >/dev/null; then
+              psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/removetbl.sql
+              psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
+            else
               psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
             fi
           '';
@@ -264,7 +270,7 @@ in
           wantedBy = [ "multi-user.target" ];
           serviceConfig = {
             ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config";
-            ExecStartPre = onlyoffice-prestart;
+            ExecStartPre = [ onlyoffice-prestart ];
             Group = "onlyoffice";
             Restart = "always";
             RuntimeDirectory = "onlyoffice";
@@ -281,6 +287,8 @@ in
         group = "onlyoffice";
         isSystemUser = true;
       };
+
+      nginx.extraGroups = [ "onlyoffice" ];
     };
 
     users.groups.onlyoffice = { };
diff --git a/nixpkgs/nixos/modules/services/web-apps/openvscode-server.nix b/nixpkgs/nixos/modules/services/web-apps/openvscode-server.nix
new file mode 100644
index 000000000000..d0db614d8d72
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/openvscode-server.nix
@@ -0,0 +1,211 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.openvscode-server;
+  defaultUser = "openvscode-server";
+  defaultGroup = defaultUser;
+in {
+  options = {
+    services.openvscode-server = {
+      enable = lib.mkEnableOption (lib.mdDoc "openvscode-server");
+
+      package = lib.mkPackageOptionMD pkgs "openvscode-server" { };
+
+      extraPackages = lib.mkOption {
+        default = [ ];
+        description = lib.mdDoc ''
+          Additional packages to add to the openvscode-server {env}`PATH`.
+        '';
+        example = lib.literalExpression "[ pkgs.go ]";
+        type = lib.types.listOf lib.types.package;
+      };
+
+      extraEnvironment = lib.mkOption {
+        type = lib.types.attrsOf lib.types.str;
+        description = lib.mdDoc ''
+          Additional environment variables to pass to openvscode-server.
+        '';
+        default = { };
+        example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
+      };
+
+      extraArguments = lib.mkOption {
+        default = [ ];
+        description = lib.mdDoc ''
+          Additional arguments to pass to openvscode-server.
+        '';
+        example = lib.literalExpression ''[ "--log=info" ]'';
+        type = lib.types.listOf lib.types.str;
+      };
+
+      host = lib.mkOption {
+        default = "localhost";
+        description = lib.mdDoc ''
+          The host name or IP address the server should listen to.
+        '';
+        type = lib.types.str;
+      };
+
+      port = lib.mkOption {
+        default = 3000;
+        description = lib.mdDoc ''
+          The port the server should listen to. If 0 is passed a random free port is picked. If a range in the format num-num is passed, a free port from the range (end inclusive) is selected.
+        '';
+        type = lib.types.port;
+      };
+
+      user = lib.mkOption {
+        default = defaultUser;
+        example = "yourUser";
+        description = lib.mdDoc ''
+          The user to run openvscode-server as.
+          By default, a user named `${defaultUser}` will be created.
+        '';
+        type = lib.types.str;
+      };
+
+      group = lib.mkOption {
+        default = defaultGroup;
+        example = "yourGroup";
+        description = lib.mdDoc ''
+          The group to run openvscode-server under.
+          By default, a group named `${defaultGroup}` will be created.
+        '';
+        type = lib.types.str;
+      };
+
+      extraGroups = lib.mkOption {
+        default = [ ];
+        description = lib.mdDoc ''
+          An array of additional groups for the `${defaultUser}` user.
+        '';
+        example = [ "docker" ];
+        type = lib.types.listOf lib.types.str;
+      };
+
+      withoutConnectionToken = lib.mkOption {
+        default = false;
+        description = lib.mdDoc ''
+          Run without a connection token. Only use this if the connection is secured by other means.
+        '';
+        example = true;
+        type = lib.types.bool;
+      };
+
+      socketPath = lib.mkOption {
+        default = null;
+        example = "/run/openvscode/socket";
+        description = lib.mdDoc ''
+          The path to a socket file for the server to listen to.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      userDataDir = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+          Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      serverDataDir = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+          Specifies the directory that server data is kept in.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      extensionsDir = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+          Set the root path for extensions.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      telemetryLevel = lib.mkOption {
+        default = "off";
+        example = "crash";
+        description = lib.mdDoc ''
+          Sets the initial telemetry level. Valid levels are: 'off', 'crash', 'error' and 'all'.
+        '';
+        type = lib.types.str;
+      };
+
+      connectionToken = lib.mkOption {
+        default = null;
+        example = "secret-token";
+        description = lib.mdDoc ''
+          A secret that must be included with all requests.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+      connectionTokenFile = lib.mkOption {
+        default = null;
+        description = lib.mdDoc ''
+          Path to a file that contains the connection token.
+        '';
+        type = lib.types.nullOr lib.types.str;
+      };
+
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.openvscode-server = {
+      description = "OpenVSCode server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      path = cfg.extraPackages;
+      environment = cfg.extraEnvironment;
+      serviceConfig = {
+        ExecStart = ''
+          ${lib.getExe cfg.package} \
+            --accept-server-license-terms \
+            --host=${cfg.host} \
+            --port=${toString cfg.port} \
+          '' + lib.optionalString (cfg.telemetryLevel == true) ''
+            --telemetry-level=${cfg.telemetryLevel} \
+          '' + lib.optionalString (cfg.withoutConnectionToken == true) ''
+            --without-connection-token \
+          '' + lib.optionalString (cfg.socketPath != null) ''
+            --socket-path=${cfg.socketPath} \
+          '' + lib.optionalString (cfg.userDataDir != null) ''
+            --user-data-dir=${cfg.userDataDir} \
+          '' + lib.optionalString (cfg.serverDataDir != null) ''
+            --server-data-dir=${cfg.serverDataDir} \
+          '' + lib.optionalString (cfg.extensionsDir != null) ''
+            --extensions-dir=${cfg.extensionsDir} \
+          '' + lib.optionalString (cfg.connectionToken != null) ''
+            --connection-token=${cfg.connectionToken} \
+          '' + lib.optionalString (cfg.connectionTokenFile != null) ''
+            --connection-token-file=${cfg.connectionTokenFile} \
+          '' + lib.escapeShellArgs 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}" = lib.mkMerge [
+      (lib.mkIf (cfg.user == defaultUser) {
+        isNormalUser = true;
+        description = "openvscode-server user";
+        inherit (cfg) group;
+      })
+      {
+        packages = cfg.extraPackages;
+        inherit (cfg) extraGroups;
+      }
+    ];
+
+    users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { };
+  };
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix b/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix
index c409adbc7106..72c5d6c7818c 100644
--- a/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix
@@ -4,7 +4,7 @@ let
 in
 {
   options.services.openwebrx = with lib; {
-    enable = mkEnableOption "OpenWebRX Web interface for Software-Defined Radios on http://localhost:8073";
+    enable = mkEnableOption (lib.mdDoc "OpenWebRX Web interface for Software-Defined Radios on http://localhost:8073");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/web-apps/outline.nix b/nixpkgs/nixos/modules/services/web-apps/outline.nix
new file mode 100644
index 000000000000..6f63198a68a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/outline.nix
@@ -0,0 +1,781 @@
+{ config, lib, pkgs, ...}:
+
+let
+  defaultUser = "outline";
+  cfg = config.services.outline;
+in
+{
+  # See here for a reference of all the options:
+  #   https://github.com/outline/outline/blob/v0.67.0/.env.sample
+  #   https://github.com/outline/outline/blob/v0.67.0/app.json
+  #   https://github.com/outline/outline/blob/v0.67.0/server/env.ts
+  #   https://github.com/outline/outline/blob/v0.67.0/shared/types.ts
+  # The order is kept the same here to make updating easier.
+  options.services.outline = {
+    enable = lib.mkEnableOption (lib.mdDoc "outline");
+
+    package = lib.mkOption {
+      default = pkgs.outline;
+      defaultText = lib.literalExpression "pkgs.outline";
+      type = lib.types.package;
+      example = lib.literalExpression ''
+        pkgs.outline.overrideAttrs (super: {
+          # Ignore the domain part in emails that come from OIDC. This is might
+          # be helpful if you want multiple users with different email providers
+          # to still land in the same team. Note that this effectively makes
+          # Outline a single-team instance.
+          patchPhase = ${"''"}
+            sed -i 's/const domain = parts\.length && parts\[1\];/const domain = "example.com";/g' server/routes/auth/providers/oidc.ts
+          ${"''"};
+        })
+      '';
+      description = lib.mdDoc "Outline package to use.";
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = defaultUser;
+      description = lib.mdDoc ''
+        User under which the service should run. If this is the default value,
+        the user will be created, with the specified group as the primary
+        group.
+      '';
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = defaultUser;
+      description = lib.mdDoc ''
+        Group under which the service should run. If this is the default value,
+        the group will be created.
+      '';
+    };
+
+    sequelizeArguments = lib.mkOption {
+      type = lib.types.str;
+      default = "";
+      example = "--env=production-ssl-disabled";
+      description = lib.mdDoc ''
+        Optional arguments to pass to `sequelize` calls.
+      '';
+    };
+
+    #
+    # Required options
+    #
+
+    secretKeyFile = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/outline/secret_key";
+      description = lib.mdDoc ''
+        File path that contains the application secret key. It must be 32
+        bytes long and hex-encoded. If the file does not exist, a new key will
+        be generated and saved here.
+      '';
+    };
+
+    utilsSecretFile = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/outline/utils_secret";
+      description = lib.mdDoc ''
+        File path that contains the utility secret key. If the file does not
+        exist, a new key will be generated and saved here.
+      '';
+    };
+
+    databaseUrl = lib.mkOption {
+      type = lib.types.str;
+      default = "local";
+      description = lib.mdDoc ''
+        URI to use for the main PostgreSQL database. If this needs to include
+        credentials that shouldn't be world-readable in the Nix store, set an
+        environment file on the systemd service and override the
+        `DATABASE_URL` entry. Pass the string
+        `local` to setup a database on the local server.
+      '';
+    };
+
+    redisUrl = lib.mkOption {
+      type = lib.types.str;
+      default = "local";
+      description = lib.mdDoc ''
+        Connection to a redis server. If this needs to include credentials
+        that shouldn't be world-readable in the Nix store, set an environment
+        file on the systemd service and override the
+        `REDIS_URL` entry. Pass the string
+        `local` to setup a local Redis database.
+      '';
+    };
+
+    publicUrl = lib.mkOption {
+      type = lib.types.str;
+      default = "http://localhost:3000";
+      description = lib.mdDoc "The fully qualified, publicly accessible URL";
+    };
+
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 3000;
+      description = lib.mdDoc "Listening port.";
+    };
+
+    storage = lib.mkOption {
+      description = lib.mdDoc ''
+        To support uploading of images for avatars and document attachments an
+        s3-compatible storage must be provided. AWS S3 is recommended for
+        redundancy however if you want to keep all file storage local an
+        alternative such as [minio](https://github.com/minio/minio)
+        can be used.
+
+        A more detailed guide on setting up S3 is available
+        [here](https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f).
+      '';
+      example = lib.literalExpression ''
+        {
+          accessKey = "...";
+          secretKeyFile = "/somewhere";
+          uploadBucketUrl = "https://minio.example.com";
+          uploadBucketName = "outline";
+          region = "us-east-1";
+        }
+      '';
+      type = lib.types.submodule {
+        options = {
+          accessKey = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "S3 access key.";
+          };
+          secretKeyFile = lib.mkOption {
+            type = lib.types.path;
+            description = lib.mdDoc "File path that contains the S3 secret key.";
+          };
+          region = lib.mkOption {
+            type = lib.types.str;
+            default = "xx-xxxx-x";
+            description = lib.mdDoc "AWS S3 region name.";
+          };
+          uploadBucketUrl = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              URL endpoint of an S3-compatible API where uploads should be
+              stored.
+            '';
+          };
+          uploadBucketName = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Name of the bucket where uploads should be stored.";
+          };
+          uploadMaxSize = lib.mkOption {
+            type = lib.types.int;
+            default = 26214400;
+            description = lib.mdDoc "Maxmium file size for uploads.";
+          };
+          forcePathStyle = lib.mkOption {
+            type = lib.types.bool;
+            default = true;
+            description = lib.mdDoc "Force S3 path style.";
+          };
+          acl = lib.mkOption {
+            type = lib.types.str;
+            default = "private";
+            description = lib.mdDoc "ACL setting.";
+          };
+        };
+      };
+    };
+
+    #
+    # Authentication
+    #
+
+    slackAuthentication = lib.mkOption {
+      description = lib.mdDoc ''
+        To configure Slack auth, you'll need to create an Application at
+        https://api.slack.com/apps
+
+        When configuring the Client ID, add a redirect URL under "OAuth & Permissions"
+        to `https://[publicUrl]/auth/slack.callback`.
+      '';
+      default = null;
+      type = lib.types.nullOr (lib.types.submodule {
+        options = {
+          clientId = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Authentication key.";
+          };
+          secretFile = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "File path containing the authentication secret.";
+          };
+        };
+      });
+    };
+
+    googleAuthentication = lib.mkOption {
+      description = lib.mdDoc ''
+        To configure Google auth, you'll need to create an OAuth Client ID at
+        https://console.cloud.google.com/apis/credentials
+
+        When configuring the Client ID, add an Authorized redirect URI to
+        `https://[publicUrl]/auth/google.callback`.
+      '';
+      default = null;
+      type = lib.types.nullOr (lib.types.submodule {
+        options = {
+          clientId = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Authentication client identifier.";
+          };
+          clientSecretFile = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "File path containing the authentication secret.";
+          };
+        };
+      });
+    };
+
+    azureAuthentication = lib.mkOption {
+      description = lib.mdDoc ''
+        To configure Microsoft/Azure auth, you'll need to create an OAuth
+        Client. See
+        [the guide](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4)
+        for details on setting up your Azure App.
+      '';
+      default = null;
+      type = lib.types.nullOr (lib.types.submodule {
+        options = {
+          clientId = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Authentication client identifier.";
+          };
+          clientSecretFile = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "File path containing the authentication secret.";
+          };
+          resourceAppId = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Authentication application resource ID.";
+          };
+        };
+      });
+    };
+
+    oidcAuthentication = lib.mkOption {
+      description = lib.mdDoc ''
+        To configure generic OIDC auth, you'll need some kind of identity
+        provider. See the documentation for whichever IdP you use to fill out
+        all the fields. The redirect URL is
+        `https://[publicUrl]/auth/oidc.callback`.
+      '';
+      default = null;
+      type = lib.types.nullOr (lib.types.submodule {
+        options = {
+          clientId = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Authentication client identifier.";
+          };
+          clientSecretFile = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "File path containing the authentication secret.";
+          };
+          authUrl = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "OIDC authentication URL endpoint.";
+          };
+          tokenUrl = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "OIDC token URL endpoint.";
+          };
+          userinfoUrl = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "OIDC userinfo URL endpoint.";
+          };
+          usernameClaim = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              Specify which claims to derive user information from. Supports any
+              valid JSON path with the JWT payload
+            '';
+            default = "preferred_username";
+          };
+          displayName = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Display name for OIDC authentication.";
+            default = "OpenID";
+          };
+          scopes = lib.mkOption {
+            type = lib.types.listOf lib.types.str;
+            description = lib.mdDoc "OpenID authentication scopes.";
+            default = [ "openid" "profile" "email" ];
+          };
+        };
+      });
+    };
+
+    #
+    # Optional configuration
+    #
+
+    sslKeyFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+        File path that contains the Base64-encoded private key for HTTPS
+        termination. This is only required if you do not use an external reverse
+        proxy. See
+        [the documentation](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4).
+      '';
+    };
+    sslCertFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+        File path that contains the Base64-encoded certificate for HTTPS
+        termination. This is only required if you do not use an external reverse
+        proxy. See
+        [the documentation](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4).
+      '';
+    };
+
+    cdnUrl = lib.mkOption {
+      type = lib.types.str;
+      default = "";
+      description = lib.mdDoc ''
+        If using a Cloudfront/Cloudflare distribution or similar it can be set
+        using this option. This will cause paths to JavaScript files,
+        stylesheets and images to be updated to the hostname defined here. In
+        your CDN configuration the origin server should be set to public URL.
+      '';
+    };
+
+    forceHttps = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Auto-redirect to HTTPS in production. The default is
+        `true` but you may set this to `false`
+        if you can be sure that SSL is terminated at an external loadbalancer.
+      '';
+    };
+
+    enableUpdateCheck = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Have the installation check for updates by sending anonymized statistics
+        to the maintainers.
+      '';
+    };
+
+    concurrency = lib.mkOption {
+      type = lib.types.int;
+      default = 1;
+      description = lib.mdDoc ''
+        How many processes should be spawned. For a rough estimate, divide your
+        server's available memory by 512.
+      '';
+    };
+
+    maximumImportSize = lib.mkOption {
+      type = lib.types.int;
+      default = 5120000;
+      description = lib.mdDoc ''
+        The maximum size of document imports. Overriding this could be required
+        if you have especially large Word documents with embedded imagery.
+      '';
+    };
+
+    debugOutput = lib.mkOption {
+      type = lib.types.nullOr (lib.types.enum [ "http" ]);
+      default = null;
+      description = lib.mdDoc "Set this to `http` log HTTP requests.";
+    };
+
+    slackIntegration = lib.mkOption {
+      description = lib.mdDoc ''
+        For a complete Slack integration with search and posting to channels
+        this configuration is also needed. See here for details:
+        https://wiki.generaloutline.com/share/be25efd1-b3ef-4450-b8e5-c4a4fc11e02a
+      '';
+      default = null;
+      type = lib.types.nullOr (lib.types.submodule {
+        options = {
+          verificationTokenFile = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "File path containing the verification token.";
+          };
+          appId = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Application ID.";
+          };
+          messageActions = lib.mkOption {
+            type = lib.types.bool;
+            default = true;
+            description = lib.mdDoc "Whether to enable message actions.";
+          };
+        };
+      });
+    };
+
+    googleAnalyticsId = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Optionally enable Google Analytics to track page views in the knowledge
+        base.
+      '';
+    };
+
+    sentryDsn = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Optionally enable [Sentry](https://sentry.io/) to
+        track errors and performance.
+      '';
+    };
+
+    sentryTunnel = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Optionally add a
+        [Sentry proxy tunnel](https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option)
+        for bypassing ad blockers in the UI.
+      '';
+    };
+
+    logo = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Custom logo displayed on the authentication screen. This will be scaled
+        to a height of 60px.
+      '';
+    };
+
+    smtp = lib.mkOption {
+      description = lib.mdDoc ''
+        To support sending outgoing transactional emails such as
+        "document updated" or "you've been invited" you'll need to provide
+        authentication for an SMTP server.
+      '';
+      default = null;
+      type = lib.types.nullOr (lib.types.submodule {
+        options = {
+          host = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Host name or IP address of the SMTP server.";
+          };
+          port = lib.mkOption {
+            type = lib.types.port;
+            description = lib.mdDoc "TCP port of the SMTP server.";
+          };
+          username = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Username to authenticate with.";
+          };
+          passwordFile = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              File path containing the password to authenticate with.
+            '';
+          };
+          fromEmail = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Sender email in outgoing mail.";
+          };
+          replyEmail = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Reply address in outgoing mail.";
+          };
+          tlsCiphers = lib.mkOption {
+            type = lib.types.str;
+            default = "";
+            description = lib.mdDoc "Override SMTP cipher configuration.";
+          };
+          secure = lib.mkOption {
+            type = lib.types.bool;
+            default = true;
+            description = lib.mdDoc "Use a secure SMTP connection.";
+          };
+        };
+      });
+    };
+
+    defaultLanguage = lib.mkOption {
+      type = lib.types.enum [
+         "da_DK"
+         "de_DE"
+         "en_US"
+         "es_ES"
+         "fa_IR"
+         "fr_FR"
+         "it_IT"
+         "ja_JP"
+         "ko_KR"
+         "nl_NL"
+         "pl_PL"
+         "pt_BR"
+         "pt_PT"
+         "ru_RU"
+         "sv_SE"
+         "th_TH"
+         "vi_VN"
+         "zh_CN"
+         "zh_TW"
+      ];
+      default = "en_US";
+      description = lib.mdDoc ''
+        The default interface language. See
+        [translate.getoutline.com](https://translate.getoutline.com/)
+        for a list of available language codes and their rough percentage
+        translated.
+      '';
+    };
+
+    rateLimiter.enable = lib.mkEnableOption (lib.mdDoc "rate limiter for the application web server");
+    rateLimiter.requests = lib.mkOption {
+      type = lib.types.int;
+      default = 5000;
+      description = lib.mdDoc "Maximum number of requests in a throttling window.";
+    };
+    rateLimiter.durationWindow = lib.mkOption {
+      type = lib.types.int;
+      default = 60;
+      description = lib.mdDoc "Length of a throttling window.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    users.users = lib.optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} = {
+        isSystemUser = true;
+        group = cfg.group;
+      };
+    };
+
+    users.groups = lib.optionalAttrs (cfg.group == defaultUser) {
+      ${defaultUser} = { };
+    };
+
+    systemd.tmpfiles.rules = [
+      "f ${cfg.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
+      "f ${cfg.utilsSecretFile} 0600 ${cfg.user} ${cfg.group} -"
+      "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
+    ];
+
+    services.postgresql = lib.mkIf (cfg.databaseUrl == "local") {
+      enable = true;
+      ensureUsers = [{
+        name = "outline";
+        ensurePermissions."DATABASE outline" = "ALL PRIVILEGES";
+      }];
+      ensureDatabases = [ "outline" ];
+    };
+
+    services.redis.servers.outline = lib.mkIf (cfg.redisUrl == "local") {
+      enable = true;
+      user = config.services.outline.user;
+      port = 0; # Disable the TCP listener
+    };
+
+    systemd.services.outline = let
+      localRedisUrl = "redis+unix:///run/redis-outline/redis.sock";
+      localPostgresqlUrl = "postgres://localhost/outline?host=/run/postgresql";
+
+      # Create an outline-sequalize wrapper (a wrapper around the wrapper) that
+      # has the config file's path baked in. This is necessary because there is
+      # at least two occurrences of outline calling this from its own code.
+      sequelize = pkgs.writeShellScriptBin "outline-sequelize" ''
+        exec ${cfg.package}/bin/outline-sequelize \
+          --config $RUNTIME_DIRECTORY/database.json \
+          ${cfg.sequelizeArguments} \
+          "$@"
+      '';
+    in {
+      description = "Outline wiki and knowledge base";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ]
+        ++ lib.optional (cfg.databaseUrl == "local") "postgresql.service"
+        ++ lib.optional (cfg.redisUrl == "local") "redis-outline.service";
+      requires = lib.optional (cfg.databaseUrl == "local") "postgresql.service"
+        ++ lib.optional (cfg.redisUrl == "local") "redis-outline.service";
+      path = [
+        pkgs.openssl # Required by the preStart script
+        sequelize
+      ];
+
+
+      environment = lib.mkMerge [
+        {
+          NODE_ENV = "production";
+
+          REDIS_URL = if cfg.redisUrl == "local" then localRedisUrl else cfg.redisUrl;
+          URL = cfg.publicUrl;
+          PORT = builtins.toString cfg.port;
+
+          AWS_ACCESS_KEY_ID = cfg.storage.accessKey;
+          AWS_REGION = cfg.storage.region;
+          AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl;
+          AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName;
+          AWS_S3_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize;
+          AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle;
+          AWS_S3_ACL = cfg.storage.acl;
+
+          CDN_URL = cfg.cdnUrl;
+          FORCE_HTTPS = builtins.toString cfg.forceHttps;
+          ENABLE_UPDATES = builtins.toString cfg.enableUpdateCheck;
+          WEB_CONCURRENCY = builtins.toString cfg.concurrency;
+          MAXIMUM_IMPORT_SIZE = builtins.toString cfg.maximumImportSize;
+          DEBUG = cfg.debugOutput;
+          GOOGLE_ANALYTICS_ID = lib.optionalString (cfg.googleAnalyticsId != null) cfg.googleAnalyticsId;
+          SENTRY_DSN = lib.optionalString (cfg.sentryDsn != null) cfg.sentryDsn;
+          SENTRY_TUNNEL = lib.optionalString (cfg.sentryTunnel != null) cfg.sentryTunnel;
+          TEAM_LOGO = lib.optionalString (cfg.logo != null) cfg.logo;
+          DEFAULT_LANGUAGE = cfg.defaultLanguage;
+
+          RATE_LIMITER_ENABLED = builtins.toString cfg.rateLimiter.enable;
+          RATE_LIMITER_REQUESTS = builtins.toString cfg.rateLimiter.requests;
+          RATE_LIMITER_DURATION_WINDOW = builtins.toString cfg.rateLimiter.durationWindow;
+        }
+
+        (lib.mkIf (cfg.slackAuthentication != null) {
+          SLACK_CLIENT_ID = cfg.slackAuthentication.clientId;
+        })
+
+        (lib.mkIf (cfg.googleAuthentication != null) {
+          GOOGLE_CLIENT_ID = cfg.googleAuthentication.clientId;
+        })
+
+        (lib.mkIf (cfg.azureAuthentication != null) {
+          AZURE_CLIENT_ID = cfg.azureAuthentication.clientId;
+          AZURE_RESOURCE_APP_ID = cfg.azureAuthentication.resourceAppId;
+        })
+
+        (lib.mkIf (cfg.oidcAuthentication != null) {
+          OIDC_CLIENT_ID = cfg.oidcAuthentication.clientId;
+          OIDC_AUTH_URI = cfg.oidcAuthentication.authUrl;
+          OIDC_TOKEN_URI = cfg.oidcAuthentication.tokenUrl;
+          OIDC_USERINFO_URI = cfg.oidcAuthentication.userinfoUrl;
+          OIDC_USERNAME_CLAIM = cfg.oidcAuthentication.usernameClaim;
+          OIDC_DISPLAY_NAME = cfg.oidcAuthentication.displayName;
+          OIDC_SCOPES = lib.concatStringsSep " " cfg.oidcAuthentication.scopes;
+        })
+
+        (lib.mkIf (cfg.slackIntegration != null) {
+          SLACK_APP_ID = cfg.slackIntegration.appId;
+          SLACK_MESSAGE_ACTIONS = builtins.toString cfg.slackIntegration.messageActions;
+        })
+
+        (lib.mkIf (cfg.smtp != null) {
+          SMTP_HOST = cfg.smtp.host;
+          SMTP_PORT = builtins.toString cfg.smtp.port;
+          SMTP_USERNAME = cfg.smtp.username;
+          SMTP_FROM_EMAIL = cfg.smtp.fromEmail;
+          SMTP_REPLY_EMAIL = cfg.smtp.replyEmail;
+          SMTP_TLS_CIPHERS = cfg.smtp.tlsCiphers;
+          SMTP_SECURE = builtins.toString cfg.smtp.secure;
+        })
+      ];
+
+      preStart = ''
+        if [ ! -s ${lib.escapeShellArg cfg.secretKeyFile} ]; then
+          openssl rand -hex 32 > ${lib.escapeShellArg cfg.secretKeyFile}
+        fi
+        if [ ! -s ${lib.escapeShellArg cfg.utilsSecretFile} ]; then
+          openssl rand -hex 32 > ${lib.escapeShellArg cfg.utilsSecretFile}
+        fi
+
+        # The config file is required for the sequelize CLI.
+        ${if (cfg.databaseUrl == "local") then ''
+          cat <<EOF > $RUNTIME_DIRECTORY/database.json
+          {
+            "production-ssl-disabled": {
+              "host": "/run/postgresql",
+              "username": null,
+              "password": null,
+              "dialect": "postgres"
+            }
+          }
+          EOF
+        '' else ''
+          cat <<EOF > $RUNTIME_DIRECTORY/database.json
+          {
+            "production": {
+              "use_env_variable": "DATABASE_URL",
+              "dialect": "postgres",
+              "dialectOptions": {
+                "ssl": {
+                  "rejectUnauthorized": false
+                }
+              }
+            },
+            "production-ssl-disabled": {
+              "use_env_variable": "DATABASE_URL",
+              "dialect": "postgres"
+            }
+          }
+          EOF
+        ''}
+      '';
+
+      script = ''
+        export SECRET_KEY="$(head -n1 ${lib.escapeShellArg cfg.secretKeyFile})"
+        export UTILS_SECRET="$(head -n1 ${lib.escapeShellArg cfg.utilsSecretFile})"
+        export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})"
+        ${lib.optionalString (cfg.slackAuthentication != null) ''
+          export SLACK_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.slackAuthentication.secretFile})"
+        ''}
+        ${lib.optionalString (cfg.googleAuthentication != null) ''
+          export GOOGLE_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.googleAuthentication.clientSecretFile})"
+        ''}
+        ${lib.optionalString (cfg.azureAuthentication != null) ''
+          export AZURE_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.azureAuthentication.clientSecretFile})"
+        ''}
+        ${lib.optionalString (cfg.oidcAuthentication != null) ''
+          export OIDC_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.oidcAuthentication.clientSecretFile})"
+        ''}
+        ${lib.optionalString (cfg.sslKeyFile != null) ''
+          export SSL_KEY="$(head -n1 ${lib.escapeShellArg cfg.sslKeyFile})"
+        ''}
+        ${lib.optionalString (cfg.sslCertFile != null) ''
+          export SSL_CERT="$(head -n1 ${lib.escapeShellArg cfg.sslCertFile})"
+        ''}
+        ${lib.optionalString (cfg.slackIntegration != null) ''
+          export SLACK_VERIFICATION_TOKEN="$(head -n1 ${lib.escapeShellArg cfg.slackIntegration.verificationTokenFile})"
+        ''}
+        ${lib.optionalString (cfg.smtp != null) ''
+          export SMTP_PASSWORD="$(head -n1 ${lib.escapeShellArg cfg.smtp.passwordFile})"
+        ''}
+
+        ${if (cfg.databaseUrl == "local") then ''
+          export DATABASE_URL=${lib.escapeShellArg localPostgresqlUrl}
+          export PGSSLMODE=disable
+        '' else ''
+          export DATABASE_URL=${lib.escapeShellArg cfg.databaseUrl}
+        ''}
+
+        ${cfg.package}/bin/outline-server
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Restart = "always";
+        ProtectSystem = "strict";
+        PrivateHome = true;
+        PrivateTmp = true;
+        UMask = "0007";
+
+        StateDirectory = "outline";
+        StateDirectoryMode = "0750";
+        RuntimeDirectory = "outline";
+        RuntimeDirectoryMode = "0750";
+        # This working directory is required to find stuff like the set of
+        # onboarding files:
+        WorkingDirectory = "${cfg.package}/share/outline";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix b/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix
new file mode 100644
index 000000000000..666b82621268
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/peering-manager.nix
@@ -0,0 +1,265 @@
+{ config, lib, pkgs, buildEnv, ... }:
+
+with lib;
+
+let
+  cfg = config.services.peering-manager;
+  configFile = pkgs.writeTextFile {
+    name = "configuration.py";
+    text = ''
+      ALLOWED_HOSTS = ['*']
+      DATABASE = {
+        'NAME': 'peering-manager',
+        'USER': 'peering-manager',
+        'HOST': '/run/postgresql',
+      }
+
+      # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
+      # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
+      # to use two separate database IDs.
+      REDIS = {
+        'tasks': {
+          'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}',
+          'DATABASE': 0,
+        },
+        'caching': {
+          'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}',
+          'DATABASE': 1,
+        }
+      }
+
+      with open("${cfg.secretKeyFile}", "r") as file:
+        SECRET_KEY = file.readline()
+    '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
+      with open("${cfg.peeringdbApiKeyFile}", "r") as file:
+        PEERINGDB_API_KEY = file.readline()
+    '' + ''
+
+      ${cfg.extraConfig}
+    '';
+  };
+  pkg = (pkgs.peering-manager.overrideAttrs (old: {
+    postInstall = ''
+      ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
+    '' + optionalString cfg.enableLdap ''
+      ln -s ${cfg.ldapConfigPath} $out/opt/peering-manager/peering_manager/ldap_config.py
+    '';
+  })).override {
+    inherit (cfg) plugins;
+  };
+  peeringManagerManageScript = with pkgs; (writeScriptBin "peering-manager-manage" ''
+    #!${stdenv.shell}
+    export PYTHONPATH=${pkg.pythonPath}
+    sudo -u peering-manager ${pkg}/bin/peering-manager "$@"
+  '');
+
+in {
+  options.services.peering-manager = {
+    enable = mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable Peering Manager.
+
+        This module requires a reverse proxy that serves `/static` separately.
+        See this [example](https://github.com/peering-manager-community/peering-manager/blob/develop/contrib/nginx.conf/) on how to configure this.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "[::1]";
+      description = lib.mdDoc ''
+        Address the server will listen on.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8001;
+      description = lib.mdDoc ''
+        Port the server will listen on.
+      '';
+    };
+
+    plugins = mkOption {
+      type = types.functionTo (types.listOf types.package);
+      default = _: [];
+      defaultText = literalExpression ''
+        python3Packages: with python3Packages; [];
+      '';
+      description = lib.mdDoc ''
+        List of plugin packages to install.
+      '';
+    };
+
+    secretKeyFile = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path to a file containing the secret key.
+      '';
+    };
+
+    peeringdbApiKeyFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to a file containing the PeeringDB API key.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = lib.mdDoc ''
+        Additional lines of configuration appended to the `configuration.py`.
+        See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
+      '';
+    };
+
+    enableLdap = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable LDAP-Authentication for Peering Manager.
+
+        This requires a configuration file being pass through `ldapConfigPath`.
+      '';
+    };
+
+    ldapConfigPath = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
+        See the [documentation](https://peering-manager.readthedocs.io/en/stable/setup/6-ldap/#configuration) for possible options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.peering-manager.plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+
+    system.build.peeringManagerPkg = pkg;
+
+    services.redis.servers.peering-manager.enable = true;
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "peering-manager" ];
+      ensureUsers = [
+        {
+          name = "peering-manager";
+          ensurePermissions = {
+            "DATABASE \"peering-manager\"" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+    };
+
+    environment.systemPackages = [ peeringManagerManageScript ];
+
+    systemd.targets.peering-manager = {
+      description = "Target for all Peering Manager services";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" "redis-peering-manager.service" ];
+    };
+
+    systemd.services = let
+      defaultServiceConfig = {
+        WorkingDirectory = "/var/lib/peering-manager";
+        User = "peering-manager";
+        Group = "peering-manager";
+        StateDirectory = "peering-manager";
+        StateDirectoryMode = "0750";
+        Restart = "on-failure";
+      };
+    in {
+      peering-manager-migration = {
+        description = "Peering Manager migrations";
+        wantedBy = [ "peering-manager.target" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          Type = "oneshot";
+          ExecStart = ''
+            ${pkg}/bin/peering-manager migrate
+          '';
+        };
+      };
+
+      peering-manager = {
+        description = "Peering Manager WSGI Service";
+        wantedBy = [ "peering-manager.target" ];
+        after = [ "peering-manager-migration.service" ];
+
+        preStart = ''
+          ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
+        '';
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          ExecStart = ''
+            ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
+              --bind ${cfg.listenAddress}:${toString cfg.port} \
+              --pythonpath ${pkg}/opt/peering-manager
+          '';
+        };
+      };
+
+      peering-manager-rq = {
+        description = "Peering Manager Request Queue Worker";
+        wantedBy = [ "peering-manager.target" ];
+        after = [ "peering-manager.service" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          ExecStart = ''
+            ${pkg}/bin/peering-manager rqworker high default low
+          '';
+        };
+      };
+
+      peering-manager-housekeeping = {
+        description = "Peering Manager housekeeping job";
+        after = [ "peering-manager.service" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          Type = "oneshot";
+          ExecStart = ''
+            ${pkg}/bin/peering-manager housekeeping
+          '';
+        };
+      };
+    };
+
+    systemd.timers.peering-manager-housekeeping = {
+      description = "Run Peering Manager housekeeping job";
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        OnCalendar = "daily";
+      };
+    };
+
+    users.users.peering-manager = {
+      home = "/var/lib/peering-manager";
+      isSystemUser = true;
+      group = "peering-manager";
+    };
+    users.groups.peering-manager = {};
+    users.groups."${config.services.redis.servers.peering-manager.user}".members = [ "peering-manager" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/peertube.nix b/nixpkgs/nixos/modules/services/web-apps/peertube.nix
index c5a80e2d7d9d..4ef2d7dce532 100644
--- a/nixpkgs/nixos/modules/services/web-apps/peertube.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/peertube.nix
@@ -67,9 +67,19 @@ let
     node ~/dist/server/tools/peertube.js $@
   '';
 
+  nginxCommonHeaders = lib.optionalString cfg.enableWebHttps ''
+    add_header Strict-Transport-Security      'max-age=63072000; includeSubDomains';
+  '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
+    add_header Alt-Svc                        'h3=":443"; ma=86400';
+  '' + ''
+    add_header Access-Control-Allow-Origin    '*';
+    add_header Access-Control-Allow-Methods   'GET, OPTIONS';
+    add_header Access-Control-Allow-Headers   'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+  '';
+
 in {
   options.services.peertube = {
-    enable = lib.mkEnableOption "Enable Peertube’s service";
+    enable = lib.mkEnableOption (lib.mdDoc "Peertube");
 
     user = lib.mkOption {
       type = lib.types.str;
@@ -90,13 +100,13 @@ in {
     };
 
     listenHttp = lib.mkOption {
-      type = lib.types.int;
+      type = lib.types.port;
       default = 9000;
       description = lib.mdDoc "listen port for HTTP server.";
     };
 
     listenWeb = lib.mkOption {
-      type = lib.types.int;
+      type = lib.types.port;
       default = 9000;
       description = lib.mdDoc "listen port for WEB server.";
     };
@@ -145,6 +155,24 @@ in {
       description = lib.mdDoc "Configuration for peertube.";
     };
 
+    configureNginx = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc "Configure nginx as a reverse proxy for peertube.";
+    };
+
+    secrets = {
+      secretsFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/secrets/peertube";
+        description = lib.mdDoc ''
+          Secrets to run PeerTube.
+          Generate one using `openssl rand -hex 32`
+        '';
+      };
+    };
+
     database = {
       createLocally = lib.mkOption {
         type = lib.types.bool;
@@ -165,7 +193,7 @@ in {
       };
 
       port = lib.mkOption {
-        type = lib.types.int;
+        type = lib.types.port;
         default = 5432;
         description = lib.mdDoc "Database host port.";
       };
@@ -185,7 +213,7 @@ in {
       passwordFile = lib.mkOption {
         type = lib.types.nullOr lib.types.path;
         default = null;
-        example = "/run/keys/peertube/password-posgressql-db";
+        example = "/run/keys/peertube/password-postgresql";
         description = lib.mdDoc "Password for PostgreSQL database.";
       };
     };
@@ -266,6 +294,11 @@ in {
             prevent this.
           '';
       }
+      { assertion = cfg.secrets.secretsFile != null;
+          message = ''
+            <option>services.peertube.secrets.secretsFile</option> needs to be set.
+          '';
+      }
       { 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.
@@ -333,6 +366,7 @@ in {
           captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
           cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
           plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
+          well_known = lib.mkDefault "/var/lib/peertube/storage/well_known/";
           client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
         };
         import = {
@@ -351,12 +385,14 @@ in {
     systemd.tmpfiles.rules = [
       "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
       "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
+      "d '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '/var/lib/peertube/www' 0750 ${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" ];
+      requires = [ "postgresql.service" ];
 
       script = let
         psqlSetupCommands = pkgs.writeText "peertube-init.sql" ''
@@ -385,18 +421,24 @@ in {
     systemd.services.peertube = {
       description = "PeerTube daemon";
       after = [ "network.target" ]
-        ++ lib.optionals cfg.redis.createLocally [ "redis.service" ]
+        ++ lib.optional cfg.redis.createLocally "redis-peertube.service"
+        ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
+      requires = lib.optional cfg.redis.createLocally "redis-peertube.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 python3 ];
+      path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ];
 
       script = ''
         #!/bin/sh
         umask 077
         cat > /var/lib/peertube/config/local.yaml <<EOF
+        ${lib.optionalString (cfg.secrets.secretsFile != null) ''
+        secrets:
+          peertube: '$(cat ${cfg.secrets.secretsFile})'
+        ''}
         ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
         database:
           password: '$(cat ${cfg.database.passwordFile})'
@@ -410,8 +452,11 @@ in {
           password: '$(cat ${cfg.smtp.passwordFile})'
         ''}
         EOF
-        ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
+        umask 027
         ln -sf ${configFile} /var/lib/peertube/config/production.json
+        ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
+        ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client
+        ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides
         npm start
       '';
       serviceConfig = {
@@ -420,6 +465,7 @@ in {
         RestartSec = 20;
         TimeoutSec = 60;
         WorkingDirectory = cfg.package;
+        SyslogIdentifier = "peertube";
         # User and group
         User = cfg.user;
         Group = cfg.group;
@@ -441,6 +487,347 @@ in {
       } // cfgService;
     };
 
+    services.nginx = lib.mkIf cfg.configureNginx {
+      enable = true;
+      virtualHosts."${cfg.localDomain}" = {
+        root = "/var/lib/peertube/www";
+
+        # Application
+        locations."/" = {
+          tryFiles = "/dev/null @api";
+          priority = 1110;
+        };
+
+        locations."= /api/v1/videos/upload-resumable" = {
+          tryFiles = "/dev/null @api";
+          priority = 1120;
+
+          extraConfig = ''
+            client_max_body_size                        0;
+            proxy_request_buffering                     off;
+          '';
+        };
+
+        locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
+          tryFiles = "/dev/null @api";
+          root = cfg.settings.storage.tmp;
+          priority = 1130;
+
+          extraConfig = ''
+            client_max_body_size                        12G;
+            add_header X-File-Maximum-Size              8G always;
+          '' + lib.optionalString cfg.enableWebHttps ''
+            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
+          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
+            add_header Alt-Svc                          'h3=":443"; ma=86400';
+          '';
+        };
+
+        locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
+          tryFiles = "/dev/null @api";
+          priority = 1140;
+
+          extraConfig = ''
+            client_max_body_size                        6M;
+            add_header X-File-Maximum-Size              4M always;
+          '' + lib.optionalString cfg.enableWebHttps ''
+            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
+          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
+            add_header Alt-Svc                          'h3=":443"; ma=86400';
+          '';
+        };
+
+        locations."@api" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1150;
+
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
+
+            proxy_connect_timeout                       10m;
+
+            proxy_send_timeout                          10m;
+            proxy_read_timeout                          10m;
+
+            client_max_body_size                        100k;
+            send_timeout                                10m;
+          '';
+        };
+
+        # Websocket
+        locations."/socket.io" = {
+          tryFiles = "/dev/null @api_websocket";
+          priority = 1210;
+        };
+
+        locations."/tracker/socket" = {
+          tryFiles = "/dev/null @api_websocket";
+          priority = 1220;
+
+          extraConfig = ''
+            proxy_read_timeout                          15m;
+          '';
+        };
+
+        locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
+          tryFiles = "/dev/null @api_websocket";
+          priority = 1230;
+        };
+
+        locations."@api_websocket" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1240;
+
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
+            proxy_set_header Upgrade                    $http_upgrade;
+            proxy_set_header Connection                 'upgrade';
+
+            proxy_http_version                          1.1;
+          '';
+        };
+
+        # Bypass PeerTube for performance reasons.
+        locations."~ ^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png|default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$" = {
+          tryFiles = "/client-overrides/$1 /client/$1 $1";
+          priority = 1310;
+        };
+
+        locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
+          alias = "${cfg.package}/client/dist/$1";
+          priority = 1320;
+          extraConfig = ''
+            add_header Cache-Control                    'public, max-age=604800, immutable';
+          '' + lib.optionalString cfg.enableWebHttps ''
+            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
+          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
+            add_header Alt-Svc                          'h3=":443"; ma=86400';
+          '';
+        };
+
+        locations."^~ /lazy-static/avatars/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.avatars;
+          priority = 1330;
+          extraConfig = ''
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Cache-Control                  'no-cache';
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+
+            ${nginxCommonHeaders}
+            add_header Cache-Control                    'public, max-age=7200';
+
+            rewrite ^/lazy-static/avatars/(.*)$         /$1 break;
+          '';
+        };
+
+        locations."^~ /lazy-static/banners/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.avatars;
+          priority = 1340;
+          extraConfig = ''
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Cache-Control                  'no-cache';
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+
+            ${nginxCommonHeaders}
+            add_header Cache-Control                    'public, max-age=7200';
+
+            rewrite ^/lazy-static/banners/(.*)$         /$1 break;
+          '';
+        };
+
+        locations."^~ /lazy-static/previews/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.previews;
+          priority = 1350;
+          extraConfig = ''
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Cache-Control                  'no-cache';
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+
+            ${nginxCommonHeaders}
+            add_header Cache-Control                    'public, max-age=7200';
+
+            rewrite ^/lazy-static/previews/(.*)$        /$1 break;
+          '';
+        };
+
+        locations."^~ /static/streaming-playlists/private/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1410;
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
+
+            proxy_limit_rate                            5M;
+          '';
+        };
+
+        locations."^~ /static/webseed/private/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1420;
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
+
+            proxy_limit_rate                            5M;
+          '';
+        };
+
+        locations."^~ /static/thumbnails/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.thumbnails;
+          priority = 1430;
+          extraConfig = ''
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Cache-Control                  'no-cache';
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+
+            ${nginxCommonHeaders}
+            add_header Cache-Control                    'public, max-age=7200';
+
+            rewrite ^/static/thumbnails/(.*)$           /$1 break;
+          '';
+        };
+
+        locations."^~ /static/redundancy/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.redundancy;
+          priority = 1440;
+          extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+            if ($request_method = 'GET') {
+              ${nginxCommonHeaders}
+
+              access_log                                off;
+            }
+
+            aio                                         threads;
+            sendfile                                    on;
+            sendfile_max_chunk                          1M;
+
+            limit_rate                                  $peertube_limit_rate;
+            limit_rate_after                            5M;
+
+            rewrite ^/static/redundancy/(.*)$           /$1 break;
+          '';
+        };
+
+        locations."^~ /static/streaming-playlists/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.streaming_playlists;
+          priority = 1450;
+          extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+            if ($request_method = 'GET') {
+              ${nginxCommonHeaders}
+
+              access_log                                off;
+            }
+
+            aio                                         threads;
+            sendfile                                    on;
+            sendfile_max_chunk                          1M;
+
+            limit_rate                                  $peertube_limit_rate;
+            limit_rate_after                            5M;
+
+            rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
+          '';
+        };
+
+        locations."^~ /static/webseed/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.videos;
+          priority = 1460;
+          extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+            if ($request_method = 'GET') {
+              ${nginxCommonHeaders}
+
+              access_log                                off;
+            }
+
+            aio                                         threads;
+            sendfile                                    on;
+            sendfile_max_chunk                          1M;
+
+            limit_rate                                  $peertube_limit_rate;
+            limit_rate_after                            5M;
+
+            rewrite ^/static/webseed/(.*)$              /$1 break;
+          '';
+        };
+
+        extraConfig = lib.optionalString cfg.enableWebHttps ''
+          add_header Strict-Transport-Security          'max-age=63072000; includeSubDomains';
+        '';
+      };
+    };
+
     services.postgresql = lib.mkIf cfg.database.createLocally {
       enable = true;
     };
@@ -472,12 +859,14 @@ in {
           home = cfg.package;
         };
       })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ])
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ])
       (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
     ];
 
-    users.groups = lib.optionalAttrs (cfg.group == "peertube") {
-      peertube = { };
+    users.groups = {
+      ${cfg.group} = {
+        members = lib.optional cfg.configureNginx config.services.nginx.user;
+      };
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index faf0ce13238e..dd51bacd75ea 100644
--- a/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -18,40 +18,40 @@ in
 
     services.pgpkeyserver-lite = {
 
-      enable = mkEnableOption "pgpkeyserver-lite on a nginx vHost proxying to a gpg keyserver";
+      enable = mkEnableOption (lib.mdDoc "pgpkeyserver-lite on a nginx vHost proxying to a gpg keyserver");
 
       package = mkOption {
         default = pkgs.pgpkeyserver-lite;
         defaultText = literalExpression "pkgs.pgpkeyserver-lite";
         type = types.package;
-        description = "
+        description = lib.mdDoc ''
           Which webgui derivation to use.
-        ";
+        '';
       };
 
       hostname = mkOption {
         type = types.str;
-        description = "
+        description = lib.mdDoc ''
           Which hostname to set the vHost to that is proxying to sks.
-        ";
+        '';
       };
 
       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.
-        ";
+        description = lib.mdDoc ''
+          Which IP address the sks-keyserver is listening on.
+        '';
       };
 
       hkpPort = mkOption {
         default = sksCfg.hkpPort;
         defaultText = literalExpression "config.${sksOpt.hkpPort}";
         type = types.int;
-        description = "
+        description = lib.mdDoc ''
           Which port the sks-keyserver is listening on.
-        ";
+        '';
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/web-apps/photoprism.nix b/nixpkgs/nixos/modules/services/web-apps/photoprism.nix
new file mode 100644
index 000000000000..d5ca6014780a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/photoprism.nix
@@ -0,0 +1,155 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.photoprism;
+
+  env = {
+    PHOTOPRISM_ORIGINALS_PATH = cfg.originalsPath;
+    PHOTOPRISM_STORAGE_PATH = cfg.storagePath;
+    PHOTOPRISM_IMPORT_PATH = cfg.importPath;
+    PHOTOPRISM_HTTP_HOST = cfg.address;
+    PHOTOPRISM_HTTP_PORT = toString cfg.port;
+  } // (
+    lib.mapAttrs (_: toString) cfg.settings
+  );
+
+  manage =
+    let
+      setupEnv = lib.concatStringsSep "\n" (lib.mapAttrsToList (name: val: "export ${name}=${lib.escapeShellArg val}") env);
+    in
+    pkgs.writeShellScript "manage" ''
+      ${setupEnv}
+      exec ${cfg.package}/bin/photoprism "$@"
+    '';
+in
+{
+  meta.maintainers = with lib.maintainers; [ stunkymonkey ];
+
+  options.services.photoprism = {
+
+    enable = lib.mkEnableOption (lib.mdDoc "Photoprism web server");
+
+    passwordFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Admin password file.
+      '';
+    };
+
+    address = lib.mkOption {
+      type = lib.types.str;
+      default = "localhost";
+      description = lib.mdDoc ''
+        Web interface address.
+      '';
+    };
+
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 2342;
+      description = lib.mdDoc ''
+        Web interface port.
+      '';
+    };
+
+    originalsPath = lib.mkOption {
+      type = lib.types.path;
+      default = null;
+      example = "/data/photos";
+      description = lib.mdDoc ''
+        Storage path of your original media files (photos and videos).
+      '';
+    };
+
+    importPath = lib.mkOption {
+      type = lib.types.str;
+      default = "import";
+      description = lib.mdDoc ''
+        Relative or absolute to the `originalsPath` from where the files should be imported.
+      '';
+    };
+
+    storagePath = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/photoprism";
+      description = lib.mdDoc ''
+        Location for sidecar, cache, and database files.
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs "photoprism" { };
+
+    settings = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      default = { };
+      description = lib.mdDoc ''
+        See [the getting-started guide](https://docs.photoprism.app/getting-started/config-options/) for available options.
+      '';
+      example = {
+        PHOTOPRISM_DEFAULT_LOCALE = "de";
+        PHOTOPRISM_ADMIN_USER = "root";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.photoprism = {
+      description = "Photoprism server";
+
+      serviceConfig = {
+        Restart = "on-failure";
+        User = "photoprism";
+        Group = "photoprism";
+        DynamicUser = true;
+        StateDirectory = "photoprism";
+        WorkingDirectory = "/var/lib/photoprism";
+        RuntimeDirectory = "photoprism";
+
+        LoadCredential = lib.optionalString (cfg.passwordFile != null)
+          "PHOTOPRISM_ADMIN_PASSWORD:${cfg.passwordFile}";
+
+        CapabilityBoundingSet = "";
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
+        UMask = "0066";
+      } // lib.optionalAttrs (cfg.port < 1024) {
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+      };
+
+      wantedBy = [ "multi-user.target" ];
+      environment = env;
+
+      # reminder: easier password configuration will come in https://github.com/photoprism/photoprism/pull/2302
+      preStart = ''
+        ln -sf ${manage} photoprism-manage
+
+        ${lib.optionalString (cfg.passwordFile != null) ''
+          export PHOTOPRISM_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PHOTOPRISM_ADMIN_PASSWORD")
+        ''}
+        exec ${cfg.package}/bin/photoprism migrations run -f
+      '';
+
+      script = ''
+        ${lib.optionalString (cfg.passwordFile != null) ''
+          export PHOTOPRISM_ADMIN_PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/PHOTOPRISM_ADMIN_PASSWORD")
+        ''}
+        exec ${cfg.package}/bin/photoprism start
+      '';
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/phylactery.nix b/nixpkgs/nixos/modules/services/web-apps/phylactery.nix
index d512b48539b8..4801bd203b48 100644
--- a/nixpkgs/nixos/modules/services/web-apps/phylactery.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/phylactery.nix
@@ -4,7 +4,7 @@ with lib;
 let cfg = config.services.phylactery;
 in {
   options.services.phylactery = {
-    enable = mkEnableOption "Whether to enable Phylactery server";
+    enable = mkEnableOption (lib.mdDoc "Whether to enable Phylactery server");
 
     host = mkOption {
       type = types.str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/pict-rs.md b/nixpkgs/nixos/modules/services/web-apps/pict-rs.md
index 4b622049909d..2fa6bb3aebce 100644
--- a/nixpkgs/nixos/modules/services/web-apps/pict-rs.md
+++ b/nixpkgs/nixos/modules/services/web-apps/pict-rs.md
@@ -15,6 +15,7 @@ this will start the http server on port 8080 by default.
 ## Usage {#module-services-pict-rs-usage}
 
 pict-rs offers the following endpoints:
+
 - `POST /image` for uploading an image. Uploaded content must be valid multipart/form-data with an
     image array located within the `images[]` key
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/pict-rs.nix b/nixpkgs/nixos/modules/services/web-apps/pict-rs.nix
index ab5a9ed07356..3270715a051b 100644
--- a/nixpkgs/nixos/modules/services/web-apps/pict-rs.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/pict-rs.nix
@@ -5,12 +5,10 @@ let
 in
 {
   meta.maintainers = with maintainers; [ happysalada ];
-  # Don't edit the docbook xml directly, edit the md and generate it:
-  # `pandoc pict-rs.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > pict-rs.xml`
-  meta.doc = ./pict-rs.xml;
+  meta.doc = ./pict-rs.md;
 
   options.services.pict-rs = {
-    enable = mkEnableOption "pict-rs server";
+    enable = mkEnableOption (lib.mdDoc "pict-rs server");
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/pict-rs";
@@ -36,8 +34,8 @@ in
   config = lib.mkIf cfg.enable {
     systemd.services.pict-rs = {
       environment = {
-        PICTRS_PATH = cfg.dataDir;
-        PICTRS_ADDR = "${cfg.address}:${toString cfg.port}";
+        PICTRS__PATH = cfg.dataDir;
+        PICTRS__ADDR = "${cfg.address}:${toString cfg.port}";
       };
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/web-apps/pict-rs.xml b/nixpkgs/nixos/modules/services/web-apps/pict-rs.xml
deleted file mode 100644
index bf129f5cc2ac..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/pict-rs.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-pict-rs">
-  <title>Pict-rs</title>
-  <para>
-    pict-rs is a a simple image hosting service.
-  </para>
-  <section xml:id="module-services-pict-rs-quickstart">
-    <title>Quickstart</title>
-    <para>
-      the minimum to start pict-rs is
-    </para>
-    <programlisting language="bash">
-services.pict-rs.enable = true;
-</programlisting>
-    <para>
-      this will start the http server on port 8080 by default.
-    </para>
-  </section>
-  <section xml:id="module-services-pict-rs-usage">
-    <title>Usage</title>
-    <para>
-      pict-rs offers the following endpoints: -
-      <literal>POST /image</literal> for uploading an image. Uploaded
-      content must be valid multipart/form-data with an image array
-      located within the <literal>images[]</literal> key
-    </para>
-    <programlisting>
-This endpoint returns the following JSON structure on success with a 201 Created status
-```json
-{
-    &quot;files&quot;: [
-        {
-            &quot;delete_token&quot;: &quot;JFvFhqJA98&quot;,
-            &quot;file&quot;: &quot;lkWZDRvugm.jpg&quot;
-        },
-        {
-            &quot;delete_token&quot;: &quot;kAYy9nk2WK&quot;,
-            &quot;file&quot;: &quot;8qFS0QooAn.jpg&quot;
-        },
-        {
-            &quot;delete_token&quot;: &quot;OxRpM3sf0Y&quot;,
-            &quot;file&quot;: &quot;1hJaYfGE01.jpg&quot;
-        }
-    ],
-    &quot;msg&quot;: &quot;ok&quot;
-}
-```
-</programlisting>
-    <itemizedlist>
-      <listitem>
-        <para>
-          <literal>GET /image/download?url=...</literal> Download an
-          image from a remote server, returning the same JSON payload as
-          the <literal>POST</literal> endpoint
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/original/{file}</literal> for getting a
-          full-resolution image. <literal>file</literal> here is the
-          <literal>file</literal> key from the <literal>/image</literal>
-          endpoint’s JSON
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/details/original/{file}</literal> for
-          getting the details of a full-resolution image. The returned
-          JSON is structured like so:
-          <literal>json     {         &quot;width&quot;: 800,         &quot;height&quot;: 537,         &quot;content_type&quot;: &quot;image/webp&quot;,         &quot;created_at&quot;: [             2020,             345,             67376,             394363487         ]     }</literal>
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/process.{ext}?src={file}&amp;...</literal>
-          get a file with transformations applied. existing
-          transformations include
-        </para>
-        <itemizedlist spacing="compact">
-          <listitem>
-            <para>
-              <literal>identity=true</literal>: apply no changes
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>blur={float}</literal>: apply a gaussian blur to
-              the file
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>thumbnail={int}</literal>: produce a thumbnail of
-              the image fitting inside an <literal>{int}</literal> by
-              <literal>{int}</literal> square using raw pixel sampling
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>resize={int}</literal>: produce a thumbnail of
-              the image fitting inside an <literal>{int}</literal> by
-              <literal>{int}</literal> square using a Lanczos2 filter.
-              This is slower than sampling but looks a bit better in
-              some cases
-            </para>
-          </listitem>
-          <listitem>
-            <para>
-              <literal>crop={int-w}x{int-h}</literal>: produce a cropped
-              version of the image with an <literal>{int-w}</literal> by
-              <literal>{int-h}</literal> aspect ratio. The resulting
-              crop will be centered on the image. Either the width or
-              height of the image will remain full-size, depending on
-              the image’s aspect ratio and the requested aspect ratio.
-              For example, a 1600x900 image cropped with a 1x1 aspect
-              ratio will become 900x900. A 1600x1100 image cropped with
-              a 16x9 aspect ratio will become 1600x900.
-            </para>
-          </listitem>
-        </itemizedlist>
-        <para>
-          Supported <literal>ext</literal> file extensions include
-          <literal>png</literal>, <literal>jpg</literal>, and
-          <literal>webp</literal>
-        </para>
-        <para>
-          An example of usage could be
-          <literal>GET /image/process.jpg?src=asdf.png&amp;thumbnail=256&amp;blur=3.0</literal>
-          which would create a 256x256px JPEG thumbnail and blur it
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>GET /image/details/process.{ext}?src={file}&amp;...</literal>
-          for getting the details of a processed image. The returned
-          JSON is the same format as listed for the full-resolution
-          details endpoint.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          <literal>DELETE /image/delete/{delete_token}/{file}</literal>
-          or <literal>GET /image/delete/{delete_token}/{file}</literal>
-          to delete a file, where <literal>delete_token</literal> and
-          <literal>file</literal> are from the <literal>/image</literal>
-          endpoint’s JSON
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-  <section xml:id="module-services-pict-rs-missing">
-    <title>Missing</title>
-    <itemizedlist spacing="compact">
-      <listitem>
-        <para>
-          Configuring the secure-api-key is not included yet. The
-          envisioned basic use case is consumption on localhost by other
-          services without exposing the service to the internet.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/pixelfed.nix b/nixpkgs/nixos/modules/services/web-apps/pixelfed.nix
new file mode 100644
index 000000000000..430a368650ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/pixelfed.nix
@@ -0,0 +1,478 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pixelfed;
+  user = cfg.user;
+  group = cfg.group;
+  pixelfed = cfg.package.override { inherit (cfg) dataDir runtimeDir; };
+  # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L185-L190
+  extraPrograms = with pkgs; [ jpegoptim optipng pngquant gifsicle ffmpeg ];
+  # Ensure PHP extensions: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L135-L147
+  phpPackage = cfg.phpPackage.buildEnv {
+    extensions = { enabled, all }:
+      enabled
+      ++ (with all; [ bcmath ctype curl mbstring gd intl zip redis imagick ]);
+  };
+  configFile =
+    pkgs.writeText "pixelfed-env" (lib.generators.toKeyValue { } cfg.settings);
+  # Management script
+  pixelfed-manage = pkgs.writeShellScriptBin "pixelfed-manage" ''
+    cd ${pixelfed}
+    sudo=exec
+    if [[ "$USER" != ${user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${user}'
+    fi
+    $sudo ${cfg.phpPackage}/bin/php artisan "$@"
+  '';
+  dbSocket = {
+    "pgsql" = "/run/postgresql";
+    "mysql" = "/run/mysqld/mysqld.sock";
+  }.${cfg.database.type};
+  dbService = {
+    "pgsql" = "postgresql.service";
+    "mysql" = "mysql.service";
+  }.${cfg.database.type};
+  redisService = "redis-pixelfed.service";
+in {
+  options.services = {
+    pixelfed = {
+      enable = mkEnableOption (lib.mdDoc "a Pixelfed instance");
+      package = mkPackageOptionMD pkgs "pixelfed" { };
+      phpPackage = mkPackageOptionMD pkgs "php81" { };
+
+      user = mkOption {
+        type = types.str;
+        default = "pixelfed";
+        description = lib.mdDoc ''
+          User account under which pixelfed runs.
+
+          ::: {.note}
+          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 pixelfed application starts.
+          :::
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "pixelfed";
+        description = lib.mdDoc ''
+          Group account under which pixelfed runs.
+
+          ::: {.note}
+          If left as the default value this group will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the group exists before the pixelfed application starts.
+          :::
+        '';
+      };
+
+      domain = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          FQDN for the Pixelfed instance.
+        '';
+      };
+
+      secretFile = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          A secret file to be sourced for the .env settings.
+          Place `APP_KEY` and other settings that should not end up in the Nix store here.
+        '';
+      };
+
+      settings = mkOption {
+        type = with types; (attrsOf (oneOf [ bool int str ]));
+        description = lib.mdDoc ''
+          .env settings for Pixelfed.
+          Secrets should use `secretFile` option instead.
+        '';
+      };
+
+      nginx = mkOption {
+        type = types.nullOr (types.submodule
+          (import ../web-servers/nginx/vhost-options.nix {
+            inherit config lib;
+          }));
+        default = null;
+        example = lib.literalExpression ''
+          {
+            serverAliases = [
+              "pics.''${config.networking.domain}"
+            ];
+            enableACME = true;
+            forceHttps = true;
+          }
+        '';
+        description = lib.mdDoc ''
+          With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr.
+          Set to {} if you do not need any customization to the virtual host.
+          If enabled, then by default, the {option}`serverName` is
+          `''${domain}`,
+          If this is set to null (the default), no nginx virtualHost will be configured.
+        '';
+      };
+
+      redis.createLocally = mkEnableOption
+        (lib.mdDoc "a local Redis database using UNIX socket authentication")
+        // {
+          default = true;
+        };
+
+      database = {
+        createLocally = mkEnableOption
+          (lib.mdDoc "a local database using UNIX socket authentication") // {
+            default = true;
+          };
+        automaticMigrations = mkEnableOption
+          (lib.mdDoc "automatic migrations for database schema and data") // {
+            default = true;
+          };
+
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" ];
+          example = "pgsql";
+          default = "mysql";
+          description = lib.mdDoc ''
+            Database engine to use.
+            Note that PGSQL is not well supported: https://github.com/pixelfed/pixelfed/issues/2727
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "pixelfed";
+          description = lib.mdDoc "Database name.";
+        };
+      };
+
+      maxUploadSize = mkOption {
+        type = types.str;
+        default = "8M";
+        description = lib.mdDoc ''
+          Max upload size with units.
+        '';
+      };
+
+      poolConfig = mkOption {
+        type = with types; attrsOf (oneOf [ int str bool ]);
+        default = { };
+
+        description = lib.mdDoc ''
+          Options for Pixelfed's PHP-FPM pool.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/pixelfed";
+        description = lib.mdDoc ''
+          State directory of the `pixelfed` user which holds
+          the application's state and data.
+        '';
+      };
+
+      runtimeDir = mkOption {
+        type = types.str;
+        default = "/run/pixelfed";
+        description = lib.mdDoc ''
+          Ruutime directory of the `pixelfed` user which holds
+          the application's caches and temporary files.
+        '';
+      };
+
+      schedulerInterval = mkOption {
+        type = types.str;
+        default = "1d";
+        description = lib.mdDoc "How often the Pixelfed cron task should run";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.pixelfed = mkIf (cfg.user == "pixelfed") {
+      isSystemUser = true;
+      group = cfg.group;
+      extraGroups = lib.optional cfg.redis.createLocally "redis-pixelfed";
+    };
+    users.groups.pixelfed = mkIf (cfg.group == "pixelfed") { };
+
+    services.redis.servers.pixelfed.enable = lib.mkIf cfg.redis.createLocally true;
+    services.pixelfed.settings = mkMerge [
+      ({
+        APP_ENV = mkDefault "production";
+        APP_DEBUG = mkDefault false;
+        # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L312-L316
+        APP_URL = mkDefault "https://${cfg.domain}";
+        ADMIN_DOMAIN = mkDefault cfg.domain;
+        APP_DOMAIN = mkDefault cfg.domain;
+        SESSION_DOMAIN = mkDefault cfg.domain;
+        SESSION_SECURE_COOKIE = mkDefault true;
+        OPEN_REGISTRATION = mkDefault false;
+        # ActivityPub: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L360-L364
+        ACTIVITY_PUB = mkDefault true;
+        AP_REMOTE_FOLLOW = mkDefault true;
+        AP_INBOX = mkDefault true;
+        AP_OUTBOX = mkDefault true;
+        AP_SHAREDINBOX = mkDefault true;
+        # Image optimization: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L367-L404
+        PF_OPTIMIZE_IMAGES = mkDefault true;
+        IMAGE_DRIVER = mkDefault "imagick";
+        # Mobile APIs
+        OAUTH_ENABLED = mkDefault true;
+        # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L351
+        EXP_EMC = mkDefault true;
+        # Defer to systemd
+        LOG_CHANNEL = mkDefault "stderr";
+        # TODO: find out the correct syntax?
+        # TRUST_PROXIES = mkDefault "127.0.0.1/8, ::1/128";
+      })
+      (mkIf (cfg.redis.createLocally) {
+        BROADCAST_DRIVER = mkDefault "redis";
+        CACHE_DRIVER = mkDefault "redis";
+        QUEUE_DRIVER = mkDefault "redis";
+        SESSION_DRIVER = mkDefault "redis";
+        WEBSOCKET_REPLICATION_MODE = mkDefault "redis";
+        # Support phpredis and predis configuration-style.
+        REDIS_SCHEME = "unix";
+        REDIS_HOST = config.services.redis.servers.pixelfed.unixSocket;
+        REDIS_PATH = config.services.redis.servers.pixelfed.unixSocket;
+      })
+      (mkIf (cfg.database.createLocally) {
+        DB_CONNECTION = cfg.database.type;
+        DB_SOCKET = dbSocket;
+        DB_DATABASE = cfg.database.name;
+        DB_USERNAME = user;
+        # No TCP/IP connection.
+        DB_PORT = 0;
+      })
+    ];
+
+    environment.systemPackages = [ pixelfed-manage ];
+
+    services.mysql =
+      mkIf (cfg.database.createLocally && cfg.database.type == "mysql") {
+        enable = mkDefault true;
+        package = mkDefault pkgs.mariadb;
+        ensureDatabases = [ cfg.database.name ];
+        ensureUsers = [{
+          name = user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }];
+      };
+
+    services.postgresql =
+      mkIf (cfg.database.createLocally && cfg.database.type == "pgsql") {
+        enable = mkDefault true;
+        ensureDatabases = [ cfg.database.name ];
+        ensureUsers = [{
+          name = user;
+          ensurePermissions = { };
+        }];
+      };
+
+    # Make each individual option overridable with lib.mkDefault.
+    services.pixelfed.poolConfig = lib.mapAttrs' (n: v: lib.nameValuePair n (lib.mkDefault v)) {
+      "pm" = "dynamic";
+      "php_admin_value[error_log]" = "stderr";
+      "php_admin_flag[log_errors]" = true;
+      "catch_workers_output" = true;
+      "pm.max_children" = "32";
+      "pm.start_servers" = "2";
+      "pm.min_spare_servers" = "2";
+      "pm.max_spare_servers" = "4";
+      "pm.max_requests" = "500";
+    };
+
+    services.phpfpm.pools.pixelfed = {
+      inherit user group;
+      inherit phpPackage;
+
+      phpOptions = ''
+        post_max_size = ${toString cfg.maxUploadSize}
+        upload_max_filesize = ${toString cfg.maxUploadSize}
+        max_execution_time = 600;
+      '';
+
+      settings = {
+        "listen.owner" = user;
+        "listen.group" = group;
+        "listen.mode" = "0660";
+        "catch_workers_output" = "yes";
+      } // cfg.poolConfig;
+    };
+
+    systemd.services.phpfpm-pixelfed.after = [ "pixelfed-data-setup.service" ];
+    systemd.services.phpfpm-pixelfed.requires =
+      [ "pixelfed-horizon.service" "pixelfed-data-setup.service" ]
+      ++ lib.optional cfg.database.createLocally dbService
+      ++ lib.optional cfg.redis.createLocally redisService;
+    # Ensure image optimizations programs are available.
+    systemd.services.phpfpm-pixelfed.path = extraPrograms;
+
+    systemd.services.pixelfed-horizon = {
+      description = "Pixelfed task queueing via Laravel Horizon framework";
+      after = [ "network.target" "pixelfed-data-setup.service" ];
+      requires = [ "pixelfed-data-setup.service" ]
+        ++ (lib.optional cfg.database.createLocally dbService)
+        ++ (lib.optional cfg.redis.createLocally redisService);
+      wantedBy = [ "multi-user.target" ];
+      # Ensure image optimizations programs are available.
+      path = extraPrograms;
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pixelfed-manage}/bin/pixelfed-manage horizon";
+        StateDirectory =
+          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
+        User = user;
+        Group = group;
+        Restart = "on-failure";
+      };
+    };
+
+    systemd.timers.pixelfed-cron = {
+      description = "Pixelfed periodic tasks timer";
+      after = [ "pixelfed-data-setup.service" ];
+      requires = [ "phpfpm-pixelfed.service" ];
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        OnBootSec = cfg.schedulerInterval;
+        OnUnitActiveSec = cfg.schedulerInterval;
+      };
+    };
+
+    systemd.services.pixelfed-cron = {
+      description = "Pixelfed periodic tasks";
+      # Ensure image optimizations programs are available.
+      path = extraPrograms;
+
+      serviceConfig = {
+        ExecStart = "${pixelfed-manage}/bin/pixelfed-manage schedule:run";
+        User = user;
+        Group = group;
+        StateDirectory = cfg.dataDir;
+      };
+    };
+
+    systemd.services.pixelfed-data-setup = {
+      description =
+        "Pixelfed setup: migrations, environment file update, cache reload, data changes";
+      wantedBy = [ "multi-user.target" ];
+      after = lib.optional cfg.database.createLocally dbService;
+      requires = lib.optional cfg.database.createLocally dbService;
+      path = with pkgs; [ bash pixelfed-manage rsync ] ++ extraPrograms;
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        Group = group;
+        StateDirectory =
+          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
+        LoadCredential = "env-secrets:${cfg.secretFile}";
+        UMask = "077";
+      };
+
+      script = ''
+        # Concatenate non-secret .env and secret .env
+        rm -f ${cfg.dataDir}/.env
+        cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env
+        echo -e '\n' >> ${cfg.dataDir}/.env
+        cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
+
+        # Link the static storage (package provided) to the runtime storage
+        # Necessary for cities.json and static images.
+        mkdir -p ${cfg.dataDir}/storage
+        rsync -av --no-perms ${pixelfed}/storage-static/ ${cfg.dataDir}/storage
+        chmod -R +w ${cfg.dataDir}/storage
+
+        # Link the app.php in the runtime folder.
+        # We cannot link the cache folder only because bootstrap folder needs to be writeable.
+        ln -sf ${pixelfed}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php
+
+        # https://laravel.com/docs/10.x/filesystem#the-public-disk
+        # Creating the public/storage → storage/app/public link
+        # is unnecessary as it's part of the installPhase of pixelfed.
+
+        # Install Horizon
+        # FIXME: require write access to public/ — should be done as part of install — pixelfed-manage horizon:publish
+
+        # Before running any PHP program, cleanup the bootstrap.
+        # It's necessary if you upgrade the application otherwise you might
+        # try to import non-existent modules.
+        rm -rf ${cfg.runtimeDir}/bootstrap/*
+
+        # Perform the first migration.
+        [[ ! -f ${cfg.dataDir}/.initial-migration ]] && pixelfed-manage migrate --force && touch ${cfg.dataDir}/.initial-migration
+
+        ${lib.optionalString cfg.database.automaticMigrations ''
+          # Force migrate the database.
+          pixelfed-manage migrate --force
+        ''}
+
+        # Import location data
+        pixelfed-manage import:cities
+
+        ${lib.optionalString cfg.settings.ACTIVITY_PUB ''
+          # ActivityPub federation bookkeeping
+          [[ ! -f ${cfg.dataDir}/.instance-actor-created ]] && pixelfed-manage instance:actor && touch ${cfg.dataDir}/.instance-actor-created
+        ''}
+
+        ${lib.optionalString cfg.settings.OAUTH_ENABLED ''
+          # Generate Passport encryption keys
+          [[ ! -f ${cfg.dataDir}/.passport-keys-generated ]] && pixelfed-manage passport:keys && touch ${cfg.dataDir}/.passport-keys-generated
+        ''}
+
+        pixelfed-manage route:cache
+        pixelfed-manage view:cache
+        pixelfed-manage config:cache
+      '';
+    };
+
+    systemd.tmpfiles.rules = [
+      # Cache must live across multiple systemd units runtimes.
+      "d ${cfg.runtimeDir}/                         0700 ${user} ${group} - -"
+      "d ${cfg.runtimeDir}/cache                    0700 ${user} ${group} - -"
+    ];
+
+    # Enable NGINX to access our phpfpm-socket.
+    users.users."${config.services.nginx.group}".extraGroups = [ cfg.group ];
+    services.nginx = mkIf (cfg.nginx != null) {
+      enable = true;
+      virtualHosts."${cfg.domain}" = mkMerge [
+        cfg.nginx
+        {
+          root = lib.mkForce "${pixelfed}/public/";
+          locations."/".tryFiles = "$uri $uri/ /index.php?$query_string";
+          locations."/favicon.ico".extraConfig = ''
+            access_log off; log_not_found off;
+          '';
+          locations."/robots.txt".extraConfig = ''
+            access_log off; log_not_found off;
+          '';
+          locations."~ \\.php$".extraConfig = ''
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+            fastcgi_pass unix:${config.services.phpfpm.pools.pixelfed.socket};
+            fastcgi_index index.php;
+          '';
+          locations."~ /\\.(?!well-known).*".extraConfig = ''
+            deny all;
+          '';
+          extraConfig = ''
+            add_header X-Frame-Options "SAMEORIGIN";
+            add_header X-XSS-Protection "1; mode=block";
+            add_header X-Content-Type-Options "nosniff";
+            index index.html index.htm index.php;
+            error_page 404 /index.php;
+            client_max_body_size ${toString cfg.maxUploadSize};
+          '';
+        }
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix b/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix
index acd9292ceb45..5ebee48c3e0b 100644
--- a/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.plantuml-server = {
-      enable = mkEnableOption "PlantUML server";
+      enable = mkEnableOption (lib.mdDoc "PlantUML server");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.md b/nixpkgs/nixos/modules/services/web-apps/plausible.md
new file mode 100644
index 000000000000..1328ce69441a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/plausible.md
@@ -0,0 +1,35 @@
+# Plausible {#module-services-plausible}
+
+[Plausible](https://plausible.io/) is a privacy-friendly alternative to
+Google analytics.
+
+## Basic Usage {#module-services-plausible-basic-usage}
+
+At first, a secret key is needed to be generated. This can be done with e.g.
+```ShellSession
+$ openssl rand -base64 64
+```
+
+After that, `plausible` can be deployed like this:
+```
+{
+  services.plausible = {
+    enable = true;
+    adminUser = {
+      # activate is used to skip the email verification of the admin-user that's
+      # automatically created by plausible. This is only supported if
+      # postgresql is configured by the module. This is done by default, but
+      # can be turned off with services.plausible.database.postgres.setup.
+      activate = true;
+      email = "admin@localhost";
+      passwordFile = "/run/secrets/plausible-admin-pwd";
+    };
+    server = {
+      baseUrl = "http://analytics.example.org";
+      # secretKeybaseFile is a path to the file which contains the secret generated
+      # with openssl as described above.
+      secretKeybaseFile = "/run/secrets/plausible-secret-key-base";
+    };
+  };
+}
+```
diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.nix b/nixpkgs/nixos/modules/services/web-apps/plausible.nix
index 6f098134c922..893dfa10acbc 100644
--- a/nixpkgs/nixos/modules/services/web-apps/plausible.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/plausible.nix
@@ -7,7 +7,9 @@ let
 
 in {
   options.services.plausible = {
-    enable = mkEnableOption "plausible";
+    enable = mkEnableOption (lib.mdDoc "plausible");
+
+    package = mkPackageOptionMD pkgs "plausible" { };
 
     releaseCookiePath = mkOption {
       type = with types; either str path;
@@ -40,22 +42,22 @@ in {
         '';
       };
 
-      activate = mkEnableOption "activating the freshly created admin-user";
+      activate = mkEnableOption (lib.mdDoc "activating the freshly created admin-user");
     };
 
     database = {
       clickhouse = {
-        setup = mkEnableOption "creating a clickhouse instance" // { default = true; };
+        setup = mkEnableOption (lib.mdDoc "creating a clickhouse instance") // { default = true; };
         url = mkOption {
           default = "http://localhost:8123/default";
           type = types.str;
-          description = ''
-            The URL to be used to connect to <package>clickhouse</package>.
+          description = lib.mdDoc ''
+            The URL to be used to connect to `clickhouse`.
           '';
         };
       };
       postgres = {
-        setup = mkEnableOption "creating a postgresql instance" // { default = true; };
+        setup = mkEnableOption (lib.mdDoc "creating a postgresql instance") // { default = true; };
         dbname = mkOption {
           default = "plausible";
           type = types.str;
@@ -66,8 +68,8 @@ in {
         socket = mkOption {
           default = "/run/postgresql";
           type = types.str;
-          description = ''
-            Path to the UNIX domain-socket to communicate with <package>postgres</package>.
+          description = lib.mdDoc ''
+            Path to the UNIX domain-socket to communicate with `postgres`.
           '';
         };
       };
@@ -148,7 +150,7 @@ in {
             The path to the file with the password in case SMTP auth is enabled.
           '';
         };
-        enableSSL = mkEnableOption "SSL when connecting to the SMTP server";
+        enableSSL = mkEnableOption (lib.mdDoc "SSL when connecting to the SMTP server");
         retries = mkOption {
           type = types.ints.unsigned;
           default = 2;
@@ -180,15 +182,19 @@ in {
 
     services.epmd.enable = true;
 
-    environment.systemPackages = [ pkgs.plausible ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services = mkMerge [
       {
         plausible = {
-          inherit (pkgs.plausible.meta) description;
+          inherit (cfg.package.meta) description;
           documentation = [ "https://plausible.io/docs/self-hosting" ];
           wantedBy = [ "multi-user.target" ];
-          after = optionals cfg.database.postgres.setup [ "postgresql.service" "plausible-postgres.service" ];
+          after = optional cfg.database.clickhouse.setup "clickhouse.service"
+          ++ 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"
@@ -229,7 +235,7 @@ in {
             SMTP_USER_NAME = cfg.mail.smtp.user;
           });
 
-          path = [ pkgs.plausible ]
+          path = [ cfg.package ]
             ++ optional cfg.database.postgres.setup config.services.postgresql.package;
           script = ''
             export CONFIG_DIR=$CREDENTIALS_DIRECTORY
@@ -237,10 +243,10 @@ in {
             export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )"
 
             # setup
-            ${pkgs.plausible}/createdb.sh
-            ${pkgs.plausible}/migrate.sh
+            ${cfg.package}/createdb.sh
+            ${cfg.package}/migrate.sh
             ${optionalString cfg.adminUser.activate ''
-              if ! ${pkgs.plausible}/init-admin.sh | grep 'already exists'; then
+              if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then
                 psql -d plausible <<< "UPDATE users SET email_verified=true;"
               fi
             ''}
@@ -288,5 +294,5 @@ in {
   };
 
   meta.maintainers = with maintainers; [ ma27 ];
-  meta.doc = ./plausible.xml;
+  meta.doc = ./plausible.md;
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.xml b/nixpkgs/nixos/modules/services/web-apps/plausible.xml
deleted file mode 100644
index 92a571b9fbdb..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/plausible.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-services-plausible">
- <title>Plausible</title>
- <para>
-  <link xlink:href="https://plausible.io/">Plausible</link> is a privacy-friendly alternative to
-  Google analytics.
- </para>
- <section xml:id="module-services-plausible-basic-usage">
-  <title>Basic Usage</title>
-  <para>
-   At first, a secret key is needed to be generated. This can be done with e.g.
-   <screen><prompt>$ </prompt>openssl rand -base64 64</screen>
-  </para>
-  <para>
-   After that, <package>plausible</package> can be deployed like this:
-<programlisting>{
-  services.plausible = {
-    <link linkend="opt-services.plausible.enable">enable</link> = true;
-    adminUser = {
-      <link linkend="opt-services.plausible.adminUser.activate">activate</link> = true; <co xml:id='ex-plausible-cfg-activate' />
-      <link linkend="opt-services.plausible.adminUser.email">email</link> = "admin@localhost";
-      <link linkend="opt-services.plausible.adminUser.passwordFile">passwordFile</link> = "/run/secrets/plausible-admin-pwd";
-    };
-    server = {
-      <link linkend="opt-services.plausible.server.baseUrl">baseUrl</link> = "http://analytics.example.org";
-      <link linkend="opt-services.plausible.server.secretKeybaseFile">secretKeybaseFile</link> = "/run/secrets/plausible-secret-key-base"; <co xml:id='ex-plausible-cfg-secretbase' />
-    };
-  };
-}</programlisting>
-   <calloutlist>
-    <callout arearefs='ex-plausible-cfg-activate'>
-     <para>
-      <varname>activate</varname> is used to skip the email verification of the admin-user that's
-      automatically created by <package>plausible</package>. This is only supported if
-      <package>postgresql</package> is configured by the module. This is done by default, but
-      can be turned off with <xref linkend="opt-services.plausible.database.postgres.setup" />.
-     </para>
-    </callout>
-    <callout arearefs='ex-plausible-cfg-secretbase'>
-     <para>
-      <varname>secretKeybaseFile</varname> is a path to the file which contains the secret generated
-      with <package>openssl</package> as described above.
-     </para>
-    </callout>
-   </calloutlist>
-  </para>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix b/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix
index c2d65f59e4dc..7b6fb06e3565 100644
--- a/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix
@@ -19,7 +19,7 @@ let
 in
 {
   options.services.powerdns-admin = {
-    enable = mkEnableOption "the PowerDNS web interface";
+    enable = mkEnableOption (lib.mdDoc "the PowerDNS web interface");
 
     extraArgs = mkOption {
       type = types.listOf types.str;
@@ -78,7 +78,8 @@ in
       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";
+        # Set environment variables only for starting flask database upgrade
+        ExecStartPre = "${pkgs.coreutils}/bin/env FLASK_APP=${pkgs.powerdns-admin}/share/powerdnsadmin/__init__.py SESSION_TYPE= ${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";
diff --git a/nixpkgs/nixos/modules/services/web-apps/prosody-filer.nix b/nixpkgs/nixos/modules/services/web-apps/prosody-filer.nix
index 1d40809c420c..84953546d8e0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/prosody-filer.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/prosody-filer.nix
@@ -11,7 +11,7 @@ in {
 
   options = {
     services.prosody-filer = {
-      enable = mkEnableOption "Prosody Filer XMPP upload file server";
+      enable = mkEnableOption (lib.mdDoc "Prosody Filer XMPP upload file server");
 
       settings = mkOption {
         description = lib.mdDoc ''
@@ -79,7 +79,7 @@ in {
         LockPersonality = true;
         RemoveIPC = true;
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/web-apps/restya-board.nix b/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
index ae80a7866a16..4b32f06826e2 100644
--- a/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
@@ -25,7 +25,7 @@ in
 
     services.restya-board = {
 
-      enable = mkEnableOption "restya-board";
+      enable = mkEnableOption (lib.mdDoc "restya-board");
 
       dataDir = mkOption {
         type = types.path;
@@ -69,7 +69,7 @@ in
         };
 
         listenPort = mkOption {
-          type = types.int;
+          type = types.port;
           default = 3000;
           description = lib.mdDoc ''
             Listen port for the virtualhost to use.
@@ -132,7 +132,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 25;
           description = lib.mdDoc ''
             Port used to connect to SMTP server.
diff --git a/nixpkgs/nixos/modules/services/web-apps/rss-bridge.nix b/nixpkgs/nixos/modules/services/web-apps/rss-bridge.nix
index b1a3907d1964..1a710f4a6a67 100644
--- a/nixpkgs/nixos/modules/services/web-apps/rss-bridge.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/rss-bridge.nix
@@ -11,7 +11,7 @@ in
 {
   options = {
     services.rss-bridge = {
-      enable = mkEnableOption "rss-bridge";
+      enable = mkEnableOption (lib.mdDoc "rss-bridge");
 
       user = mkOption {
         type = types.str;
@@ -66,10 +66,10 @@ in
             "Twitter"
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of bridges to be whitelisted.
           If the list is empty, rss-bridge will use whitelist.default.txt.
-          Use <literal>[ "*" ]</literal> to whitelist all.
+          Use `[ "*" ]` to whitelist all.
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/web-apps/selfoss.nix b/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
index 016e053c802b..8debd4904e88 100644
--- a/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
@@ -30,7 +30,7 @@ in
   {
     options = {
       services.selfoss = {
-        enable = mkEnableOption "selfoss";
+        enable = mkEnableOption (lib.mdDoc "selfoss");
 
         user = mkOption {
           type = types.str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/sftpgo.nix b/nixpkgs/nixos/modules/services/web-apps/sftpgo.nix
new file mode 100644
index 000000000000..846478ecbd6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/sftpgo.nix
@@ -0,0 +1,375 @@
+{ options, config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sftpgo;
+  defaultUser = "sftpgo";
+  settingsFormat = pkgs.formats.json {};
+  configFile = settingsFormat.generate "sftpgo.json" cfg.settings;
+  hasPrivilegedPorts = any (port: port > 0 && port < 1024) (
+    catAttrs "port" (cfg.settings.httpd.bindings
+      ++ cfg.settings.ftpd.bindings
+      ++ cfg.settings.sftpd.bindings
+      ++ cfg.settings.webdavd.bindings
+    )
+  );
+in
+{
+  options.services.sftpgo = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc "sftpgo";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.sftpgo;
+      defaultText = literalExpression "pkgs.sftpgo";
+      description = mdDoc ''
+        Which SFTPGo package to use.
+      '';
+    };
+
+    extraArgs = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = mdDoc ''
+        Additional command line arguments to pass to the sftpgo daemon.
+      '';
+      example = [ "--log-level" "info" ];
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/sftpgo";
+      description = mdDoc ''
+        The directory where SFTPGo stores its data files.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = mdDoc ''
+        User account name under which SFTPGo runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = mdDoc ''
+        Group name under which SFTPGo runs.
+      '';
+    };
+
+    loadDataFile = mkOption {
+      default = null;
+      type = with types; nullOr path;
+      description = mdDoc ''
+        Path to a json file containing users and folders to load (or update) on startup.
+        Check the [documentation](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
+        for the `--loaddata-from` command line argument for more info.
+      '';
+    };
+
+    settings = mkOption {
+      default = {};
+      description = mdDoc ''
+        The primary sftpgo configuration. See the
+        [configuration reference](https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md)
+        for possible values.
+      '';
+      type = with types; submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          httpd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for httpd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 8080;
+                  description = mdDoc ''
+                    The port for serving HTTP(S) requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+
+                enable_web_admin = mkOption {
+                  type = types.bool;
+                  default = true;
+                  description = mdDoc ''
+                    Enable the built-in web admin for this interface binding.
+                  '';
+                };
+
+                enable_web_client = mkOption {
+                  type = types.bool;
+                  default = true;
+                  description = mdDoc ''
+                    Enable the built-in web client for this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          ftpd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for ftpd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 0;
+                  description = mdDoc ''
+                    The port for serving FTP requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          sftpd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for sftpd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 0;
+                  description = mdDoc ''
+                    The port for serving SFTP requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          webdavd.bindings = mkOption {
+            default = [];
+            description = mdDoc ''
+              Configure listen addresses and ports for webdavd.
+            '';
+            type = types.listOf (types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    Network listen address. Leave blank to listen on all available network interfaces.
+                    On *NIX you can specify an absolute path to listen on a Unix-domain socket.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 0;
+                  description = mdDoc ''
+                    The port for serving WebDAV requests.
+
+                    Setting the port to `0` disables listening on this interface binding.
+                  '';
+                };
+              };
+            });
+          };
+
+          smtp = mkOption {
+            default = {};
+            description = mdDoc ''
+              SMTP configuration section.
+            '';
+            type = types.submodule {
+              freeformType = settingsFormat.type;
+              options = {
+                host = mkOption {
+                  type = types.str;
+                  default = "";
+                  description = mdDoc ''
+                    Location of SMTP email server. Leave empty to disable email sending capabilities.
+                  '';
+                };
+
+                port = mkOption {
+                  type = types.port;
+                  default = 465;
+                  description = mdDoc "Port of the SMTP Server.";
+                };
+
+                encryption = mkOption {
+                  type = types.enum [ 0 1 2 ];
+                  default = 1;
+                  description = mdDoc ''
+                    Encryption scheme:
+                    - `0`: No encryption
+                    - `1`: TLS
+                    - `2`: STARTTLS
+                  '';
+                };
+
+                auth_type = mkOption {
+                  type = types.enum [ 0 1 2 ];
+                  default = 0;
+                  description = mdDoc ''
+                    - `0`: Plain
+                    - `1`: Login
+                    - `2`: CRAM-MD5
+                  '';
+                };
+
+                user = mkOption {
+                  type = types.str;
+                  default = "sftpgo";
+                  description = mdDoc "SMTP username.";
+                };
+
+                from = mkOption {
+                  type = types.str;
+                  default = "SFTPGo <sftpgo@example.com>";
+                  description = mdDoc ''
+                    From address.
+                  '';
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.sftpgo.settings = (mapAttrs (name: mkDefault) {
+      ftpd.bindings = [{ port = 0; }];
+      httpd.bindings = [{ port = 0; }];
+      sftpd.bindings = [{ port = 0; }];
+      webdavd.bindings = [{ port = 0; }];
+      httpd.openapi_path = "${cfg.package}/share/sftpgo/openapi";
+      httpd.templates_path = "${cfg.package}/share/sftpgo/templates";
+      httpd.static_files_path = "${cfg.package}/share/sftpgo/static";
+      smtp.templates_path = "${cfg.package}/share/sftpgo/templates";
+    });
+
+    users = optionalAttrs (cfg.user == defaultUser) {
+      users = {
+        ${defaultUser} = {
+          description = "SFTPGo system user";
+          isSystemUser = true;
+          group = defaultUser;
+          home = cfg.dataDir;
+        };
+      };
+
+      groups = {
+        ${defaultUser} = {
+          members = [ defaultUser ];
+        };
+      };
+    };
+
+    systemd.services.sftpgo = {
+      description = "SFTPGo daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        SFTPGO_CONFIG_FILE = mkDefault configFile;
+        SFTPGO_LOG_FILE_PATH = mkDefault ""; # log to journal
+        SFTPGO_LOADDATA_FROM = mkIf (cfg.loadDataFile != null) cfg.loadDataFile;
+      };
+
+      serviceConfig = mkMerge [
+        ({
+          Type = "simple";
+          User = cfg.user;
+          Group = cfg.group;
+          WorkingDirectory = cfg.dataDir;
+          ReadWritePaths = [ cfg.dataDir ];
+          LimitNOFILE = 8192; # taken from upstream
+          KillMode = "mixed";
+          ExecStart = "${cfg.package}/bin/sftpgo serve ${utils.escapeSystemdExecArgs cfg.extraArgs}";
+          ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID";
+
+          # Service hardening
+          CapabilityBoundingSet = [ (optionalString hasPrivilegedPorts "CAP_NET_BIND_SERVICE") ];
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateTmp = 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_INET AF_INET6 AF_UNIX";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
+          UMask = "0077";
+        })
+        (mkIf hasPrivilegedPorts {
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        })
+        (mkIf (cfg.dataDir == options.services.sftpgo.dataDir.default) {
+          StateDirectory = baseNameOf cfg.dataDir;
+        })
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/shiori.nix b/nixpkgs/nixos/modules/services/web-apps/shiori.nix
index 494f8587306f..f0505e052e1c 100644
--- a/nixpkgs/nixos/modules/services/web-apps/shiori.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/shiori.nix
@@ -6,7 +6,7 @@ let
 in {
   options = {
     services.shiori = {
-      enable = mkEnableOption "Shiori simple bookmarks manager";
+      enable = mkEnableOption (lib.mdDoc "Shiori simple bookmarks manager");
 
       package = mkOption {
         type = types.package;
@@ -86,7 +86,7 @@ in {
         SystemCallErrorNumber = "EPERM";
         SystemCallFilter = [
           "@system-service"
-          "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@resources" "~@setuid"
+          "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
         ];
       };
     };
diff --git a/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix b/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix
index c0d29b048a33..93b0aafab64b 100644
--- a/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix
@@ -28,7 +28,7 @@ let
 in {
   options.services.snipe-it = {
 
-    enable = mkEnableOption "A free open source IT asset/license management system";
+    enable = mkEnableOption (lib.mdDoc "A free open source IT asset/license management system");
 
     user = mkOption {
       default = "snipeit";
@@ -54,11 +54,8 @@ in {
 
     hostName = lib.mkOption {
       type = lib.types.str;
-      default = if config.networking.domain != null then
-                  config.networking.fqdn
-                else
-                  config.networking.hostName;
-      defaultText = lib.literalExpression "config.networking.fqdn";
+      default = config.networking.fqdnOrHostName;
+      defaultText = lib.literalExpression "config.networking.fqdnOrHostName";
       example = "snipe-it.example.com";
       description = lib.mdDoc ''
         The hostname to serve Snipe-IT on.
@@ -250,7 +247,7 @@ in {
                 options = {
                   _secret = mkOption {
                     type = nullOr (oneOf [ str path ]);
-                    description = ''
+                    description = lib.mdDoc ''
                       The path to a file containing the value the
                       option should be set to in the final
                       configuration file.
@@ -384,7 +381,7 @@ in {
     };
 
     systemd.services.snipe-it-setup = {
-      description = "Preperation tasks for snipe-it";
+      description = "Preparation tasks for snipe-it";
       before = [ "phpfpm-snipe-it.service" ];
       after = optional db.createLocally "mysql.service";
       wantedBy = [ "multi-user.target" ];
@@ -394,7 +391,7 @@ in {
         User = user;
         WorkingDirectory = snipe-it;
         RuntimeDirectory = "snipe-it/cache";
-        RuntimeDirectoryMode = 0700;
+        RuntimeDirectoryMode = "0700";
       };
       path = [ pkgs.replace-secret ];
       script =
@@ -454,25 +451,44 @@ in {
 
           # migrate db
           ${pkgs.php}/bin/php artisan migrate --force
+
+          # A placeholder file for invalid barcodes
+          invalid_barcode_location="${cfg.dataDir}/public/uploads/barcodes/invalid_barcode.gif"
+          if [ ! -e "$invalid_barcode_location" ]; then
+              cp ${snipe-it}/share/snipe-it/invalid_barcode.gif "$invalid_barcode_location"
+          fi
         '';
     };
 
     systemd.tmpfiles.rules = [
-      "d ${cfg.dataDir}                            0710 ${user} ${group} - -"
-      "d ${cfg.dataDir}/bootstrap                  0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/bootstrap/cache            0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public                     0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/public/uploads             0750 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage                    0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/app                0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/fonts              0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework          0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/cache    0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/framework/views    0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/logs               0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/uploads            0700 ${user} ${group} - -"
-      "d ${cfg.dataDir}/storage/private_uploads    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}                              0710 ${user} ${group} - -"
+      "d ${cfg.dataDir}/bootstrap                    0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/bootstrap/cache              0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public                       0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads               0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/accessories   0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/assets        0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/avatars       0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/barcodes      0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/categories    0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/companies     0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/components    0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/consumables   0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/departments   0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/locations     0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/manufacturers 0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/models        0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads/suppliers     0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage                      0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/app                  0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/fonts                0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework            0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/cache      0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/sessions   0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/views      0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/logs                 0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/uploads              0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/private_uploads      0700 ${user} ${group} - -"
     ];
 
     users = {
diff --git a/nixpkgs/nixos/modules/services/web-apps/sogo.nix b/nixpkgs/nixos/modules/services/web-apps/sogo.nix
index a134282de83a..5e5d9472829d 100644
--- a/nixpkgs/nixos/modules/services/web-apps/sogo.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/sogo.nix
@@ -18,7 +18,7 @@
 
 in {
   options.services.sogo = with types; {
-    enable = mkEnableOption "SOGo groupware";
+    enable = mkEnableOption (lib.mdDoc "SOGo groupware");
 
     vhostName = mkOption {
       description = lib.mdDoc "Name of the nginx vhost";
@@ -49,7 +49,7 @@ in {
         Replacement-filepath mapping for sogo.conf.
         Every key is replaced with the contents of the file specified as value.
 
-        In the example, every occurence of LDAP_BINDPW will be replaced with the text of the
+        In the example, every occurrence of LDAP_BINDPW will be replaced with the text of the
         specified file.
       '';
       type = attrsOf str;
diff --git a/nixpkgs/nixos/modules/services/web-apps/trilium.nix b/nixpkgs/nixos/modules/services/web-apps/trilium.nix
index bb1061cf278e..a91d64f620b6 100644
--- a/nixpkgs/nixos/modules/services/web-apps/trilium.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/trilium.nix
@@ -24,7 +24,7 @@ in
 {
 
   options.services.trilium-server = with lib; {
-    enable = mkEnableOption "trilium-server";
+    enable = mkEnableOption (lib.mdDoc "trilium-server");
 
     dataDir = mkOption {
       type = types.str;
@@ -67,7 +67,7 @@ in
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8080;
       description = lib.mdDoc ''
         The port number to bind to.
diff --git a/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
index f105b0aa3f72..3102e6a46953 100644
--- a/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
@@ -121,7 +121,7 @@ let
 
     services.tt-rss = {
 
-      enable = mkEnableOption "tt-rss";
+      enable = mkEnableOption (lib.mdDoc "tt-rss");
 
       root = mkOption {
         type = types.path;
@@ -208,7 +208,7 @@ let
         };
 
         port = mkOption {
-          type = types.nullOr types.int;
+          type = types.nullOr types.port;
           default = null;
           description = lib.mdDoc ''
             The database's port. If not set, the default ports will be provided (5432
@@ -534,7 +534,7 @@ let
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
       ${poolName} = {
         inherit (cfg) user;
-        phpPackage = pkgs.php80;
+        phpPackage = pkgs.php81;
         settings = mapAttrs (name: mkDefault) {
           "listen.owner" = "nginx";
           "listen.group" = "nginx";
diff --git a/nixpkgs/nixos/modules/services/web-apps/vikunja.nix b/nixpkgs/nixos/modules/services/web-apps/vikunja.nix
index 7db610159809..8bc8e8c29259 100644
--- a/nixpkgs/nixos/modules/services/web-apps/vikunja.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/vikunja.nix
@@ -10,7 +10,7 @@ let
   usePostgresql = cfg.database.type == "postgres";
 in {
   options.services.vikunja = with lib; {
-    enable = mkEnableOption "vikunja service";
+    enable = mkEnableOption (lib.mdDoc "vikunja service");
     package-api = mkOption {
       default = pkgs.vikunja-api;
       type = types.package;
@@ -56,6 +56,11 @@ in {
       type = types.str;
       description = lib.mdDoc "The Hostname under which the frontend is running.";
     };
+    port = mkOption {
+      type = types.port;
+      default = 3456;
+      description = lib.mdDoc "The TCP port exposed by the API.";
+    };
 
     settings = mkOption {
       type = format.type;
@@ -101,6 +106,7 @@ in {
         inherit (cfg.database) type host user database path;
       };
       service = {
+        interface = ":${toString cfg.port}";
         frontendurl = "${cfg.frontendScheme}://${cfg.frontendHostname}/";
       };
       files = {
@@ -132,7 +138,7 @@ in {
           tryFiles = "try_files $uri $uri/ /";
         };
         "~* ^/(api|dav|\\.well-known)/" = {
-          proxyPass = "http://localhost:3456";
+          proxyPass = "http://localhost:${toString cfg.port}";
           extraConfig = ''
             client_max_body_size 20M;
           '';
diff --git a/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix b/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix
index c4dee3c6eec7..b673a7c1179e 100644
--- a/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix
@@ -7,7 +7,7 @@ let
 in {
   options = {
     services.whitebophir = {
-      enable = mkEnableOption "whitebophir, an online collaborative whiteboard server (persistent state will be maintained under <filename>/var/lib/whitebophir</filename>)";
+      enable = mkEnableOption (lib.mdDoc "whitebophir, an online collaborative whiteboard server (persistent state will be maintained under {file}`/var/lib/whitebophir`)");
 
       package = mkOption {
         default = pkgs.whitebophir;
diff --git a/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix b/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix
index 5dc0bb73259b..631740f51ce3 100644
--- a/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix
@@ -10,14 +10,14 @@ let
   configFile = format.generate "wiki-js.yml" cfg.settings;
 in {
   options.services.wiki-js = {
-    enable = mkEnableOption "wiki-js";
+    enable = mkEnableOption (lib.mdDoc "wiki-js");
 
     environmentFile = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = "/root/wiki-js.env";
       description = lib.mdDoc ''
-        Environment fiel to inject e.g. secrets into the configuration.
+        Environment file to inject e.g. secrets into the configuration.
       '';
     };
 
@@ -54,10 +54,10 @@ in {
             type = mkOption {
               default = "postgres";
               type = types.enum [ "postgres" "mysql" "mariadb" "mssql" ];
-              description = ''
-                Database driver to use for persistence. Please note that <literal>sqlite</literal>
+              description = lib.mdDoc ''
+                Database driver to use for persistence. Please note that `sqlite`
                 is currently not supported as the build process for it is currently not implemented
-                in <package>pkgs.wiki-js</package> and it's not recommended by upstream for
+                in `pkgs.wiki-js` and it's not recommended by upstream for
                 production use.
               '';
             };
@@ -85,25 +85,23 @@ in {
             '';
           };
 
-          offline = mkEnableOption "offline mode" // {
-            description = ''
+          offline = mkEnableOption (lib.mdDoc "offline mode") // {
+            description = lib.mdDoc ''
               Disable latest file updates and enable
-              <link xlink:href="https://docs.requarks.io/install/sideload">sideloading</link>.
+              [sideloading](https://docs.requarks.io/install/sideload).
             '';
           };
         };
       };
-      description = ''
-        Settings to configure <package>wiki-js</package>. This directly
-        corresponds to <link xlink:href="https://docs.requarks.io/install/config">the upstream configuration options</link>.
+      description = lib.mdDoc ''
+        Settings to configure `wiki-js`. This directly
+        corresponds to [the upstream configuration options](https://docs.requarks.io/install/config).
 
         Secrets can be injected via the environment by
-        <itemizedlist>
-          <listitem><para>specifying <xref linkend="opt-services.wiki-js.environmentFile"/>
-          to contain secrets</para></listitem>
-          <listitem><para>and setting sensitive values to <literal>$(ENVIRONMENT_VAR)</literal>
-          with this value defined in the environment-file.</para></listitem>
-        </itemizedlist>
+        - specifying [](#opt-services.wiki-js.environmentFile)
+          to contain secrets
+        - and setting sensitive values to `$(ENVIRONMENT_VAR)`
+          with this value defined in the environment-file.
       '';
     };
   };
@@ -115,7 +113,13 @@ in {
       documentation = [ "https://docs.requarks.io/" ];
       wantedBy = [ "multi-user.target" ];
 
-      path = with pkgs; [ coreutils ];
+      path = with pkgs; [
+        # Needed for git storage.
+        git
+        # Needed for git+ssh storage.
+        openssh
+      ];
+
       preStart = ''
         ln -sf ${configFile} /var/lib/${cfg.stateDirectoryName}/config.yml
         ln -sf ${pkgs.wiki-js}/server /var/lib/${cfg.stateDirectoryName}
@@ -129,7 +133,7 @@ in {
         WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
         DynamicUser = true;
         PrivateTmp = true;
-        ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.wiki-js}/server";
+        ExecStart = "${pkgs.nodejs_18}/bin/node ${pkgs.wiki-js}/server";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
index c841ded353e7..d4c987da1144 100644
--- a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
@@ -22,6 +22,7 @@ let
       ln -s ${wpConfig hostName cfg} $out/share/wordpress/wp-config.php
       # symlink uploads directory
       ln -s ${cfg.uploadsDir} $out/share/wordpress/wp-content/uploads
+      ln -s ${cfg.fontsDir} $out/share/wordpress/wp-content/fonts
 
       # https://github.com/NixOS/nixpkgs/pull/53399
       #
@@ -30,35 +31,60 @@ let
       # requests that look like: https://example.com/wp-content//nix/store/...plugin/path/some-file.js
       # Since hard linking directories is not allowed, copying is the next best thing.
 
-      # copy additional plugin(s) and theme(s)
-      ${concatMapStringsSep "\n" (theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${theme.name}") cfg.themes}
-      ${concatMapStringsSep "\n" (plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${plugin.name}") cfg.plugins}
+      # copy additional plugin(s), theme(s) and language(s)
+      ${concatStringsSep "\n" (mapAttrsToList (name: theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${name}") cfg.themes)}
+      ${concatStringsSep "\n" (mapAttrsToList (name: plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${name}") cfg.plugins)}
+      ${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages}
     '';
   };
 
-  wpConfig = hostName: cfg: pkgs.writeText "wp-config-${hostName}.php" ''
-    <?php
-      define('DB_NAME', '${cfg.database.name}');
-      define('DB_HOST', '${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}');
-      define('DB_USER', '${cfg.database.user}');
-      ${optionalString (cfg.database.passwordFile != null) "define('DB_PASSWORD', file_get_contents('${cfg.database.passwordFile}'));"}
-      define('DB_CHARSET', 'utf8');
-      $table_prefix  = '${cfg.database.tablePrefix}';
-
-      require_once('${stateDir hostName}/secret-keys.php');
-
-      # wordpress is installed onto a read-only file system
-      define('DISALLOW_FILE_EDIT', true);
-      define('AUTOMATIC_UPDATER_DISABLED', true);
-
-      ${cfg.extraConfig}
-
-      if ( !defined('ABSPATH') )
-        define('ABSPATH', dirname(__FILE__) . '/');
+  mergeConfig = cfg: {
+    # wordpress is installed onto a read-only file system
+    DISALLOW_FILE_EDIT = true;
+    AUTOMATIC_UPDATER_DISABLED = true;
+    DB_NAME = cfg.database.name;
+    DB_HOST = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}";
+    DB_USER = cfg.database.user;
+    DB_CHARSET = "utf8";
+    # Always set DB_PASSWORD even when passwordFile is not set. This is the
+    # default Wordpress behaviour.
+    DB_PASSWORD =  if (cfg.database.passwordFile != null) then { _file = cfg.database.passwordFile; } else "";
+  } // cfg.settings;
+
+  wpConfig = hostName: cfg: let
+    conf_gen = c: mapAttrsToList (k: v: "define('${k}', ${mkPhpValue v});") cfg.mergedConfig;
+  in pkgs.writeTextFile {
+    name = "wp-config-${hostName}.php";
+    text = ''
+      <?php
+        $table_prefix  = '${cfg.database.tablePrefix}';
+
+        require_once('${stateDir hostName}/secret-keys.php');
+
+        ${cfg.extraConfig}
+        ${concatStringsSep "\n" (conf_gen cfg.mergedConfig)}
+
+        if ( !defined('ABSPATH') )
+          define('ABSPATH', dirname(__FILE__) . '/');
+
+        require_once(ABSPATH . 'wp-settings.php');
+      ?>
+    '';
+    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
+  };
 
-      require_once(ABSPATH . 'wp-settings.php');
-    ?>
-  '';
+  mkPhpValue = v: let
+    isHasAttr = s: isAttrs v && hasAttr s v;
+  in
+    if isString v then escapeShellArg v
+    # NOTE: If any value contains a , (comma) this will not get escaped
+    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
+    else if isInt v then toString v
+    else if isBool v then boolToString v
+    else if isHasAttr "_file" then "trim(file_get_contents(${lib.escapeShellArg v._file}))"
+    else if isHasAttr "_raw" then v._raw
+    else abort "The Wordpress config value ${lib.generators.toPretty {} v} can not be encoded."
+  ;
 
   secretsVars = [ "AUTH_KEY" "SECURE_AUTH_KEY" "LOGGED_IN_KEY" "NONCE_KEY" "AUTH_SALT" "SECURE_AUTH_SALT" "LOGGED_IN_SALT" "NONCE_SALT" ];
   secretsScript = hostStateDir: ''
@@ -75,7 +101,7 @@ let
     fi
   '';
 
-  siteOpts = { lib, name, ... }:
+  siteOpts = { lib, name, config, ... }:
     {
       options = {
         package = mkOption {
@@ -94,57 +120,81 @@ let
           '';
         };
 
+        fontsDir = mkOption {
+          type = types.path;
+          default = "/var/lib/wordpress/${name}/fonts";
+          description = lib.mdDoc ''
+            This directory is used to download fonts from a remote location, e.g.
+            to host google fonts locally.
+          '';
+        };
+
         plugins = mkOption {
-          type = types.listOf types.path;
-          default = [];
-          description = ''
-            List of path(s) to respective plugin(s) which are copied from the 'plugins' directory.
-            <note><para>These plugins need to be packaged before use, see example.</para></note>
+          type = with types; coercedTo
+            (listOf path)
+            (l: warn "setting this option with a list is deprecated"
+              listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l))
+            (attrsOf path);
+          default = {};
+          description = lib.mdDoc ''
+            Path(s) to respective plugin(s) which are copied from the 'plugins' directory.
+
+            ::: {.note}
+            These plugins need to be packaged before use, see example.
+            :::
           '';
           example = literalExpression ''
-            let
-              # Wordpress plugin 'embed-pdf-viewer' installation example
-              embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
-                name = "embed-pdf-viewer-plugin";
-                # Download the theme from the wordpress site
-                src = pkgs.fetchurl {
-                  url = "https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip";
-                  sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
-                };
-                # We need unzip to build this package
-                nativeBuildInputs = [ pkgs.unzip ];
-                # Installing simply means copying all files to the output directory
-                installPhase = "mkdir -p $out; cp -R * $out/";
-              };
-            # And then pass this theme to the themes list like this:
-            in [ embedPdfViewerPlugin ]
+            {
+              inherit (pkgs.wordpressPackages.plugins) embed-pdf-viewer-plugin;
+            }
           '';
         };
 
         themes = mkOption {
+          type = with types; coercedTo
+            (listOf path)
+            (l: warn "setting this option with a list is deprecated"
+              listToAttrs (map (p: nameValuePair (p.name or (throw "${p} does not have a name")) p) l))
+            (attrsOf path);
+          default = { inherit (pkgs.wordpressPackages.themes) twentytwentythree; };
+          defaultText = literalExpression "{ inherit (pkgs.wordpressPackages.themes) twentytwentythree; }";
+          description = lib.mdDoc ''
+            Path(s) to respective theme(s) which are copied from the 'theme' directory.
+
+            ::: {.note}
+            These themes need to be packaged before use, see example.
+            :::
+          '';
+          example = literalExpression ''
+            {
+              inherit (pkgs.wordpressPackages.themes) responsive-theme;
+            }
+          '';
+        };
+
+        languages = mkOption {
           type = types.listOf types.path;
           default = [];
-          description = ''
-            List of path(s) to respective theme(s) which are copied from the 'theme' directory.
-            <note><para>These themes need to be packaged before use, see example.</para></note>
+          description = lib.mdDoc ''
+            List of path(s) to respective language(s) which are copied from the 'languages' directory.
           '';
           example = literalExpression ''
-            let
-              # Let's package the responsive theme
-              responsiveTheme = pkgs.stdenv.mkDerivation {
-                name = "responsive-theme";
-                # Download the theme from the wordpress site
+            [(
+              # Let's package the German language.
+              # For other languages try to replace language and country code in the download URL with your desired one.
+              # Reference https://translate.wordpress.org for available translations and
+              # codes.
+              language-de = pkgs.stdenv.mkDerivation {
+                name = "language-de";
                 src = pkgs.fetchurl {
-                  url = "https://downloads.wordpress.org/theme/responsive.3.14.zip";
-                  sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
+                  url = "https://de.wordpress.org/wordpress-''${pkgs.wordpress.version}-de_DE.tar.gz";
+                  # Name is required to invalidate the hash when wordpress is updated
+                  name = "wordpress-''${pkgs.wordpress.version}-language-de"
+                  sha256 = "sha256-dlas0rXTSV4JAl8f/UyMbig57yURRYRhTMtJwF9g8h0=";
                 };
-                # We need unzip to build this package
-                nativeBuildInputs = [ pkgs.unzip ];
-                # Installing simply means copying all files to the output directory
-                installPhase = "mkdir -p $out; cp -R * $out/";
+                installPhase = "mkdir -p $out; cp -r ./wp-content/languages/* $out/";
               };
-            # And then pass this theme to the themes list like this:
-            in [ responsiveTheme ]
+            )];
           '';
         };
 
@@ -240,6 +290,42 @@ let
           '';
         };
 
+        settings = mkOption {
+          type = types.attrsOf types.anything;
+          default = {};
+          description = lib.mdDoc ''
+            Structural Wordpress configuration.
+            Refer to <https://developer.wordpress.org/apis/wp-config-php>
+            for details and supported values.
+          '';
+          example = literalExpression ''
+            {
+              WP_DEFAULT_THEME = "twentytwentytwo";
+              WP_SITEURL = "https://example.org";
+              WP_HOME = "https://example.org";
+              WP_DEBUG = true;
+              WP_DEBUG_DISPLAY = true;
+              WPLANG = "de_DE";
+              FORCE_SSL_ADMIN = true;
+              AUTOMATIC_UPDATER_DISABLED = true;
+            }
+          '';
+        };
+
+        mergedConfig = mkOption {
+          readOnly = true;
+          default = mergeConfig config;
+          defaultText = literalExpression ''
+            {
+              DISALLOW_FILE_EDIT = true;
+              AUTOMATIC_UPDATER_DISABLED = true;
+            }
+          '';
+          description = lib.mdDoc ''
+            Read only representation of the final configuration.
+          '';
+        };
+
         extraConfig = mkOption {
           type = types.lines;
           default = "";
@@ -247,11 +333,16 @@ let
             Any additional text to be appended to the wp-config.php
             configuration file. This is a PHP script. For configuration
             settings, see <https://codex.wordpress.org/Editing_wp-config.php>.
+
+            **Note**: Please pass structured settings via
+            `services.wordpress.sites.${name}.settings` instead.
           '';
           example = ''
-            define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
+            @ini_set( 'log_errors', 'Off' );
+            @ini_set( 'display_errors', 'On' );
           '';
         };
+
       };
 
       config.virtualHost.hostName = mkDefault name;
@@ -366,6 +457,8 @@ in
       "d '${stateDir hostName}' 0750 ${user} ${webserver.group} - -"
       "d '${cfg.uploadsDir}' 0750 ${user} ${webserver.group} - -"
       "Z '${cfg.uploadsDir}' 0750 ${user} ${webserver.group} - -"
+      "d '${cfg.fontsDir}' 0750 ${user} ${webserver.group} - -"
+      "Z '${cfg.fontsDir}' 0750 ${user} ${webserver.group} - -"
     ]) eachSite);
 
     systemd.services = mkMerge [
diff --git a/nixpkgs/nixos/modules/services/web-apps/writefreely.nix b/nixpkgs/nixos/modules/services/web-apps/writefreely.nix
new file mode 100644
index 000000000000..a7671aa717f4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/writefreely.nix
@@ -0,0 +1,484 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (builtins) toString;
+  inherit (lib) types mkIf mkOption mkDefault;
+  inherit (lib) optional optionals optionalAttrs optionalString;
+
+  inherit (pkgs) sqlite;
+
+  format = pkgs.formats.ini {
+    mkKeyValue = key: value:
+      let
+        value' = lib.optionalString (value != null)
+          (if builtins.isBool value then
+            if value == true then "true" else "false"
+          else
+            toString value);
+      in "${key} = ${value'}";
+  };
+
+  cfg = config.services.writefreely;
+
+  isSqlite = cfg.database.type == "sqlite3";
+  isMysql = cfg.database.type == "mysql";
+  isMysqlLocal = isMysql && cfg.database.createLocally == true;
+
+  hostProtocol = if cfg.acme.enable then "https" else "http";
+
+  settings = cfg.settings // {
+    app = cfg.settings.app or { } // {
+      host = cfg.settings.app.host or "${hostProtocol}://${cfg.host}";
+    };
+
+    database = if cfg.database.type == "sqlite3" then {
+      type = "sqlite3";
+      filename = cfg.settings.database.filename or "writefreely.db";
+      database = cfg.database.name;
+    } else {
+      type = "mysql";
+      username = cfg.database.user;
+      password = "#dbpass#";
+      database = cfg.database.name;
+      host = cfg.database.host;
+      port = cfg.database.port;
+      tls = cfg.database.tls;
+    };
+
+    server = cfg.settings.server or { } // {
+      bind = cfg.settings.server.bind or "localhost";
+      gopher_port = cfg.settings.server.gopher_port or 0;
+      autocert = !cfg.nginx.enable && cfg.acme.enable;
+      templates_parent_dir =
+        cfg.settings.server.templates_parent_dir or cfg.package.src;
+      static_parent_dir = cfg.settings.server.static_parent_dir or assets;
+      pages_parent_dir =
+        cfg.settings.server.pages_parent_dir or cfg.package.src;
+      keys_parent_dir = cfg.settings.server.keys_parent_dir or cfg.stateDir;
+    };
+  };
+
+  configFile = format.generate "config.ini" settings;
+
+  assets = pkgs.stdenvNoCC.mkDerivation {
+    pname = "writefreely-assets";
+
+    inherit (cfg.package) version src;
+
+    nativeBuildInputs = with pkgs.nodePackages; [ less ];
+
+    buildPhase = ''
+      mkdir -p $out
+
+      cp -r static $out/
+    '';
+
+    installPhase = ''
+      less_dir=$src/less
+      css_dir=$out/static/css
+
+      lessc $less_dir/app.less $css_dir/write.css
+      lessc $less_dir/fonts.less $css_dir/fonts.css
+      lessc $less_dir/icons.less $css_dir/icons.css
+      lessc $less_dir/prose.less $css_dir/prose.css
+    '';
+  };
+
+  withConfigFile = text: ''
+    db_pass=${
+      optionalString (cfg.database.passwordFile != null)
+      "$(head -n1 ${cfg.database.passwordFile})"
+    }
+
+    cp -f ${configFile} '${cfg.stateDir}/config.ini'
+    sed -e "s,#dbpass#,$db_pass,g" -i '${cfg.stateDir}/config.ini'
+    chmod 440 '${cfg.stateDir}/config.ini'
+
+    ${text}
+  '';
+
+  withMysql = text:
+    withConfigFile ''
+      query () {
+        local result=$(${config.services.mysql.package}/bin/mysql \
+          --user=${cfg.database.user} \
+          --password=$db_pass \
+          --database=${cfg.database.name} \
+          --silent \
+          --raw \
+          --skip-column-names \
+          --execute "$1" \
+        )
+
+        echo $result
+      }
+
+      ${text}
+    '';
+
+  withSqlite = text:
+    withConfigFile ''
+      query () {
+        local result=$(${sqlite}/bin/sqlite3 \
+          '${cfg.stateDir}/${settings.database.filename}'
+          "$1" \
+        )
+
+        echo $result
+      }
+
+      ${text}
+    '';
+in {
+  options.services.writefreely = {
+    enable =
+      lib.mkEnableOption (lib.mdDoc "Writefreely, build a digital writing community");
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.writefreely;
+      defaultText = lib.literalExpression "pkgs.writefreely";
+      description = lib.mdDoc "Writefreely package to use.";
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var/lib/writefreely";
+      description = lib.mdDoc "The state directory where keys and data are stored.";
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "writefreely";
+      description = lib.mdDoc "User under which Writefreely is ran.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "writefreely";
+      description = lib.mdDoc "Group under which Writefreely is ran.";
+    };
+
+    host = mkOption {
+      type = types.str;
+      default = "";
+      description = lib.mdDoc "The public host name to serve.";
+      example = "example.com";
+    };
+
+    settings = mkOption {
+      default = { };
+      description = lib.mdDoc ''
+        Writefreely configuration ({file}`config.ini`). Refer to
+        <https://writefreely.org/docs/latest/admin/config>
+        for details.
+      '';
+
+      type = types.submodule {
+        freeformType = format.type;
+
+        options = {
+          app = {
+            theme = mkOption {
+              type = types.str;
+              default = "write";
+              description = lib.mdDoc "The theme to apply.";
+            };
+          };
+
+          server = {
+            port = mkOption {
+              type = types.port;
+              default = if cfg.nginx.enable then 18080 else 80;
+              defaultText = "80";
+              description = lib.mdDoc "The port WriteFreely should listen on.";
+            };
+          };
+        };
+      };
+    };
+
+    database = {
+      type = mkOption {
+        type = types.enum [ "sqlite3" "mysql" ];
+        default = "sqlite3";
+        description = lib.mdDoc "The database provider to use.";
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "writefreely";
+        description = lib.mdDoc "The name of the database to store data in.";
+      };
+
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = if cfg.database.type == "mysql" then "writefreely" else null;
+        defaultText = "writefreely";
+        description = lib.mdDoc "The database user to connect as.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc "The file to load the database password from.";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "The database host to connect to.";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 3306;
+        description = lib.mdDoc "The port used when connecting to the database host.";
+      };
+
+      tls = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          lib.mdDoc "Whether or not TLS should be used for the database connection.";
+      };
+
+      migrate = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          lib.mdDoc "Whether or not to automatically run migrations on startup.";
+      };
+
+      createLocally = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          When {option}`services.writefreely.database.type` is set to
+          `"mysql"`, this option will enable the MySQL service locally.
+        '';
+      };
+    };
+
+    admin = {
+      name = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc "The name of the first admin user.";
+        default = null;
+      };
+
+      initialPasswordFile = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          Path to a file containing the initial password for the admin user.
+          If not provided, the default password will be set to `nixos`.
+        '';
+        default = pkgs.writeText "default-admin-pass" "nixos";
+        defaultText = "/nix/store/xxx-default-admin-pass";
+      };
+    };
+
+    nginx = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          lib.mdDoc "Whether or not to enable and configure nginx as a proxy for WriteFreely.";
+      };
+
+      forceSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether or not to force the use of SSL.";
+      };
+    };
+
+    acme = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          lib.mdDoc "Whether or not to automatically fetch and configure SSL certs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.host != "";
+        message = "services.writefreely.host must be set";
+      }
+      {
+        assertion = isMysqlLocal -> cfg.database.passwordFile != null;
+        message =
+          "services.writefreely.database.passwordFile must be set if services.writefreely.database.createLocally is set to true";
+      }
+      {
+        assertion = isSqlite -> !cfg.database.createLocally;
+        message =
+          "services.writefreely.database.createLocally has no use when services.writefreely.database.type is set to sqlite3";
+      }
+    ];
+
+    users = {
+      users = optionalAttrs (cfg.user == "writefreely") {
+        writefreely = {
+          group = cfg.group;
+          home = cfg.stateDir;
+          isSystemUser = true;
+        };
+      };
+
+      groups =
+        optionalAttrs (cfg.group == "writefreely") { writefreely = { }; };
+    };
+
+    systemd.tmpfiles.rules =
+      [ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" ];
+
+    systemd.services.writefreely = {
+      after = [ "network.target" ]
+        ++ optional isSqlite "writefreely-sqlite-init.service"
+        ++ optional isMysql "writefreely-mysql-init.service"
+        ++ optional isMysqlLocal "mysql.service";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.stateDir;
+        Restart = "always";
+        RestartSec = 20;
+        ExecStart =
+          "${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' serve";
+        AmbientCapabilities =
+          optionalString (settings.server.port < 1024) "cap_net_bind_service";
+      };
+
+      preStart = ''
+        if ! test -d "${cfg.stateDir}/keys"; then
+          mkdir -p ${cfg.stateDir}/keys
+
+          # Key files end up with the wrong permissions by default.
+          # We need to correct them so that Writefreely can read them.
+          chmod -R 750 "${cfg.stateDir}/keys"
+
+          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' keys generate
+        fi
+      '';
+    };
+
+    systemd.services.writefreely-sqlite-init = mkIf isSqlite {
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.stateDir;
+        ReadOnlyPaths = optional (cfg.admin.initialPasswordFile != null)
+          cfg.admin.initialPasswordFile;
+      };
+
+      script = let
+        migrateDatabase = optionalString cfg.database.migrate ''
+          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
+        '';
+
+        createAdmin = optionalString (cfg.admin.name != null) ''
+          if [[ $(query "SELECT COUNT(*) FROM users") == 0 ]]; then
+            admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
+
+            ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
+          fi
+        '';
+      in withSqlite ''
+        if ! test -f '${settings.database.filename}'; then
+          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
+        fi
+
+        ${migrateDatabase}
+
+        ${createAdmin}
+      '';
+    };
+
+    systemd.services.writefreely-mysql-init = mkIf isMysql {
+      wantedBy = [ "multi-user.target" ];
+      after = optional isMysqlLocal "mysql.service";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.stateDir;
+        ReadOnlyPaths = optional isMysqlLocal cfg.database.passwordFile
+          ++ optional (cfg.admin.initialPasswordFile != null)
+          cfg.admin.initialPasswordFile;
+      };
+
+      script = let
+        updateUser = optionalString isMysqlLocal ''
+          # WriteFreely currently *requires* a password for authentication, so we
+          # need to update the user in MySQL accordingly. By default MySQL users
+          # authenticate with auth_socket or unix_socket.
+          # See: https://github.com/writefreely/writefreely/issues/568
+          ${config.services.mysql.package}/bin/mysql --skip-column-names --execute "ALTER USER '${cfg.database.user}'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$db_pass'); FLUSH PRIVILEGES;"
+        '';
+
+        migrateDatabase = optionalString cfg.database.migrate ''
+          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
+        '';
+
+        createAdmin = optionalString (cfg.admin.name != null) ''
+          if [[ $(query 'SELECT COUNT(*) FROM users') == 0 ]]; then
+            admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
+            ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
+          fi
+        '';
+      in withMysql ''
+        ${updateUser}
+
+        if [[ $(query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${cfg.database.name}'") == 0 ]]; then
+          ${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
+        fi
+
+        ${migrateDatabase}
+
+        ${createAdmin}
+      '';
+    };
+
+    services.mysql = mkIf isMysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = {
+          "${cfg.database.name}.*" = "ALL PRIVILEGES";
+          # WriteFreely requires the use of passwords, so we need permissions
+          # to `ALTER` the user to add password support and also to reload
+          # permissions so they can be used.
+          "*.*" = "CREATE USER, RELOAD";
+        };
+      }];
+    };
+
+    services.nginx = lib.mkIf cfg.nginx.enable {
+      enable = true;
+      recommendedProxySettings = true;
+
+      virtualHosts."${cfg.host}" = {
+        enableACME = cfg.acme.enable;
+        forceSSL = cfg.nginx.forceSSL;
+
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString settings.server.port}";
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/youtrack.nix b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
index 789880d61f61..09a2b9e965c0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
@@ -21,7 +21,7 @@ in
 {
   options.services.youtrack = {
 
-    enable = mkEnableOption "YouTrack service";
+    enable = mkEnableOption (lib.mdDoc "YouTrack service");
 
     address = mkOption {
       description = lib.mdDoc ''
@@ -68,7 +68,7 @@ in
         The port youtrack will listen on.
       '';
       default = 8080;
-      type = types.int;
+      type = types.port;
     };
 
     statePath = mkOption {
diff --git a/nixpkgs/nixos/modules/services/web-apps/zabbix.nix b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
index c6ac809a73b0..2cea7e7cea72 100644
--- a/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
@@ -40,7 +40,7 @@ in
 
   options.services = {
     zabbixWeb = {
-      enable = mkEnableOption "the Zabbix web interface";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix web interface");
 
       package = mkOption {
         type = types.package;
@@ -51,7 +51,7 @@ in
 
       server = {
         port = mkOption {
-          type = types.int;
+          type = types.port;
           description = lib.mdDoc "The port of the Zabbix server to connect to.";
           default = 10051;
         };
@@ -78,7 +78,7 @@ in
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default =
             if cfg.database.type == "mysql" then config.services.mysql.port
             else if cfg.database.type == "pgsql" then config.services.postgresql.port
diff --git a/nixpkgs/nixos/modules/services/web-servers/agate.nix b/nixpkgs/nixos/modules/services/web-servers/agate.nix
index 3f7b298fa94e..a0c8a8c94ee5 100644
--- a/nixpkgs/nixos/modules/services/web-servers/agate.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/agate.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.agate = {
-      enable = mkEnableOption "Agate Server";
+      enable = mkEnableOption (lib.mdDoc "Agate Server");
 
       package = mkOption {
         type = types.package;
@@ -43,7 +43,7 @@ in
         type = types.listOf types.str;
         description = lib.mdDoc ''
           Domain name of this Gemini server, enables checking hostname and port
-          in requests. (multiple occurences means basic vhosts)
+          in requests. (multiple occurrences means basic vhosts)
         '';
       };
 
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 a8c9fe263693..588f5ee4d003 100644
--- a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -18,7 +18,7 @@ let
     sed -i $out/bin/apachectl -e 's|$HTTPD -t|$HTTPD -t -f /etc/httpd/httpd.conf|'
   '';
 
-  php = cfg.phpPackage.override { apacheHttpd = pkg; };
+  php = cfg.phpPackage.override { apxs2Support = true; apacheHttpd = pkg; };
 
   phpModuleName = let
     majorVersion = lib.versions.major (lib.getVersion php);
@@ -168,7 +168,7 @@ let
         <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
             ServerName ${hostOpts.hostName}
             ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
-            ServerAdmin ${adminAddr}
+            ${optionalString (adminAddr != null) "ServerAdmin ${adminAddr}"}
             <IfModule mod_ssl.c>
                 SSLEngine off
             </IfModule>
@@ -187,7 +187,7 @@ let
         <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
             ServerName ${hostOpts.hostName}
             ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
-            ServerAdmin ${adminAddr}
+            ${optionalString (adminAddr != null) "ServerAdmin ${adminAddr}"}
             SSLEngine on
             SSLCertificateFile ${sslServerCert}
             SSLCertificateKeyFile ${sslServerKey}
@@ -404,7 +404,7 @@ in
 
     services.httpd = {
 
-      enable = mkEnableOption "the Apache HTTP Server";
+      enable = mkEnableOption (lib.mdDoc "the Apache HTTP Server");
 
       package = mkOption {
         type = types.package;
@@ -445,18 +445,19 @@ in
             { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Additional Apache modules to be used. These can be
           specified as a string in the case of modules distributed
           with Apache, or as an attribute set specifying the
-          <varname>name</varname> and <varname>path</varname> of the
+          {var}`name` and {var}`path` of the
           module.
         '';
       };
 
       adminAddr = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         example = "admin@example.org";
+        default = null;
         description = lib.mdDoc "E-mail address of the server administrator.";
       };
 
@@ -484,14 +485,14 @@ in
       user = mkOption {
         type = types.str;
         default = "wwwrun";
-        description = ''
+        description = lib.mdDoc ''
           User account under which httpd children processes run.
 
           If you require the main httpd process to run as
-          <literal>root</literal> add the following configuration:
-          <programlisting>
+          `root` add the following configuration:
+          ```
           systemd.services.httpd.serviceConfig.User = lib.mkForce "root";
-          </programlisting>
+          ```
         '';
       };
 
@@ -659,6 +660,13 @@ in
           `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
         '';
       }
+      {
+        assertion = cfg.enablePHP -> php.ztsSupport;
+        message = ''
+          The php package provided by `services.httpd.phpPackage` is not built with zts support. Please
+          ensure the php has zts support by settings `services.httpd.phpPackage = php.override { ztsSupport = true; }`
+        '';
+      }
     ] ++ map (name: mkCertOwnershipAssertion {
       inherit (cfg) group user;
       cert = config.security.acme.certs.${name};
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
index 726ad2683d28..f2d4f8357047 100644
--- a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
@@ -43,7 +43,7 @@ in
     priority = mkOption {
       type = types.int;
       default = 1000;
-      description = ''
+      description = lib.mdDoc ''
         Order of this location block in relation to the others in the vhost.
         The semantics are the same as with `lib.mkOrder`. Smaller values have
         a greater priority.
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
index 4f84cad73514..7b87f9ef4bde 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
@@ -45,16 +45,14 @@ in
         { ip = "192.154.1.1"; port = 80; }
         { ip = "*"; port = 8080; }
       ];
-      description = ''
+      description = lib.mdDoc ''
         Listen addresses and ports for this virtual host.
-        <note>
-        <para>
-          This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>.
-        </para>
-        <para>
-          If you only want to set the addresses manually and not the ports, take a look at <literal>listenAddresses</literal>.
-        </para>
-        </note>
+
+        ::: {.note}
+        This option overrides `addSSL`, `forceSSL` and `onlySSL`.
+
+        If you only want to set the addresses manually and not the ports, take a look at `listenAddresses`.
+        :::
       '';
     };
 
@@ -63,7 +61,7 @@ in
 
       description = lib.mdDoc ''
         Listen addresses for this virtual host.
-        Compared to `listen` this only sets the addreses
+        Compared to `listen` this only sets the addresses
         and the ports are chosen automatically.
       '';
       default = [ "*" ];
@@ -118,12 +116,12 @@ in
     useACMEHost = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A host of an existing Let's Encrypt certificate to use.
         This is useful if you have many subdomains and want to avoid hitting the
-        <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
-        Alternately, you can generate a certificate through <option>enableACME</option>.
-        <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using  <xref linkend="opt-security.acme.certs"/>.</emphasis>
+        [rate limit](https://letsencrypt.org/docs/rate-limits).
+        Alternately, you can generate a certificate through {option}`enableACME`.
+        *Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using [](#opt-security.acme.certs).*
       '';
     };
 
@@ -202,14 +200,14 @@ in
           file = "/home/eelco/some-file.png";
         }
       ];
-      description = ''
+      description = lib.mdDoc ''
         This option provides a simple way to serve individual, static files.
 
-        <note><para>
-          This option has been deprecated and will be removed in a future
-          version of NixOS. You can achieve the same result by making use of
-          the <literal>locations.&lt;name&gt;.alias</literal> option.
-        </para></note>
+        ::: {.note}
+        This option has been deprecated and will be removed in a future
+        version of NixOS. You can achieve the same result by making use of
+        the `locations.<name>.alias` option.
+        :::
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix b/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix
index 7f6bc3c4a4d0..f5a9cfac5d77 100644
--- a/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix
@@ -26,7 +26,7 @@ let
 
   configFile =
     let
-      Caddyfile = pkgs.writeText "Caddyfile" ''
+      Caddyfile = pkgs.writeTextDir "Caddyfile" ''
         {
           ${cfg.globalConfig}
         }
@@ -34,10 +34,12 @@ let
       '';
 
       Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
-        ${cfg.package}/bin/caddy fmt ${Caddyfile} > $out
+        mkdir -p $out
+        cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile
+        caddy fmt --overwrite $out/Caddyfile
       '';
     in
-      if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile;
+      "${if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile}/Caddyfile";
 
   acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts);
 
@@ -52,33 +54,33 @@ in
 
   # interface
   options.services.caddy = {
-    enable = mkEnableOption "Caddy web server";
+    enable = mkEnableOption (lib.mdDoc "Caddy web server");
 
     user = mkOption {
       default = "caddy";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         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>
+        ::: {.note}
+        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.
+        :::
       '';
     };
 
     group = mkOption {
       default = "caddy";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         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>
+        ::: {.note}
+        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.
+        :::
       '';
     };
 
@@ -94,34 +96,31 @@ in
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/caddy";
-      description = ''
+      description = lib.mdDoc ''
         The data directory for caddy.
 
-        <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>
+        ::: {.note}
+        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.
+
+        Caddy v2 replaced `CADDYPATH` with XDG directories.
+        See <https://caddyserver.com/docs/conventions#file-locations>.
+        :::
       '';
     };
 
     logDir = mkOption {
       type = types.path;
       default = "/var/log/caddy";
-      description = ''
+      description = lib.mdDoc ''
         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>
+        ::: {.note}
+        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.
+        :::
       '';
     };
 
@@ -145,7 +144,7 @@ in
       default = configFile;
       defaultText = "A Caddyfile automatically generated by values from services.caddy.*";
       example = literalExpression ''
-        pkgs.writeText "Caddyfile" '''
+        pkgs.writeTextDir "Caddyfile" '''
           example.com
 
           root * /var/www/wordpress
@@ -160,18 +159,25 @@ in
     };
 
     adapter = mkOption {
-      default = "caddyfile";
-      example = "nginx";
-      type = types.str;
-      description = ''
+      default = null;
+      example = literalExpression "nginx";
+      type = with types; nullOr str;
+      description = lib.mdDoc ''
         Name of the config adapter to use.
-        See <link xlink:href="https://caddyserver.com/docs/config-adapters"/>
+        See <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>
+        If `null` is specified, the `--adapter` argument is omitted when
+        starting or restarting Caddy. Notably, this allows specification of a
+        configuration file in Caddy's native JSON format, as long as the
+        filename does not start with `Caddyfile` (in which case the `caddyfile`
+        adapter is implicitly enabled). See
+        <https://caddyserver.com/docs/command-line#caddy-run> for details.
+
+        ::: {.note}
+        Any value other than `null` or `caddyfile` is only valid when providing
+        your own `configFile`.
+        :::
       '';
     };
 
@@ -267,8 +273,8 @@ in
   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`";
+      { assertion = cfg.configFile == configFile -> cfg.adapter == "caddyfile" || cfg.adapter == null;
+        message = "To specify an adapter other than 'caddyfile' please provide your own configuration via `services.caddy.configFile`";
       }
     ] ++ map (name: mkCertOwnershipAssertion {
       inherit (cfg) group user;
@@ -285,6 +291,9 @@ in
       }
     '';
 
+    # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
+    boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
+
     systemd.packages = [ cfg.package ];
     systemd.services.caddy = {
       wants = map (hostOpts: "acme-finished-${hostOpts.useACMEHost}.target") acmeVHosts;
@@ -298,17 +307,15 @@ in
       serviceConfig = {
         # 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} --force" ];
-
-        ExecStartPre = "${cfg.package}/bin/caddy validate --config ${cfg.configFile} --adapter ${cfg.adapter}";
+        ExecStart = [ "" ''${cfg.package}/bin/caddy run --config ${cfg.configFile} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"} ${optionalString cfg.resume "--resume"}'' ];
+        ExecReload = [ "" ''${cfg.package}/bin/caddy reload --config ${cfg.configFile} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"} --force'' ];
+        ExecStartPre = ''${cfg.package}/bin/caddy validate --config ${cfg.configFile} ${optionalString (cfg.adapter != null) "--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";
-        SupplementaryGroups = mkIf (length acmeVHosts != 0) [ "acme" ];
 
         # TODO: attempt to upstream these options
         NoNewPrivileges = true;
@@ -331,9 +338,12 @@ in
 
     security.acme.certs =
       let
-        reloads = map (useACMEHost: nameValuePair useACMEHost { reloadServices = [ "caddy.service" ]; }) acmeHosts;
+        certCfg = map (useACMEHost: nameValuePair useACMEHost {
+          group = mkDefault cfg.group;
+          reloadServices = [ "caddy.service" ];
+        }) acmeHosts;
       in
-        listToAttrs reloads;
+        listToAttrs certCfg;
 
   };
 }
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 ed4902b03723..229b53efb49f 100644
--- a/nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix
@@ -33,16 +33,14 @@ in
     useACMEHost = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         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
+        *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>
+        manually using [](#opt-security.acme.certs).*
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
index 5663e9ca9df6..1e3a7166bc41 100644
--- a/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
@@ -15,7 +15,7 @@ let
 
 in {
   options.services.darkhttpd = with types; {
-    enable = mkEnableOption "DarkHTTPd web server";
+    enable = mkEnableOption (lib.mdDoc "DarkHTTPd web server");
 
     port = mkOption {
       default = 80;
@@ -29,7 +29,7 @@ in {
     address = mkOption {
       default = "127.0.0.1";
       type = str;
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on.
         Pass `all` to listen on all interfaces.
       '';
diff --git a/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
index c1cf9ccd9a60..649b058bd22f 100644
--- a/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -54,7 +54,7 @@ in {
 
       serviceConfig = {
         ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${
-          if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else ""
+          optionalString (cfg.socketType != "unix") "-s ${cfg.socketType}:${cfg.socketAddress}"
         }";
       } // (if cfg.user != null && cfg.group != null then {
         User = cfg.user;
diff --git a/nixpkgs/nixos/modules/services/web-servers/garage.md b/nixpkgs/nixos/modules/services/web-servers/garage.md
new file mode 100644
index 000000000000..3a9b85ce0603
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/garage.md
@@ -0,0 +1,96 @@
+# Garage {#module-services-garage}
+
+[Garage](https://garagehq.deuxfleurs.fr/)
+is an open-source, self-hostable S3 store, simpler than MinIO, for geodistributed stores.
+The server setup can be automated using
+[services.garage](#opt-services.garage.enable). A
+ client configured to your local Garage instance is available in
+ the global environment as `garage-manage`.
+
+The current default by NixOS is `garage_0_8` which is also the latest
+major version available.
+
+## General considerations on upgrades {#module-services-garage-upgrade-scenarios}
+
+Garage provides a cookbook documentation on how to upgrade:
+<https://garagehq.deuxfleurs.fr/documentation/cookbook/upgrading/>
+
+::: {.warning}
+Garage has two types of upgrades: patch-level upgrades and minor/major version upgrades.
+
+In all cases, you should read the changelog and ideally test the upgrade on a staging cluster.
+
+Checking the health of your cluster can be achieved using `garage-manage repair`.
+:::
+
+::: {.warning}
+Until 1.0 is released, patch-level upgrades are considered as minor version upgrades.
+Minor version upgrades are considered as major version upgrades.
+i.e. 0.6 to 0.7 is a major version upgrade.
+:::
+
+  - **Straightforward upgrades (patch-level upgrades).**
+    Upgrades must be performed one by one, i.e. for each node, stop it, upgrade it : change [stateVersion](#opt-system.stateVersion) or [services.garage.package](#opt-services.garage.package), restart it if it was not already by switching.
+  - **Multiple version upgrades.**
+    Garage do not provide any guarantee on moving more than one major-version forward.
+    E.g., if you're on `0.7`, you cannot upgrade to `0.9`.
+    You need to upgrade to `0.8` first.
+    As long as [stateVersion](#opt-system.stateVersion) is declared properly,
+    this is enforced automatically. The module will issue a warning to remind the user to upgrade to latest
+    Garage *after* that deploy.
+
+## Advanced upgrades (minor/major version upgrades) {#module-services-garage-advanced-upgrades}
+
+Here are some baseline instructions to handle advanced upgrades in Garage, when in doubt, please refer to upstream instructions.
+
+  - Disable API and web access to Garage.
+  - Perform `garage-manage repair --all-nodes --yes tables` and `garage-manage repair --all-nodes --yes blocks`.
+  - Verify the resulting logs and check that data is synced properly between all nodes.
+    If you have time, do additional checks (`scrub`, `block_refs`, etc.).
+  - Check if queues are empty by `garage-manage stats` or through monitoring tools.
+  - Run `systemctl stop garage` to stop the actual Garage version.
+  - Backup the metadata folder of ALL your nodes, e.g. for a metadata directory (the default one) in `/var/lib/garage/meta`,
+    you can run `pushd /var/lib/garage; tar -acf meta-v0.7.tar.zst meta/; popd`.
+  - Run the offline migration: `nix-shell -p garage_0_8 --run "garage offline-repair --yes"`, this can take some time depending on how many objects are stored in your cluster.
+  - Bump Garage version in your NixOS configuration, either by changing [stateVersion](#opt-system.stateVersion) or bumping [services.garage.package](#opt-services.garage.package), this should restart Garage automatically.
+  - Perform `garage-manage repair --all-nodes --yes tables` and `garage-manage repair --all-nodes --yes blocks`.
+  - Wait for a full table sync to run.
+
+Your upgraded cluster should be in a working state, re-enable API and web access.
+
+## Maintainer information {#module-services-garage-maintainer-info}
+
+As stated in the previous paragraph, we must provide a clean upgrade-path for Garage
+since it cannot move more than one major version forward on a single upgrade. This chapter
+adds some notes how Garage updates should be rolled out in the future.
+This is inspired from how Nextcloud does it.
+
+While patch-level updates are no problem and can be done directly in the
+package-expression (and should be backported to supported stable branches after that),
+major-releases should be added in a new attribute (e.g. Garage `v0.8.0`
+should be available in `nixpkgs` as `pkgs.garage_0_8_0`).
+To provide simple upgrade paths it's generally useful to backport those as well to stable
+branches. As long as the package-default isn't altered, this won't break existing setups.
+After that, the versioning-warning in the `garage`-module should be
+updated to make sure that the
+[package](#opt-services.garage.package)-option selects the latest version
+on fresh setups.
+
+If major-releases will be abandoned by upstream, we should check first if those are needed
+in NixOS for a safe upgrade-path before removing those. In that case we should keep those
+packages, but mark them as insecure in an expression like this (in
+`<nixpkgs/pkgs/tools/filesystem/garage/default.nix>`):
+```
+/* ... */
+{
+  garage_0_7_3 = generic {
+    version = "0.7.3";
+    sha256 = "0000000000000000000000000000000000000000000000000000";
+    eol = true;
+  };
+}
+```
+
+Ideally we should make sure that it's possible to jump two NixOS versions forward:
+i.e. the warnings and the logic in the module should guard a user to upgrade from a
+Garage on e.g. 22.11 to a Garage on 23.11.
diff --git a/nixpkgs/nixos/modules/services/web-servers/garage.nix b/nixpkgs/nixos/modules/services/web-servers/garage.nix
new file mode 100644
index 000000000000..8b5734b5a2ce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/garage.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.garage;
+  toml = pkgs.formats.toml {};
+  configFile = toml.generate "garage.toml" cfg.settings;
+in
+{
+  meta = {
+    doc = ./garage.md;
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  options.services.garage = {
+    enable = mkEnableOption (lib.mdDoc "Garage Object Storage (S3 compatible)");
+
+    extraEnvironment = mkOption {
+      type = types.attrsOf types.str;
+      description = lib.mdDoc "Extra environment variables to pass to the Garage server.";
+      default = {};
+      example = { RUST_BACKTRACE="yes"; };
+    };
+
+    logLevel = mkOption {
+      type = types.enum (["info" "debug" "trace"]);
+      default = "info";
+      example = "debug";
+      description = lib.mdDoc "Garage log level, see <https://garagehq.deuxfleurs.fr/documentation/quick-start/#launching-the-garage-server> for examples.";
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = toml.type;
+
+        options = {
+          metadata_dir = mkOption {
+            default = "/var/lib/garage/meta";
+            type = types.path;
+            description = lib.mdDoc "The metadata directory, put this on a fast disk (e.g. SSD) if possible.";
+          };
+
+          data_dir = mkOption {
+            default = "/var/lib/garage/data";
+            type = types.path;
+            description = lib.mdDoc "The main data storage, put this on your large storage (e.g. high capacity HDD)";
+          };
+
+          replication_mode = mkOption {
+            default = "none";
+            type = types.enum ([ "none" "1" "2" "3" "2-dangerous" "3-dangerous" "3-degraded" 1 2 3 ]);
+            apply = v: toString v;
+            description = lib.mdDoc "Garage replication mode, defaults to none, see: <https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#replication-mode> for reference.";
+          };
+        };
+      };
+      description = lib.mdDoc "Garage configuration, see <https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/> for reference.";
+    };
+
+    package = mkOption {
+      # TODO: when 23.05 is released and if Garage 0.9 is the default, put a stateVersion check.
+      default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.garage_0_8
+                else pkgs.garage_0_7;
+      defaultText = literalExpression "pkgs.garage_0_7";
+      type = types.package;
+      description = lib.mdDoc "Garage package to use, if you are upgrading from a major version, please read NixOS and Garage release notes for upgrade instructions.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."garage.toml" = {
+      source = configFile;
+    };
+
+    environment.systemPackages = [ cfg.package ]; # For administration
+
+    systemd.services.garage = {
+      description = "Garage Object Storage (S3 compatible)";
+      after = [ "network.target" "network-online.target" ];
+      wants = [ "network.target" "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ configFile ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/garage server";
+
+        StateDirectory = mkIf (hasPrefix "/var/lib/garage" cfg.settings.data_dir && hasPrefix "/var/lib/garage" cfg.settings.metadata_dir) "garage";
+        DynamicUser = lib.mkDefault true;
+        ProtectHome = true;
+        NoNewPrivileges = true;
+      };
+      environment = {
+        RUST_LOG = lib.mkDefault "garage=${cfg.logLevel}";
+      } // cfg.extraEnvironment;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
index 78bae1405563..6c8b3cda5f72 100644
--- a/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
@@ -17,7 +17,7 @@ with lib;
 {
   options = {
     services.hitch = {
-      enable = mkEnableOption "Hitch Server";
+      enable = mkEnableOption (lib.mdDoc "Hitch Server");
 
       backend = mkOption {
         type = types.str;
@@ -36,9 +36,9 @@ with lib;
       frontend = mkOption {
         type = types.either types.str (types.listOf types.str);
         default = "[127.0.0.1]:443";
-        description = ''
+        description = lib.mdDoc ''
           The port and interface of the listen endpoint in the
-+         form [HOST]:PORT[+CERT].
+          form [HOST]:PORT[+CERT].
         '';
         apply = toList;
       };
diff --git a/nixpkgs/nixos/modules/services/web-servers/hydron.nix b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
index 292493c4c7ba..4434965b217a 100644
--- a/nixpkgs/nixos/modules/services/web-servers/hydron.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
@@ -4,7 +4,7 @@ let
   cfg = config.services.hydron;
 in with lib; {
   options.services.hydron = {
-    enable = mkEnableOption "hydron";
+    enable = mkEnableOption (lib.mdDoc "hydron");
 
     dataDir = mkOption {
       type = types.path;
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
index 0e5af324c13f..ac573089cd5a 100644
--- a/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
@@ -1,5 +1,6 @@
 set -e
 
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 mkdir -p $out/bin
diff --git a/nixpkgs/nixos/modules/services/web-servers/keter/default.nix b/nixpkgs/nixos/modules/services/web-servers/keter/default.nix
index 83e221add37e..9adbe65de69f 100644
--- a/nixpkgs/nixos/modules/services/web-servers/keter/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/keter/default.nix
@@ -8,22 +8,22 @@ in
   };
 
   options.services.keter = {
-    enable = lib.mkEnableOption ''keter, a web app deployment manager.
+    enable = lib.mkEnableOption (lib.mdDoc ''keter, a web app deployment manager.
 Note that this module only support loading of webapps:
 Keep an old app running and swap the ports when the new one is booted.
-'';
+'');
 
     keterRoot = lib.mkOption {
       type = lib.types.str;
       default = "/var/lib/keter";
-      description = "Mutable state folder for keter";
+      description = lib.mdDoc "Mutable state folder for keter";
     };
 
     keterPackage = lib.mkOption {
       type = lib.types.package;
       default = pkgs.haskellPackages.keter;
       defaultText = lib.literalExpression "pkgs.haskellPackages.keter";
-      description = "The keter package to be used";
+      description = lib.mdDoc "The keter package to be used";
     };
 
     globalKeterConfig = lib.mkOption {
@@ -47,31 +47,31 @@ Keep an old app running and swap the ports when the new one is booted.
           }];
         }
       '';
-      description = "Global config for keter";
+      description = lib.mdDoc "Global config for keter";
     };
 
     bundle = {
       appName = lib.mkOption {
         type = lib.types.str;
         default = "myapp";
-        description = "The name keter assigns to this bundle";
+        description = lib.mdDoc "The name keter assigns to this bundle";
       };
 
       executable = lib.mkOption {
         type = lib.types.path;
-        description = "The executable to be run";
+        description = lib.mdDoc "The executable to be run";
       };
 
       domain = lib.mkOption {
         type = lib.types.str;
         default = "example.com";
-        description = "The domain keter will bind to";
+        description = lib.mdDoc "The domain keter will bind to";
       };
 
       publicScript = lib.mkOption {
         type = lib.types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Allows loading of public environment variables,
           these are emitted to the log so it shouldn't contain secrets.
         '';
@@ -81,7 +81,7 @@ Keep an old app running and swap the ports when the new one is booted.
       secretScript = lib.mkOption {
         type = lib.types.str;
         default = "";
-        description = "Allows loading of private environment variables";
+        description = lib.mdDoc "Allows loading of private environment variables";
         example = "MY_AWS_KEY=$(cat /run/keys/AWS_ACCESS_KEY_ID)";
       };
     };
@@ -140,7 +140,7 @@ Keep an old app running and swap the ports when the new one is booted.
 
       # On deploy this will load our app, by moving it into the incoming dir
       # If the bundle content changes, this will run again.
-      # Because the bundle content contains the nix path to the exectuable,
+      # Because the bundle content contains the nix path to the executable,
       # we inherit nix based cache busting.
       systemd.services.load-keter-bundle = {
         description = "load keter bundle into incoming folder";
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
index 270517a4e2a5..9a4285e3e2d2 100644
--- a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -25,13 +25,13 @@ in
 
   options.services.lighttpd.collectd = {
 
-    enable = mkEnableOption "collectd subservice accessible at http://yourserver/collectd";
+    enable = mkEnableOption (lib.mdDoc "collectd subservice accessible at http://yourserver/collectd");
 
     collectionCgi = mkOption {
       type = types.path;
       default = defaultCollectionCgi;
-      defaultText = literalDocBook ''
-        <literal>config.${options.services.collectd.package}</literal> configured for lighttpd
+      defaultText = literalMD ''
+        `config.${options.services.collectd.package}` configured for lighttpd
       '';
       description = lib.mdDoc ''
         Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
index ec847495d741..0438e12e7da8 100644
--- a/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -64,7 +64,7 @@ let
   ];
 
   maybeModuleString = moduleName:
-    if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
+    optionalString (elem moduleName cfg.enableModules) ''"${moduleName}"'';
 
   modulesIncludeString = concatStringsSep ",\n"
     (filter (x: x != "") (map maybeModuleString allKnownModules));
@@ -106,15 +106,15 @@ let
       static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
       index-file.names = ( "index.html" )
 
-      ${if cfg.mod_userdir then ''
+      ${optionalString cfg.mod_userdir ''
         userdir.path = "public_html"
-      '' else ""}
+      ''}
 
-      ${if cfg.mod_status then ''
+      ${optionalString cfg.mod_status ''
         status.status-url = "/server-status"
         status.statistics-url = "/server-statistics"
         status.config-url = "/server-config"
-      '' else ""}
+      ''}
 
       ${cfg.extraConfig}
     '';
@@ -137,7 +137,7 @@ in
 
       package = mkOption {
         default = pkgs.lighttpd;
-        defaultText = "pkgs.lighttpd";
+        defaultText = lib.literalExpression "pkgs.lighttpd";
         type = types.package;
         description = lib.mdDoc ''
           lighttpd package to use.
diff --git a/nixpkgs/nixos/modules/services/web-servers/merecat.nix b/nixpkgs/nixos/modules/services/web-servers/merecat.nix
new file mode 100644
index 000000000000..aad93605b717
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/merecat.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.merecat;
+  format = pkgs.formats.keyValue {
+    mkKeyValue = generators.mkKeyValueDefault {
+      mkValueString = v:
+        # In merecat.conf, booleans are "true" and "false"
+        if builtins.isBool v
+        then if v then "true" else "false"
+        else generators.mkValueStringDefault {} v;
+    } "=";
+  };
+  configFile = format.generate "merecat.conf" cfg.settings;
+
+in {
+
+  options.services.merecat = {
+
+    enable = mkEnableOption (lib.mdDoc "Merecat HTTP server");
+
+    settings = mkOption {
+      inherit (format) type;
+      default = { };
+      description = lib.mdDoc ''
+        Merecat configuration. Refer to merecat(8) for details on supported values.
+      '';
+      example = {
+        hostname = "localhost";
+        port = 8080;
+        virtual-host = true;
+        directory = "/srv/www";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.merecat = {
+      description = "Merecat HTTP server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.merecat}/bin/merecat -n -f ${configFile}";
+        AmbientCapabilities = lib.mkIf ((cfg.settings.port or 80) < 1024) [ "CAP_NET_BIND_SERVICE" ];
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
index 523b5de2d693..2d887af87c79 100644
--- a/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
@@ -8,7 +8,7 @@ let
   routingFile = pkgs.writeText "mighty-routing" cfg.routing;
 in {
   options.services.mighttpd2 = {
-    enable = mkEnableOption "Mighttpd2 web server";
+    enable = mkEnableOption (lib.mdDoc "Mighttpd2 web server");
 
     config = mkOption {
       default = "";
diff --git a/nixpkgs/nixos/modules/services/web-servers/minio.nix b/nixpkgs/nixos/modules/services/web-servers/minio.nix
index 60e3068521c4..21bec4f63a87 100644
--- a/nixpkgs/nixos/modules/services/web-servers/minio.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/minio.nix
@@ -14,7 +14,7 @@ in
   meta.maintainers = [ maintainers.bachp ];
 
   options.services.minio = {
-    enable = mkEnableOption "Minio Object Storage";
+    enable = mkEnableOption (lib.mdDoc "Minio Object Storage");
 
     listenAddress = mkOption {
       default = ":9000";
@@ -60,7 +60,7 @@ in
       '';
     };
 
-    rootCredentialsFile = mkOption  {
+    rootCredentialsFile = mkOption {
       type = types.nullOr types.path;
       default = null;
       description = lib.mdDoc ''
@@ -96,29 +96,62 @@ in
   config = mkIf cfg.enable {
     warnings = optional ((cfg.accessKey != "") || (cfg.secretKey != "")) "services.minio.`accessKey` and services.minio.`secretKey` are deprecated, please use services.minio.`rootCredentialsFile` instead.";
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.configDir}' - minio minio - -"
-    ] ++ (map (x:  "d '" + x + "' - minio minio - - ") cfg.dataDir);
-
-    systemd.services.minio = {
-      description = "Minio Object Storage";
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
-        Type = "simple";
-        User = "minio";
-        Group = "minio";
-        LimitNOFILE = 65536;
-        EnvironmentFile = if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
-                          else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
-                          else null;
+    systemd = lib.mkMerge [{
+      tmpfiles.rules = [
+        "d '${cfg.configDir}' - minio minio - -"
+      ] ++ (map (x: "d '" + x + "' - minio minio - - ") cfg.dataDir);
+
+      services.minio = {
+        description = "Minio Object Storage";
+        after = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
+          Type = "simple";
+          User = "minio";
+          Group = "minio";
+          LimitNOFILE = 65536;
+          EnvironmentFile =
+            if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
+            else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
+            else null;
+        };
+        environment = {
+          MINIO_REGION = "${cfg.region}";
+          MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
+        };
       };
-      environment = {
-        MINIO_REGION = "${cfg.region}";
-        MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
-      };
-    };
+    }
+
+      (lib.mkIf (cfg.rootCredentialsFile != null) {
+        # The service will fail if the credentials file is missing
+        services.minio.unitConfig.ConditionPathExists = cfg.rootCredentialsFile;
+
+        # The service will not restart if the credentials file has
+        # been changed. This can cause stale root credentials.
+        paths.minio-root-credentials = {
+          wantedBy = [ "multi-user.target" ];
+
+          pathConfig = {
+            PathChanged = [ cfg.rootCredentialsFile ];
+            Unit = "minio-restart.service";
+          };
+        };
+
+        services.minio-restart = {
+          description = "Restart MinIO";
+
+          script = ''
+            systemctl restart minio.service
+          '';
+
+          serviceConfig = {
+            Type = "oneshot";
+            Restart = "on-failure";
+            RestartSec = 5;
+          };
+        };
+      })];
 
     users.users.minio = {
       group = "minio";
diff --git a/nixpkgs/nixos/modules/services/web-servers/molly-brown.nix b/nixpkgs/nixos/modules/services/web-servers/molly-brown.nix
index 31a2e856db47..6d7ca0c12ef7 100644
--- a/nixpkgs/nixos/modules/services/web-servers/molly-brown.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/molly-brown.nix
@@ -10,7 +10,7 @@ in {
 
   options.services.molly-brown = {
 
-    enable = mkEnableOption "Molly-Brown Gemini server";
+    enable = mkEnableOption (lib.mdDoc "Molly-Brown Gemini server");
 
     port = mkOption {
       default = 1965;
@@ -34,16 +34,16 @@ in {
     certPath = mkOption {
       type = types.path;
       example = "/var/lib/acme/example.com/cert.pem";
-      description = ''
+      description = lib.mdDoc ''
         Path to TLS certificate. An ACME certificate and key may be
         shared with an HTTP server, but only if molly-brown has
         permissions allowing it to read such keys.
 
         As an example:
-        <programlisting>
+        ```
         systemd.services.molly-brown.serviceConfig.SupplementaryGroups =
           [ config.security.acme.certs."example.com".group ];
-        </programlisting>
+        ```
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
index 72b91c37f8a7..e87159ba99c7 100644
--- a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.nginx;
-  certs = config.security.acme.certs;
+  inherit (config.security.acme) certs;
   vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
   acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME || vhostConfig.useACMEHost != null) vhostsConfigs;
   dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
@@ -27,7 +27,44 @@ let
                               else "${certs.${certName}.directory}/chain.pem";
     })
   ) cfg.virtualHosts;
-  enableIPv6 = config.networking.enableIPv6;
+  inherit (config.networking) enableIPv6;
+
+  # Mime.types values are taken from brotli sample configuration - https://github.com/google/ngx_brotli
+  # and Nginx Server Configs - https://github.com/h5bp/server-configs-nginx
+  # "text/html" is implicitly included in {brotli,gzip,zstd}_types
+  compressMimeTypes = [
+    "application/atom+xml"
+    "application/geo+json"
+    "application/json"
+    "application/ld+json"
+    "application/manifest+json"
+    "application/rdf+xml"
+    "application/vnd.ms-fontobject"
+    "application/wasm"
+    "application/x-rss+xml"
+    "application/x-web-app-manifest+json"
+    "application/xhtml+xml"
+    "application/xliff+xml"
+    "application/xml"
+    "font/collection"
+    "font/otf"
+    "font/ttf"
+    "image/bmp"
+    "image/svg+xml"
+    "image/vnd.microsoft.icon"
+    "text/cache-manifest"
+    "text/calendar"
+    "text/css"
+    "text/csv"
+    "text/javascript"
+    "text/markdown"
+    "text/plain"
+    "text/vcard"
+    "text/vnd.rim.location.xloc"
+    "text/vtt"
+    "text/x-component"
+    "text/xml"
+  ];
 
   defaultFastcgiParams = {
     SCRIPT_FILENAME   = "$document_root$fastcgi_script_name";
@@ -65,22 +102,36 @@ let
     proxy_set_header        X-Forwarded-Server $host;
   '';
 
+  proxyCachePathConfig = concatStringsSep "\n" (mapAttrsToList (name: proxyCachePath: ''
+    proxy_cache_path ${concatStringsSep " " [
+      "/var/cache/nginx/${name}"
+      "keys_zone=${proxyCachePath.keysZoneName}:${proxyCachePath.keysZoneSize}"
+      "levels=${proxyCachePath.levels}"
+      "use_temp_path=${if proxyCachePath.useTempPath then "on" else "off"}"
+      "inactive=${proxyCachePath.inactive}"
+      "max_size=${proxyCachePath.maxSize}"
+    ]};
+  '') (filterAttrs (name: conf: conf.enable) cfg.proxyCachePath));
+
+  toUpstreamParameter = key: value:
+    if builtins.isBool value
+    then lib.optionalString value key
+    else "${key}=${toString value}";
+
   upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: ''
     upstream ${name} {
       ${toString (flip mapAttrsToList upstream.servers (name: server: ''
-        server ${name} ${optionalString server.backup "backup"};
+        server ${name} ${concatStringsSep " " (mapAttrsToList toUpstreamParameter server)};
       ''))}
       ${upstream.extraConfig}
     }
   ''));
 
   commonHttpConfig = ''
-      # The mime type definitions included with nginx are very incomplete, so
-      # 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;
+      # Load mime types.
+      include ${cfg.defaultMimeTypes};
       # 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
+      # contains 1026 entries 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;
 
@@ -112,7 +163,7 @@ let
       ''}
       ${upstreamConfig}
 
-      ${optionalString (cfg.recommendedOptimisation) ''
+      ${optionalString cfg.recommendedOptimisation ''
         # optimisation
         sendfile on;
         tcp_nopush on;
@@ -124,7 +175,7 @@ let
       ${optionalString (cfg.sslCiphers != null) "ssl_ciphers ${cfg.sslCiphers};"}
       ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
 
-      ${optionalString (cfg.recommendedTlsSettings) ''
+      ${optionalString cfg.recommendedTlsSettings ''
         # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
 
         ssl_session_timeout 1d;
@@ -140,30 +191,44 @@ let
         ssl_stapling_verify on;
       ''}
 
-      ${optionalString (cfg.recommendedGzipSettings) ''
+      ${optionalString cfg.recommendedBrotliSettings ''
+        brotli on;
+        brotli_static on;
+        brotli_comp_level 5;
+        brotli_window 512k;
+        brotli_min_length 256;
+        brotli_types ${lib.concatStringsSep " " compressMimeTypes};
+      ''}
+
+      ${optionalString cfg.recommendedGzipSettings
+        # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
+      ''
         gzip on;
-        gzip_proxied any;
-        gzip_comp_level 5;
-        gzip_types
-          application/atom+xml
-          application/javascript
-          application/json
-          application/xml
-          application/xml+rss
-          image/svg+xml
-          text/css
-          text/javascript
-          text/plain
-          text/xml;
+        gzip_static on;
         gzip_vary on;
+        gzip_comp_level 5;
+        gzip_min_length 256;
+        gzip_proxied expired no-cache no-store private auth;
+        gzip_types ${lib.concatStringsSep " " compressMimeTypes};
+      ''}
+
+      ${optionalString cfg.recommendedZstdSettings ''
+        zstd on;
+        zstd_comp_level 9;
+        zstd_min_length 256;
+        zstd_static on;
+        zstd_types ${lib.concatStringsSep " " compressMimeTypes};
       ''}
 
-      ${optionalString (cfg.recommendedProxySettings) ''
+      ${optionalString cfg.recommendedProxySettings ''
         proxy_redirect          off;
         proxy_connect_timeout   ${cfg.proxyTimeout};
         proxy_send_timeout      ${cfg.proxyTimeout};
         proxy_read_timeout      ${cfg.proxyTimeout};
         proxy_http_version      1.1;
+        # don't let clients close the keep-alive connection to upstream. See the nginx blog for details:
+        # https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives
+        proxy_set_header        "Connection" "";
         include ${recommendedProxyConfig};
       ''}
 
@@ -194,12 +259,12 @@ let
 
       ${cfg.commonHttpConfig}
 
-      ${vhosts}
+      ${proxyCachePathConfig}
 
       ${optionalString cfg.statusPage ''
         server {
-          listen 80;
-          ${optionalString enableIPv6 "listen [::]:80;" }
+          listen ${toString cfg.defaultHTTPListenPort};
+          ${optionalString enableIPv6 "listen [::]:${toString cfg.defaultHTTPListenPort};" }
 
           server_name localhost;
 
@@ -213,6 +278,8 @@ let
         }
       ''}
 
+      ${vhosts}
+
       ${cfg.appendHttpConfig}
     }''}
 
@@ -242,40 +309,64 @@ let
         onlySSL = vhost.onlySSL || vhost.enableSSL;
         hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
 
+        # First evaluation of defaultListen based on a set of listen lines.
+        mkDefaultListenVhost = listenLines:
+          # If this vhost has SSL or is a SSL rejection host.
+          # We enable a TLS variant for lines without explicit ssl or ssl = true.
+          optionals (hasSSL || vhost.rejectSSL)
+            (map (listen: { port = cfg.defaultSSLListenPort; ssl = true; } // listen)
+            (filter (listen: !(listen ? ssl) || listen.ssl) listenLines))
+          # If this vhost is supposed to serve HTTP
+          # We provide listen lines for those without explicit ssl or ssl = false.
+          ++ optionals (!onlySSL)
+            (map (listen: { port = cfg.defaultHTTPListenPort; ssl = false; } // listen)
+            (filter (listen: !(listen ? ssl) || !listen.ssl) listenLines));
+
         defaultListen =
           if vhost.listen != [] then vhost.listen
           else
+          if cfg.defaultListen != [] then mkDefaultListenVhost
+            # Cleanup nulls which will mess up with //.
+            # TODO: is there a better way to achieve this? i.e. mergeButIgnoreNullPlease?
+            (map (listenLine: filterAttrs (_: v: (v != null)) listenLine) cfg.defaultListen)
+          else
             let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else cfg.defaultListenAddresses;
-            in optionals (hasSSL || vhost.rejectSSL) (map (addr: { inherit addr; port = 443; ssl = true; }) addrs)
-              ++ optionals (!onlySSL) (map (addr: { inherit addr; port = 80; ssl = false; }) addrs);
+            in mkDefaultListenVhost (map (addr: { inherit addr; }) addrs);
+
 
         hostListen =
           if vhost.forceSSL
             then filter (x: x.ssl) defaultListen
             else defaultListen;
 
-        listenString = { addr, port, ssl, extraParameters ? [], ... }:
-          (if ssl && vhost.http3 then "
-          # UDP listener for **QUIC+HTTP/3
-          listen ${addr}:${toString port} http3 "
+        listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
+          # UDP listener for QUIC transport protocol.
+          (optionalString (ssl && vhost.quic) ("
+            listen ${addr}:${toString port} quic "
           + optionalString vhost.default "default_server "
           + optionalString vhost.reuseport "reuseport "
-          + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
-          + ";" else "")
+          + optionalString (extraParameters != []) (concatStringsSep " "
+            (let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
+                isCompatibleParameter = param: !(any (p: p == param) inCompatibleParameters);
+            in filter isCompatibleParameter extraParameters))
+          + ";"))
           + "
-
             listen ${addr}:${toString port} "
           + optionalString (ssl && vhost.http2) "http2 "
           + optionalString ssl "ssl "
           + optionalString vhost.default "default_server "
           + optionalString vhost.reuseport "reuseport "
+          + optionalString proxyProtocol "proxy_protocol "
           + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
           + ";";
 
         redirectListen = filter (x: !x.ssl) defaultListen;
 
         acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) ''
-          location /.well-known/acme-challenge {
+          # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
+          # We use ^~ here, so that we don't check any regexes (which could
+          # otherwise easily override this intended match accidentally).
+          location ^~ /.well-known/acme-challenge/ {
             ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
             ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"}
             auth_basic off;
@@ -304,10 +395,16 @@ let
         server {
           ${concatMapStringsSep "\n" listenString hostListen}
           server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+          ${optionalString (hasSSL && vhost.quic) ''
+            http3 ${if vhost.http3 then "on" else "off"};
+            http3_hq ${if vhost.http3_hq then "on" else "off"};
+          ''}
           ${acmeLocation}
           ${optionalString (vhost.root != null) "root ${vhost.root};"}
           ${optionalString (vhost.globalRedirect != null) ''
-            return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
+            location / {
+              return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
+            }
           ''}
           ${optionalString hasSSL ''
             ssl_certificate ${vhost.sslCertificate};
@@ -323,9 +420,10 @@ let
             ssl_conf_command Options KTLS;
           ''}
 
-          ${optionalString (hasSSL && vhost.http3) ''
+          ${optionalString (hasSSL && vhost.quic && vhost.http3)
             # Advertise that HTTP/3 is available
-            add_header Alt-Svc 'h3=":443"; ma=86400' always;
+          ''
+            add_header Alt-Svc 'h3=":$server_port"; ma=86400';
           ''}
 
           ${mkBasicAuth vhostName vhost}
@@ -385,55 +483,121 @@ in
 {
   options = {
     services.nginx = {
-      enable = mkEnableOption "Nginx Web Server";
+      enable = mkEnableOption (lib.mdDoc "Nginx Web Server");
 
       statusPage = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
-        ";
+        '';
       };
 
       recommendedTlsSettings = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Enable recommended TLS settings.
-        ";
+        '';
       };
 
       recommendedOptimisation = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Enable recommended optimisation settings.
-        ";
+        '';
+      };
+
+      recommendedBrotliSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable recommended brotli settings.
+          Learn more about compression in Brotli format [here](https://github.com/google/ngx_brotli/).
+
+          This adds `pkgs.nginxModules.brotli` to `services.nginx.additionalModules`.
+        '';
       };
 
       recommendedGzipSettings = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Enable recommended gzip settings.
-        ";
+          Learn more about compression in Gzip format [here](https://docs.nginx.com/nginx/admin-guide/web-server/compression/).
+        '';
+      };
+
+      recommendedZstdSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable recommended zstd settings.
+          Learn more about compression in Zstd format [here](https://github.com/tokers/zstd-nginx-module).
+
+          This adds `pkgs.nginxModules.zstd` to `services.nginx.additionalModules`.
+        '';
       };
 
       recommendedProxySettings = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Whether to enable recommended proxy settings if a vhost does not specify the option manually.
-        ";
+        '';
       };
 
       proxyTimeout = mkOption {
         type = types.str;
         default = "60s";
         example = "20s";
-        description = "
+        description = lib.mdDoc ''
           Change the proxy related timeouts in recommendedProxySettings.
-        ";
+        '';
+      };
+
+      defaultListen = mkOption {
+        type = with types; listOf (submodule {
+          options = {
+            addr = mkOption {
+              type = str;
+              description = lib.mdDoc "IP address.";
+            };
+            port = mkOption {
+              type = nullOr port;
+              description = lib.mdDoc "Port number.";
+              default = null;
+            };
+            ssl  = mkOption {
+              type = nullOr bool;
+              default = null;
+              description = lib.mdDoc "Enable SSL.";
+            };
+            proxyProtocol = mkOption {
+              type = bool;
+              description = lib.mdDoc "Enable PROXY protocol.";
+              default = false;
+            };
+            extraParameters = mkOption {
+              type = listOf str;
+              description = lib.mdDoc "Extra parameters of this listen directive.";
+              default = [ ];
+              example = [ "backlog=1024" "deferred" ];
+            };
+          };
+        });
+        default = [];
+        example = literalExpression ''[
+          { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
+          { addr = "0.0.0.0"; }
+          { addr = "[::0]"; }
+        ]'';
+        description = lib.mdDoc ''
+          If vhosts do not specify listen, use these addresses by default.
+          This option takes precedence over {option}`defaultListenAddresses` and
+          other listen-related defaults options.
+        '';
       };
 
       defaultListenAddresses = mkOption {
@@ -441,9 +605,40 @@ in
         default = [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]";
         defaultText = literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
         example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
-        description = "
+        description = lib.mdDoc ''
           If vhosts do not specify listenAddresses, use these addresses by default.
-        ";
+          This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`.
+        '';
+      };
+
+      defaultHTTPListenPort = mkOption {
+        type = types.port;
+        default = 80;
+        example = 8080;
+        description = lib.mdDoc ''
+          If vhosts do not specify listen.port, use these ports for HTTP by default.
+        '';
+      };
+
+      defaultSSLListenPort = mkOption {
+        type = types.port;
+        default = 443;
+        example = 8443;
+        description = lib.mdDoc ''
+          If vhosts do not specify listen.port, use these ports for SSL by default.
+        '';
+      };
+
+      defaultMimeTypes = mkOption {
+        type = types.path;
+        default = "${pkgs.mailcap}/etc/nginx/mime.types";
+        defaultText = literalExpression "$''{pkgs.mailcap}/etc/nginx/mime.types";
+        example = literalExpression "$''{pkgs.nginx}/conf/mime.types";
+        description = lib.mdDoc ''
+          Default MIME types for NGINX, as MIME types definitions from NGINX are very incomplete,
+          we use by default the ones bundled in the mailcap package, used by most of the other
+          Linux distributions.
+        '';
       };
 
       package = mkOption {
@@ -451,30 +646,29 @@ in
         defaultText = literalExpression "pkgs.nginxStable";
         type = types.package;
         apply = p: p.override {
-          modules = p.modules ++ cfg.additionalModules;
+          modules = lib.unique (p.modules ++ cfg.additionalModules);
         };
-        description = "
+        description = lib.mdDoc ''
           Nginx package to use. This defaults to the stable version. Note
           that the nginx team recommends to use the mainline version which
-          available in nixpkgs as <literal>nginxMainline</literal>.
-        ";
+          available in nixpkgs as `nginxMainline`.
+        '';
       };
 
       additionalModules = mkOption {
         default = [];
         type = types.listOf (types.attrsOf types.anything);
-        example = literalExpression "[ pkgs.nginxModules.brotli ]";
+        example = literalExpression "[ pkgs.nginxModules.echo ]";
         description = lib.mdDoc ''
           Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/)
-          to install. Packaged modules are available in
-          `pkgs.nginxModules`.
+          to install. Packaged modules are available in `pkgs.nginxModules`.
         '';
       };
 
       logError = mkOption {
         default = "stderr";
         type = types.str;
-        description = "
+        description = lib.mdDoc ''
           Configures logging.
           The first parameter defines a file that will store the log. The
           special value stderr selects the standard error file. Logging to
@@ -485,35 +679,30 @@ in
           increasing severity. Setting a certain log level will cause all
           messages of the specified and more severe log levels to be logged.
           If this parameter is omitted then error is used.
-        ";
+        '';
       };
 
       preStart =  mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Shell commands executed before the service's nginx is started.
-        ";
+        '';
       };
 
       config = mkOption {
         type = types.str;
         default = "";
-        description = ''
-          Verbatim <filename>nginx.conf</filename> configuration.
+        description = lib.mdDoc ''
+          Verbatim {file}`nginx.conf` configuration.
           This is mutually exclusive to any other config option for
-          <filename>nginx.conf</filename> except for
-          <itemizedlist>
-          <listitem><para><xref linkend="opt-services.nginx.appendConfig"/>
-          </para></listitem>
-          <listitem><para><xref linkend="opt-services.nginx.httpConfig"/>
-          </para></listitem>
-          <listitem><para><xref linkend="opt-services.nginx.logError"/>
-          </para></listitem>
-          </itemizedlist>
+          {file}`nginx.conf` except for
+          - [](#opt-services.nginx.appendConfig)
+          - [](#opt-services.nginx.httpConfig)
+          - [](#opt-services.nginx.logError)
 
           If additional verbatim config in addition to other options is needed,
-          <xref linkend="opt-services.nginx.appendConfig"/> should be used instead.
+          [](#opt-services.nginx.appendConfig) should be used instead.
         '';
       };
 
@@ -524,7 +713,7 @@ in
           Configuration lines appended to the generated Nginx
           configuration file. Commonly used by different modules
           providing http snippets. {option}`appendConfig`
-          can be specified more than once and it's value will be
+          can be specified more than once and its value will be
           concatenated (contrary to {option}`config` which
           can be set only once).
         '';
@@ -551,12 +740,12 @@ in
       httpConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Configuration lines to be set inside the http block.
           This is mutually exclusive with the structured configuration
           via virtualHosts and the recommendedXyzSettings configuration
           options. See appendHttpConfig for appending to the generated http block.
-        ";
+        '';
       };
 
       streamConfig = mkOption {
@@ -569,9 +758,9 @@ in
             proxy_pass 192.168.0.1:53535;
           }
         '';
-        description = "
+        description = lib.mdDoc ''
           Configuration lines to be set inside the stream block.
-        ";
+        '';
       };
 
       eventsConfig = mkOption {
@@ -585,20 +774,20 @@ in
       appendHttpConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Configuration lines to be appended to the generated http block.
           This is mutually exclusive with using config and httpConfig for
           specifying the whole http block verbatim.
-        ";
+        '';
       };
 
       enableReload = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Reload nginx when configuration file changes (instead of restart).
-          The configuration file is exposed at <filename>/etc/nginx/nginx.conf</filename>.
-          See also <literal>systemd.services.*.restartIfChanged</literal>.
+          The configuration file is exposed at {file}`/etc/nginx/nginx.conf`.
+          See also `systemd.services.*.restartIfChanged`.
         '';
       };
 
@@ -691,6 +880,75 @@ in
           '';
       };
 
+      proxyCachePath = mkOption {
+        type = types.attrsOf (types.submodule ({ ... }: {
+          options = {
+            enable = mkEnableOption (lib.mdDoc "this proxy cache path entry");
+
+            keysZoneName = mkOption {
+              type = types.str;
+              default = "cache";
+              example = "my_cache";
+              description = lib.mdDoc "Set name to shared memory zone.";
+            };
+
+            keysZoneSize = mkOption {
+              type = types.str;
+              default = "10m";
+              example = "32m";
+              description = lib.mdDoc "Set size to shared memory zone.";
+            };
+
+            levels = mkOption {
+              type = types.str;
+              default = "1:2";
+              example = "1:2:2";
+              description = lib.mdDoc ''
+                The levels parameter defines structure of subdirectories in cache: from
+                1 to 3, each level accepts values 1 or 2. Сan be used any combination of
+                1 and 2 in these formats: x, x:x and x:x:x.
+              '';
+            };
+
+            useTempPath = mkOption {
+              type = types.bool;
+              default = false;
+              example = true;
+              description = lib.mdDoc ''
+                Nginx first writes files that are destined for the cache to a temporary
+                storage area, and the use_temp_path=off directive instructs Nginx to
+                write them to the same directories where they will be cached. Recommended
+                that you set this parameter to off to avoid unnecessary copying of data
+                between file systems.
+              '';
+            };
+
+            inactive = mkOption {
+              type = types.str;
+              default = "10m";
+              example = "1d";
+              description = lib.mdDoc ''
+                Cached data that has not been accessed for the time specified by
+                the inactive parameter is removed from the cache, regardless of
+                its freshness.
+              '';
+            };
+
+            maxSize = mkOption {
+              type = types.str;
+              default = "1g";
+              example = "2048m";
+              description = lib.mdDoc "Set maximum cache size";
+            };
+          };
+        }));
+        default = {};
+        description = lib.mdDoc ''
+          Configure a proxy cache path entry.
+          See <http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
+        '';
+      };
+
       resolver = mkOption {
         type = types.submodule {
           options = {
@@ -731,6 +989,7 @@ in
           options = {
             servers = mkOption {
               type = types.attrsOf (types.submodule {
+                freeformType = types.attrsOf (types.oneOf [ types.bool types.int types.str ]);
                 options = {
                   backup = mkOption {
                     type = types.bool;
@@ -744,9 +1003,11 @@ in
               });
               description = lib.mdDoc ''
                 Defines the address and other parameters of the upstream servers.
+                See [the documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server)
+                for the available parameters.
               '';
               default = {};
-              example = { "127.0.0.1:8000" = {}; };
+              example = lib.literalMD "see [](#opt-services.nginx.upstreams)";
             };
             extraConfig = mkOption {
               type = types.lines;
@@ -761,14 +1022,23 @@ in
           Defines a group of servers to use as proxy target.
         '';
         default = {};
-        example = literalExpression ''
-          "backend_server" = {
-            servers = { "127.0.0.1:8000" = {}; };
-            extraConfig = ''''
+        example = {
+          "backend" = {
+            servers = {
+              "backend1.example.com:8080" = { weight = 5; };
+              "backend2.example.com" = { max_fails = 3; fail_timeout = "30s"; };
+              "backend3.example.com" = {};
+              "backup1.example.com" = { backup = true; };
+              "backup2.example.com" = { backup = true; };
+            };
+            extraConfig = ''
               keepalive 16;
-            '''';
+            '';
           };
-        '';
+          "memcached" = {
+            servers."unix:/run//memcached/memcached.sock" = {};
+          };
+        };
       };
 
       virtualHosts = mkOption {
@@ -799,11 +1069,15 @@ in
       The Nginx log directory has been moved to /var/log/nginx, the cache directory
       to /var/cache/nginx. The option services.nginx.stateDir has been removed.
     '')
+    (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "inactive" ] [ "services" "nginx" "proxyCachePath" "" "inactive" ])
+    (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "useTempPath" ] [ "services" "nginx" "proxyCachePath" "" "useTempPath" ])
+    (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "levels" ] [ "services" "nginx" "proxyCachePath" "" "levels" ])
+    (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "keysZoneSize" ] [ "services" "nginx" "proxyCachePath" "" "keysZoneSize" ])
+    (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "keysZoneName" ] [ "services" "nginx" "proxyCachePath" "" "keysZoneName" ])
+    (mkRenamedOptionModule [ "services" "nginx" "proxyCache" "enable" ] [ "services" "nginx" "proxyCachePath" "" "enable" ])
   ];
 
   config = mkIf cfg.enable {
-    # TODO: test user supplied config file pases syntax test
-
     warnings =
     let
       deprecatedSSL = name: config: optional config.enableSSL
@@ -858,12 +1132,49 @@ in
           services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
         '';
       }
+
+      {
+        assertion = cfg.package.pname != "nginxQuic" -> all (host: !host.quic) (attrValues virtualHosts);
+        message = ''
+          services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic package,
+          which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
+        '';
+      }
+
+      {
+        # The idea is to understand whether there is a virtual host with a listen configuration
+        # that requires ACME configuration but has no HTTP listener which will make deterministically fail
+        # this operation.
+        # Options' priorities are the following at the moment:
+        # listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server)
+        assertion =
+        let
+          hasAtLeastHttpListener = listenOptions: any (listenLine: if listenLine ? proxyProtocol then !listenLine.proxyProtocol else true) listenOptions;
+          hasAtLeastDefaultHttpListener = if cfg.defaultListen != [] then hasAtLeastHttpListener cfg.defaultListen else (cfg.defaultListenAddresses != []);
+        in
+          all (host:
+            let
+              hasAtLeastVhostHttpListener = if host.listen != [] then hasAtLeastHttpListener host.listen else (host.listenAddresses != []);
+              vhostAuthority = host.listen != [] || (cfg.defaultListen == [] && host.listenAddresses != []);
+            in
+              # Either vhost has precedence and we need a vhost specific http listener
+              # Either vhost set nothing and inherit from server settings
+              host.enableACME -> ((vhostAuthority && hasAtLeastVhostHttpListener) || (!vhostAuthority && hasAtLeastDefaultHttpListener))
+          ) (attrValues virtualHosts);
+        message = ''
+          services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener
+          to answer to ACME requests.
+        '';
+      }
     ] ++ map (name: mkCertOwnershipAssertion {
       inherit (cfg) group user;
       cert = config.security.acme.certs.${name};
       groups = config.users.groups;
     }) dependentCertNames;
 
+    services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli
+      ++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd;
+
     systemd.services.nginx = {
       description = "Nginx Web Server";
       wantedBy = [ "multi-user.target" ];
@@ -949,14 +1260,14 @@ in
       sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
       sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
     in mkIf (cfg.enableReload || sslServices != []) {
-      wants = optionals (cfg.enableReload) [ "nginx.service" ];
+      wants = optionals cfg.enableReload [ "nginx.service" ];
       wantedBy = sslServices ++ [ "multi-user.target" ];
       # Before the finished targets, after the renew services.
       # This service might be needed for HTTP-01 challenges, but we only want to confirm
       # certs are updated _after_ config has been reloaded.
       before = sslTargets;
       after = sslServices;
-      restartTriggers = optionals (cfg.enableReload) [ configFile ];
+      restartTriggers = optionals cfg.enableReload [ configFile ];
       # Block reloading if not all certs exist yet.
       # Happens when config changes add new vhosts/certs.
       unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames);
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 dc68194f752b..2728852058ea 100644
--- a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -122,7 +122,7 @@ with lib;
     priority = mkOption {
       type = types.int;
       default = 1000;
-      description = ''
+      description = lib.mdDoc ''
         Order of this location block in relation to the others in the vhost.
         The semantics are the same as with `lib.mkOrder`. Smaller values have
         a greater priority.
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
index a749f8bc20f1..7636c1b26115 100644
--- a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -27,12 +27,35 @@ with lib;
     };
 
     listen = mkOption {
-      type = with types; listOf (submodule { options = {
-        addr = mkOption { type = str;  description = lib.mdDoc "IP address.";  };
-        port = mkOption { type = int;  description = lib.mdDoc "Port number."; default = 80; };
-        ssl  = mkOption { type = bool; description = lib.mdDoc "Enable SSL.";  default = false; };
-        extraParameters = mkOption { type = listOf str; description = lib.mdDoc "Extra parameters of this listen directive."; default = []; example = [ "backlog=1024" "deferred" ]; };
-      }; });
+      type = with types; listOf (submodule {
+        options = {
+          addr = mkOption {
+            type = str;
+            description = lib.mdDoc "IP address.";
+          };
+          port = mkOption {
+            type = port;
+            description = lib.mdDoc "Port number.";
+            default = 80;
+          };
+          ssl = mkOption {
+            type = bool;
+            description = lib.mdDoc "Enable SSL.";
+            default = false;
+          };
+          proxyProtocol = mkOption {
+            type = bool;
+            description = lib.mdDoc "Enable PROXY protocol.";
+            default = false;
+          };
+          extraParameters = mkOption {
+            type = listOf str;
+            description = lib.mdDoc "Extra parameters of this listen directive.";
+            default = [ ];
+            example = [ "backlog=1024" "deferred" ];
+          };
+        };
+      });
       default = [];
       example = [
         { addr = "195.154.1.1"; port = 443; ssl = true; }
@@ -45,7 +68,7 @@ with lib;
         and `onlySSL`.
 
         If you only want to set the addresses manually and not
-        the ports, take a look at `listenAddresses`
+        the ports, take a look at `listenAddresses`.
       '';
     };
 
@@ -54,8 +77,8 @@ with lib;
 
       description = lib.mdDoc ''
         Listen addresses for this virtual host.
-        Compared to `listen` this only sets the addreses
-        and the ports are choosen automatically.
+        Compared to `listen` this only sets the addresses
+        and the ports are chosen automatically.
 
         Note: This option overrides `enableIPv6`
       '';
@@ -75,12 +98,12 @@ with lib;
     useACMEHost = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A host of an existing Let's Encrypt certificate to use.
         This is useful if you have many subdomains and want to avoid hitting the
-        <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
-        Alternately, you can generate a certificate through <option>enableACME</option>.
-        <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using  <xref linkend="opt-security.acme.certs"/>.</emphasis>
+        [rate limit](https://letsencrypt.org/docs/rate-limits).
+        Alternately, you can generate a certificate through {option}`enableACME`.
+        *Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using [](#opt-security.acme.certs).*
       '';
     };
 
@@ -88,7 +111,7 @@ with lib;
       type = types.nullOr types.str;
       default = "/var/lib/acme/acme-challenge";
       description = lib.mdDoc ''
-        Directory for the acme challenge which is PUBLIC, don't put certs or keys in here.
+        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.
       '';
     };
@@ -97,8 +120,12 @@ with lib;
       type = types.nullOr types.str;
       default = null;
       description = lib.mdDoc ''
-        Host which to proxy requests to if acme challenge is not found. Useful
+        Host which to proxy requests to if ACME challenge is not found. Useful
         if you want multiple hosts to be able to verify the same domain name.
+
+        With this option, you could request certificates for the present domain
+        with an ACME client that is running on another host, which you would
+        specify here.
       '';
     };
 
@@ -184,11 +211,11 @@ with lib;
       type = types.bool;
       default = true;
       description = lib.mdDoc ''
-        Whether to enable HTTP 2.
+        Whether to enable the HTTP/2 protocol.
         Note that (as of writing) due to nginx's implementation, to disable
-        HTTP 2 you have to disable it on all vhosts that use a given
+        HTTP/2 you have to disable it on all vhosts that use a given
         IP address / port.
-        If there is one server block configured to enable http2,then it is
+        If there is one server block configured to enable http2, then it is
         enabled for all server blocks on this IP.
         See https://stackoverflow.com/a/39466948/263061.
       '';
@@ -196,12 +223,42 @@ with lib;
 
     http3 = mkOption {
       type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Whether to enable the HTTP/3 protocol.
+        This requires using `pkgs.nginxQuic` package
+        which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`
+        and activate the QUIC transport protocol
+        `services.nginx.virtualHosts.<name>.quic = true;`.
+        Note that HTTP/3 support is experimental and
+        *not* yet recommended for production.
+        Read more at https://quic.nginx.org/
+      '';
+    };
+
+    http3_hq = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to enable the HTTP/0.9 protocol negotiation used in QUIC interoperability tests.
+        This requires using `pkgs.nginxQuic` package
+        which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`
+        and activate the QUIC transport protocol
+        `services.nginx.virtualHosts.<name>.quic = true;`.
+        Note that special application protocol support is experimental and
+        *not* yet recommended for production.
+        Read more at https://quic.nginx.org/
+      '';
+    };
+
+    quic = mkOption {
+      type = types.bool;
       default = false;
-      description = ''
-        Whether to enable HTTP 3.
-        This requires using <literal>pkgs.nginxQuic</literal> package
-        which can be achieved by setting <literal>services.nginx.package = pkgs.nginxQuic;</literal>.
-        Note that HTTP 3 support is experimental and
+      description = lib.mdDoc ''
+        Whether to enable the QUIC transport protocol.
+        This requires using `pkgs.nginxQuic` package
+        which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
+        Note that QUIC support is experimental and
         *not* yet recommended for production.
         Read more at https://quic.nginx.org/
       '';
diff --git a/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
index e24c77d056a2..0bd1d5b29b31 100644
--- a/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -40,9 +40,12 @@ let
         socket = mkOption {
           type = types.str;
           readOnly = true;
-          description = ''
+          description = lib.mdDoc ''
             Path to the unix socket file on which to accept FastCGI requests.
-            <note><para>This option is read-only and managed by NixOS.</para></note>
+
+            ::: {.note}
+            This option is read-only and managed by NixOS.
+            :::
           '';
           example = "${runtimeDir}/<name>.sock";
         };
diff --git a/nixpkgs/nixos/modules/services/web-servers/pomerium.nix b/nixpkgs/nixos/modules/services/web-servers/pomerium.nix
index 209de55e36ef..90748f74d24e 100644
--- a/nixpkgs/nixos/modules/services/web-servers/pomerium.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/pomerium.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.services.pomerium = {
-    enable = mkEnableOption "the Pomerium authenticating reverse proxy";
+    enable = mkEnableOption (lib.mdDoc "the Pomerium authenticating reverse proxy");
 
     configFile = mkOption {
       type = with types; nullOr path;
diff --git a/nixpkgs/nixos/modules/services/web-servers/stargazer.nix b/nixpkgs/nixos/modules/services/web-servers/stargazer.nix
new file mode 100644
index 000000000000..f0c3cf8787eb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/stargazer.nix
@@ -0,0 +1,226 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.stargazer;
+  globalSection = ''
+    listen = ${lib.concatStringsSep " " cfg.listen}
+    connection-logging = ${lib.boolToString cfg.connectionLogging}
+    log-ip = ${lib.boolToString cfg.ipLog}
+    log-ip-partial = ${lib.boolToString cfg.ipLogPartial}
+    request-timeout = ${toString cfg.requestTimeout}
+    response-timeout = ${toString cfg.responseTimeout}
+
+    [:tls]
+    store = ${toString cfg.store}
+    organization = ${cfg.certOrg}
+    gen-certs = ${lib.boolToString cfg.genCerts}
+    regen-certs = ${lib.boolToString cfg.regenCerts}
+    ${lib.optionalString (cfg.certLifetime != "") "cert-lifetime = ${cfg.certLifetime}"}
+
+  '';
+  genINI = lib.generators.toINI { };
+  configFile = pkgs.writeText "config.ini" (lib.strings.concatStrings (
+    [ globalSection ] ++ (lib.lists.forEach cfg.routes (section:
+      let
+        name = section.route;
+        params = builtins.removeAttrs section [ "route" ];
+      in
+      genINI
+        {
+          "${name}" = params;
+        } + "\n"
+    ))
+  ));
+in
+{
+  options.services.stargazer = {
+    enable = lib.mkEnableOption (lib.mdDoc "Stargazer Gemini server");
+
+    listen = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]";
+      defaultText = lib.literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
+      example = lib.literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
+      description = lib.mdDoc ''
+        Address and port to listen on.
+      '';
+    };
+
+    connectionLogging = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc "Whether or not to log connections to stdout.";
+    };
+
+    ipLog = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc "Log client IP addresses in the connection log.";
+    };
+
+    ipLogPartial = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc "Log partial client IP addresses in the connection log.";
+    };
+
+    requestTimeout = lib.mkOption {
+      type = lib.types.int;
+      default = 5;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the client to send a complete
+        request. Set to 0 to disable.
+      '';
+    };
+
+    responseTimeout = lib.mkOption {
+      type = lib.types.int;
+      default = 0;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the client to send a complete
+        request and for stargazer to finish sending the response.
+        Set to 0 to disable.
+      '';
+    };
+
+    store = lib.mkOption {
+      type = lib.types.path;
+      default = /var/lib/gemini/certs;
+      description = lib.mdDoc ''
+        Path to the certificate store on disk. This should be a
+        persistent directory writable by Stargazer.
+      '';
+    };
+
+    certOrg = lib.mkOption {
+      type = lib.types.str;
+      default = "stargazer";
+      description = lib.mdDoc ''
+        The name of the organization responsible for the X.509
+        certificate's /O name.
+      '';
+    };
+
+    genCerts = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Set to false to disable automatic certificate generation.
+        Use if you want to provide your own certs.
+      '';
+    };
+
+    regenCerts = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Set to false to turn off automatic regeneration of expired certificates.
+        Use if you want to provide your own certs.
+      '';
+    };
+
+    certLifetime = lib.mkOption {
+      type = lib.types.str;
+      default = "";
+      description = lib.mdDoc ''
+        How long certs generated by Stargazer should live for.
+        Certs live forever by default.
+      '';
+      example = lib.literalExpression "\"1y\"";
+    };
+
+    routes = lib.mkOption {
+      type = lib.types.listOf
+        (lib.types.submodule {
+          freeformType = with lib.types; attrsOf (nullOr
+            (oneOf [
+              bool
+              int
+              float
+              str
+            ]) // {
+            description = "INI atom (null, bool, int, float or string)";
+          });
+          options.route = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Route section name";
+          };
+        });
+      default = [ ];
+      description = lib.mdDoc ''
+        Routes that Stargazer should server.
+
+        Expressed as a list of attribute sets. Each set must have a key `route`
+        that becomes the section name for that route in the stargazer ini cofig.
+        The remaining keys and values become the parameters for that route.
+
+        [Refer to upstream docs for other params](https://git.sr.ht/~zethra/stargazer/tree/main/item/doc/stargazer.ini.5.txt)
+      '';
+      example = lib.literalExpression ''
+        [
+          {
+            route = "example.com";
+            root = "/srv/gemini/example.com"
+          }
+          {
+            route = "example.com:/man";
+            root = "/cgi-bin";
+            cgi = true;
+          }
+          {
+            route = "other.org~(.*)";
+            redirect = "gemini://example.com";
+            rewrite = "\1";
+          }
+        ]
+      '';
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "stargazer";
+      description = lib.mdDoc "User account under which stargazer runs.";
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "stargazer";
+      description = lib.mdDoc "Group account under which stargazer runs.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.stargazer = {
+      description = "Stargazer gemini server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.stargazer}/bin/stargazer ${configFile}";
+        Restart = "always";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    # Create default cert store
+    system.activationScripts.makeStargazerCertDir =
+      lib.optionalAttrs (cfg.store == /var/lib/gemini/certs) ''
+        mkdir -p /var/lib/gemini/certs
+        chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs
+      '';
+
+    users.users = lib.optionalAttrs (cfg.user == "stargazer") {
+      stargazer = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = lib.optionalAttrs (cfg.group == "stargazer") {
+      stargazer = { };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ gaykitty ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/tomcat.nix b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
index ec7f46e25e95..4d2c36287be6 100644
--- a/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
@@ -19,7 +19,7 @@ in
   options = {
 
     services.tomcat = {
-      enable = mkEnableOption "Apache Tomcat";
+      enable = mkEnableOption (lib.mdDoc "Apache Tomcat");
 
       package = mkOption {
         type = types.package;
@@ -34,7 +34,7 @@ in
       purifyOnStart = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           On startup, the `baseDir` directory is populated with various files,
           subdirectories and symlinks. If this option is enabled, these items
           (except for the `logs` and `work` subdirectories) are first removed.
@@ -46,7 +46,7 @@ in
       baseDir = mkOption {
         type = lib.types.path;
         default = "/var/tomcat";
-        description = ''
+        description = lib.mdDoc ''
           Location where Tomcat stores configuration files, web applications
           and logfiles. Note that it is partially cleared on each service startup
           if `purifyOnStart` is enabled.
@@ -112,10 +112,10 @@ in
       serverXml = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Verbatim server.xml configuration.
           This is mutually exclusive with the virtualHosts options.
-        ";
+        '';
       };
 
       commonLibs = mkOption {
@@ -234,11 +234,11 @@ in
           ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
         done
 
-        ${if cfg.extraConfigFiles != [] then ''
+        ${optionalString (cfg.extraConfigFiles != []) ''
           for i in ${toString cfg.extraConfigFiles}; do
             ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
           done
-        '' else ""}
+        ''}
 
         # Create a modified catalina.properties file
         # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
@@ -345,7 +345,7 @@ in
 
           # Symlink all the given web applications files or paths into the webapps/ directory
           # of this virtual host
-          for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do
+          for i in "${optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do
             if [ -f $i ]; then
               # If the given web application is a file, symlink it into the webapps/ directory
               ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
diff --git a/nixpkgs/nixos/modules/services/web-servers/traefik.nix b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
index abef963201e5..42fb95a52200 100644
--- a/nixpkgs/nixos/modules/services/web-servers/traefik.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
@@ -48,9 +48,14 @@ let
     ''
   else
     cfg.staticConfigFile;
+
+  finalStaticConfigFile =
+    if cfg.environmentFiles == []
+    then staticConfigFile
+    else "/run/traefik/config.toml";
 in {
   options.services.traefik = {
-    enable = mkEnableOption "Traefik web server";
+    enable = mkEnableOption (lib.mdDoc "Traefik web server");
 
     staticConfigFile = mkOption {
       default = null;
@@ -127,6 +132,16 @@ in {
       type = types.package;
       description = lib.mdDoc "Traefik package to use.";
     };
+
+    environmentFiles = mkOption {
+      default = [];
+      type = types.listOf types.path;
+      example = [ "/run/secrets/traefik.env" ];
+      description = lib.mdDoc ''
+        Files to load as environment file. Environment variables from this file
+        will be substituted into the static configuration file using envsubst.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -139,8 +154,13 @@ in {
       startLimitIntervalSec = 86400;
       startLimitBurst = 5;
       serviceConfig = {
-        ExecStart =
-          "${cfg.package}/bin/traefik --configfile=${staticConfigFile}";
+        EnvironmentFile = cfg.environmentFiles;
+        ExecStartPre = lib.optional (cfg.environmentFiles != [])
+          (pkgs.writeShellScript "pre-start" ''
+            umask 077
+            ${pkgs.envsubst}/bin/envsubst -i "${staticConfigFile}" > "${finalStaticConfigFile}"
+          '');
+        ExecStart = "${cfg.package}/bin/traefik --configfile=${finalStaticConfigFile}";
         Type = "simple";
         User = "traefik";
         Group = cfg.group;
@@ -155,6 +175,7 @@ in {
         ProtectHome = true;
         ProtectSystem = "full";
         ReadWriteDirectories = cfg.dataDir;
+        RuntimeDirectory = "traefik";
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix b/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix
index beb5e437c5b1..17dece8746a1 100644
--- a/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix
@@ -33,7 +33,7 @@ let
 in
 {
   options.services.trafficserver = {
-    enable = mkEnableOption "Apache Traffic Server";
+    enable = mkEnableOption (lib.mdDoc "Apache Traffic Server");
 
     cache = mkOption {
       type = types.lines;
@@ -62,7 +62,7 @@ in
     ipAllow = mkOption {
       type = types.nullOr yaml.type;
       default = lib.importJSON ./ip_allow.json;
-      defaultText = literalDocBook "upstream defaults";
+      defaultText = literalMD "upstream defaults";
       example = literalExpression ''
         {
           ip_allow = [{
@@ -85,7 +85,7 @@ in
     logging = mkOption {
       type = types.nullOr yaml.type;
       default = lib.importJSON ./logging.json;
-      defaultText = literalDocBook "upstream defaults";
+      defaultText = literalMD "upstream defaults";
       example = { };
       description = lib.mdDoc ''
         Configure logs.
diff --git a/nixpkgs/nixos/modules/services/web-servers/ttyd.nix b/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
index 0c47d9583cda..e0a8b5179e06 100644
--- a/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
@@ -30,7 +30,7 @@ in
 
   options = {
     services.ttyd = {
-      enable = mkEnableOption "ttyd daemon";
+      enable = mkEnableOption (lib.mdDoc "ttyd daemon");
 
       port = mkOption {
         type = types.port;
@@ -163,7 +163,7 @@ in
     assertions =
       [ { assertion = cfg.enableSSL
             -> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null;
-          message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specefied."; }
+          message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specified."; }
         { assertion = ! (cfg.interface != null && cfg.socket != null);
           message = "Cannot set both interface and socket for ttyd."; }
         { assertion = (cfg.username != null) == (cfg.passwordFile != null);
diff --git a/nixpkgs/nixos/modules/services/web-servers/unit/default.nix b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
index 5ad4a240bec2..1515779c9064 100644
--- a/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
@@ -10,7 +10,7 @@ let
 in {
   options = {
     services.unit = {
-      enable = mkEnableOption "Unit App Server";
+      enable = mkEnableOption (lib.mdDoc "Unit App Server");
       package = mkOption {
         type = types.package;
         default = pkgs.unit;
@@ -104,7 +104,7 @@ in {
         PIDFile = "/run/unit/unit.pid";
         ExecStart = ''
           ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
-                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --tmp '/tmp' \
+                                   --log '${cfg.logDir}/unit.log' --statedir '${cfg.stateDir}' --tmpdir '/tmp' \
                                    --user ${cfg.user} --group ${cfg.group}
         '';
         ExecStop = ''
diff --git a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
index 377c7720d6ee..c076375ed857 100644
--- a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
@@ -167,22 +167,20 @@ in {
             "CAP_NET_RAW"          # open raw sockets
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Grant capabilities to the uWSGI instance. See the
-          <literal>capabilities(7)</literal> for available values.
-          <note>
-            <para>
-              uWSGI runs as an unprivileged user (even as Emperor) with the minimal
-              capabilities required. This option can be used to add fine-grained
-              permissions without running the service as root.
-            </para>
-            <para>
-              When in Emperor mode, any capability to be inherited by a vassal must
-              be specified again in the vassal configuration using <literal>cap</literal>.
-              See the uWSGI <link xlink:href="https://uwsgi-docs.readthedocs.io/en/latest/Capabilities.html">docs</link>
-              for more information.
-            </para>
-          </note>
+          `capabilities(7)` for available values.
+
+          ::: {.note}
+          uWSGI runs as an unprivileged user (even as Emperor) with the minimal
+          capabilities required. This option can be used to add fine-grained
+          permissions without running the service as root.
+
+          When in Emperor mode, any capability to be inherited by a vassal must
+          be specified again in the vassal configuration using `cap`.
+          See the uWSGI [docs](https://uwsgi-docs.readthedocs.io/en/latest/Capabilities.html)
+          for more information.
+          :::
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
index 39ebe6338728..d7f19be0cec4 100644
--- a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
@@ -11,9 +11,9 @@ in
 {
   options = {
     services.varnish = {
-      enable = mkEnableOption "Varnish Server";
+      enable = mkEnableOption (lib.mdDoc "Varnish Server");
 
-      enableConfigCheck = mkEnableOption "checking the config during build time" // { default = true; };
+      enableConfigCheck = mkEnableOption (lib.mdDoc "checking the config during build time") // { default = true; };
 
       package = mkOption {
         type = types.package;
@@ -27,43 +27,43 @@ in
       http_address = mkOption {
         type = types.str;
         default = "*:6081";
-        description = "
+        description = lib.mdDoc ''
           HTTP listen address and port.
-        ";
+        '';
       };
 
       config = mkOption {
         type = types.lines;
-        description = "
+        description = lib.mdDoc ''
           Verbatim default.vcl configuration.
-        ";
+        '';
       };
 
       stateDir = mkOption {
         type = types.path;
         default = "/var/spool/varnish/${config.networking.hostName}";
         defaultText = literalExpression ''"/var/spool/varnish/''${config.networking.hostName}"'';
-        description = "
+        description = lib.mdDoc ''
           Directory holding all state for Varnish to run.
-        ";
+        '';
       };
 
       extraModules = mkOption {
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.varnishPackages.geoip ]";
-        description = "
+        description = lib.mdDoc ''
           Varnish modules (except 'std').
-        ";
+        '';
       };
 
       extraCommandLine = mkOption {
         type = types.str;
         default = "";
         example = "-s malloc,256M";
-        description = "
+        description = lib.mdDoc ''
           Command line switches for varnishd (run 'varnishd -?' to get list of options)
-        ";
+        '';
       };
     };
 
@@ -99,7 +99,7 @@ in
     environment.systemPackages = [ cfg.package ];
 
     # check .vcl syntax at compile time (e.g. before nixops deployment)
-    system.extraDependencies = mkIf cfg.enableConfigCheck [
+    system.checks = mkIf cfg.enableConfigCheck [
       (pkgs.runCommand "check-varnish-syntax" {} ''
         ${cfg.package}/bin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)
       '')
diff --git a/nixpkgs/nixos/modules/services/web-servers/zope2.nix b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
index a80fe882f1a7..a17fe6bc2082 100644
--- a/nixpkgs/nixos/modules/services/web-servers/zope2.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
@@ -95,7 +95,7 @@ in
           };
         }
       '';
-      description = lib.mdDoc "zope2 instances to be created automaticaly by the system.";
+      description = lib.mdDoc "zope2 instances to be created automatically by the system.";
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/x11/clight.nix b/nixpkgs/nixos/modules/services/x11/clight.nix
index 8a17b7e801e5..0f66e191fe28 100644
--- a/nixpkgs/nixos/modules/services/x11/clight.nix
+++ b/nixpkgs/nixos/modules/services/x11/clight.nix
@@ -28,13 +28,7 @@ let
       cfg.settings));
 in {
   options.services.clight = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Whether to enable clight or not.
-      '';
-    };
+    enable = mkEnableOption (lib.mdDoc "clight");
 
     temperature = {
       day = mkOption {
diff --git a/nixpkgs/nixos/modules/services/x11/colord.nix b/nixpkgs/nixos/modules/services/x11/colord.nix
index 31ccee6aa33f..cb7b9096e5db 100644
--- a/nixpkgs/nixos/modules/services/x11/colord.nix
+++ b/nixpkgs/nixos/modules/services/x11/colord.nix
@@ -11,7 +11,7 @@ in {
   options = {
 
     services.colord = {
-      enable = mkEnableOption "colord, the color management daemon";
+      enable = mkEnableOption (lib.mdDoc "colord, the color management daemon");
     };
 
   };
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/budgie.nix
new file mode 100644
index 000000000000..b7341d4d8b49
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -0,0 +1,244 @@
+{ lib, pkgs, config, utils, ... }:
+
+let
+  inherit (lib) concatMapStrings literalExpression mdDoc mkDefault mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.xserver.desktopManager.budgie;
+
+  nixos-background-light = pkgs.nixos-artwork.wallpapers.nineish;
+  nixos-background-dark = pkgs.nixos-artwork.wallpapers.nineish-dark-gray;
+
+  nixos-gsettings-overrides = pkgs.budgie.budgie-gsettings-overrides.override {
+    inherit (cfg) extraGSettingsOverrides extraGSettingsOverridePackages;
+    inherit nixos-background-dark nixos-background-light;
+  };
+
+  nixos-background-info = pkgs.writeTextFile {
+    name = "nixos-background-info";
+    text = ''
+      <?xml version="1.0"?>
+      <!DOCTYPE wallpapers SYSTEM "gnome-wp-list.dtd">
+      <wallpapers>
+        <wallpaper deleted="false">
+          <name>Nineish</name>
+          <filename>${nixos-background-light.gnomeFilePath}</filename>
+          <options>zoom</options>
+          <shade_type>solid</shade_type>
+          <pcolor>#d1dcf8</pcolor>
+          <scolor>#e3ebfe</scolor>
+        </wallpaper>
+        <wallpaper deleted="false">
+          <name>Nineish Dark Gray</name>
+          <filename>${nixos-background-dark.gnomeFilePath}</filename>
+          <options>zoom</options>
+          <shade_type>solid</shade_type>
+          <pcolor>#151515</pcolor>
+          <scolor>#262626</scolor>
+        </wallpaper>
+      </wallpapers>
+    '';
+    destination = "/share/gnome-background-properties/nixos.xml";
+  };
+in {
+  options = {
+    services.xserver.desktopManager.budgie = {
+      enable = mkEnableOption (mdDoc "the Budgie desktop");
+
+      sessionPath = mkOption {
+        description = lib.mdDoc ''
+          Additional list of packages to be added to the session search path.
+          Useful for GSettings-conditional autostart.
+
+          Note that this should be a last resort; patching the package is preferred (see GPaste).
+        '';
+        type = types.listOf types.package;
+        default = [];
+        example = literalExpression "[ pkgs.gnome.gpaste ]";
+      };
+
+      extraGSettingsOverrides = mkOption {
+        description = mdDoc "Additional GSettings overrides.";
+        type = types.lines;
+        default = "";
+      };
+
+      extraGSettingsOverridePackages = mkOption {
+        description = mdDoc "List of packages for which GSettings are overridden.";
+        type = types.listOf types.path;
+        default = [];
+      };
+
+      extraPlugins = mkOption {
+        description = mdDoc "Extra plugins for the Budgie desktop";
+        type = types.listOf types.package;
+        default = [];
+        example = literalExpression "[ pkgs.budgiePlugins.budgie-analogue-clock-applet ]";
+      };
+    };
+
+    environment.budgie.excludePackages = mkOption {
+      description = mdDoc "Which packages Budgie should exclude from the default environment.";
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression "[ pkgs.mate-terminal ]";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.displayManager.sessionPackages = with pkgs; [
+      budgie.budgie-desktop
+    ];
+
+    services.xserver.displayManager.lightdm.greeters.slick = {
+      enable = mkDefault true;
+      theme = mkDefault { name = "Qogir"; package = pkgs.qogir-theme; };
+      iconTheme = mkDefault { name = "Qogir"; package = pkgs.qogir-icon-theme; };
+      cursorTheme = mkDefault { name = "Qogir"; package = pkgs.qogir-icon-theme; };
+    };
+
+    services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie.budgie-desktop-view ];
+
+    environment.extraInit = ''
+      ${concatMapStrings (p: ''
+        if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+          export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+        fi
+        if [ -d "${p}/lib/girepository-1.0" ]; then
+          export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+          export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+        fi
+      '') cfg.sessionPath}
+    '';
+
+    environment.systemPackages = with pkgs;
+      [
+        # Budgie Desktop.
+        budgie.budgie-backgrounds
+        budgie.budgie-control-center
+        (budgie.budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
+        budgie.budgie-desktop-view
+        budgie.budgie-screensaver
+
+        # Required by the Budgie Desktop session.
+        (gnome.gnome-session.override { gnomeShellSupport = false; })
+
+        # Required by Budgie Menu.
+        gnome-menus
+
+        # Required by Budgie Control Center.
+        gnome.zenity
+
+        # Provides `gsettings`.
+        glib
+
+        # Update user directories.
+        xdg-user-dirs
+      ]
+      ++ (utils.removePackagesByName [
+          cinnamon.nemo
+          mate.eom
+          mate.pluma
+          mate.atril
+          mate.engrampa
+          mate.mate-calc
+          mate.mate-terminal
+          mate.mate-system-monitor
+          vlc
+
+          # Desktop themes.
+          qogir-theme
+          qogir-icon-theme
+          nixos-background-info
+
+          # Default settings.
+          nixos-gsettings-overrides
+        ] config.environment.budgie.excludePackages)
+      ++ cfg.sessionPath;
+
+    # Fonts.
+    fonts.fonts = mkDefault [
+      pkgs.noto-fonts
+      pkgs.hack-font
+    ];
+    fonts.fontconfig.defaultFonts = {
+      sansSerif = mkDefault ["Noto Sans"];
+      monospace = mkDefault ["Hack"];
+    };
+
+    # Qt application style.
+    qt = {
+      enable = mkDefault true;
+      style = mkDefault "gtk2";
+      platformTheme = mkDefault "gtk2";
+    };
+
+    environment.pathsToLink = [
+      "/share" # TODO: https://github.com/NixOS/nixpkgs/issues/47173
+    ];
+
+    # GSettings overrides.
+    environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+
+    # Required by Budgie Desktop.
+    services.xserver.updateDbusEnvironment = true;
+    programs.dconf.enable = true;
+
+    # Required by Budgie Screensaver.
+    security.pam.services.budgie-screensaver = {};
+
+    # Required by Budgie's Polkit Dialog.
+    security.polkit.enable = mkDefault true;
+
+    # Required by Budgie Panel plugins and/or Budgie Control Center panels.
+    networking.networkmanager.enable = mkDefault true; # for BCC's Network panel.
+    programs.nm-applet.enable = config.networking.networkmanager.enable; # Budgie has no Network applet.
+    programs.nm-applet.indicator = false; # Budgie doesn't support AppIndicators.
+
+    hardware.bluetooth.enable = mkDefault true; # for Budgie's Status Indicator and BCC's Bluetooth panel.
+    hardware.pulseaudio.enable = mkDefault true; # for Budgie's Status Indicator and BCC's Sound panel.
+
+    xdg.portal.enable = mkDefault true; # for BCC's Applications panel.
+    xdg.portal.extraPortals = with pkgs; [
+      xdg-desktop-portal-gtk # provides a XDG Portals implementation.
+    ];
+
+    services.geoclue2.enable = mkDefault true; # for BCC's Privacy > Location Services panel.
+    services.upower.enable = config.powerManagement.enable; # for Budgie's Status Indicator and BCC's Power panel.
+    services.xserver.libinput.enable = mkDefault true; # for BCC's Mouse panel.
+    services.colord.enable = mkDefault true; # for BCC's Color panel.
+    services.gnome.at-spi2-core.enable = mkDefault true; # for BCC's A11y panel.
+    services.accounts-daemon.enable = mkDefault true; # for BCC's Users panel.
+    services.fprintd.enable = mkDefault true; # for BCC's Users panel.
+    services.udisks2.enable = mkDefault true; # for BCC's Details panel.
+
+    # For BCC's Online Accounts panel.
+    services.gnome.gnome-online-accounts.enable = mkDefault true;
+    services.gnome.gnome-online-miners.enable = true;
+
+    # For BCC's Printers panel.
+    services.printing.enable = mkDefault true;
+    services.system-config-printer.enable = config.services.printing.enable;
+
+    # For BCC's Sharing panel.
+    services.dleyna-renderer.enable = mkDefault true;
+    services.dleyna-server.enable = mkDefault true;
+    services.gnome.gnome-user-share.enable = mkDefault true;
+    services.gnome.rygel.enable = mkDefault true;
+
+    # Other default services.
+    services.gnome.evolution-data-server.enable = mkDefault true;
+    services.gnome.glib-networking.enable = mkDefault true;
+    services.gnome.gnome-keyring.enable = mkDefault true;
+    services.gnome.gnome-settings-daemon.enable = mkDefault true;
+    services.gvfs.enable = mkDefault true;
+
+    # Register packages for DBus.
+    services.dbus.packages = with pkgs; [
+      budgie.budgie-control-center
+    ];
+
+    # Shell integration for MATE Terminal.
+    programs.bash.vteIntegration = true;
+    programs.zsh.vteIntegration = true;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix
index 05cf011f62c5..e0b4fb0e7bfb 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix
@@ -7,7 +7,7 @@ let
   cfg = xcfg.desktopManager.cde;
 in {
   options.services.xserver.desktopManager.cde = {
-    enable = mkEnableOption "Common Desktop Environment";
+    enable = mkEnableOption (lib.mdDoc "Common Desktop Environment");
 
     extraPackages = mkOption {
       type = with types; listOf package;
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index 26a5191761d6..7ced5b63c839 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -12,16 +12,17 @@ let
     extraGSettingsOverrides = cfg.extraGSettingsOverrides;
   };
 
+  notExcluded = pkg: (!(lib.elem pkg config.environment.cinnamon.excludePackages));
 in
 
 {
   options = {
     services.cinnamon = {
-      apps.enable = mkEnableOption "Cinnamon default applications";
+      apps.enable = mkEnableOption (lib.mdDoc "Cinnamon default applications");
     };
 
     services.xserver.desktopManager.cinnamon = {
-      enable = mkEnableOption "the cinnamon desktop manager";
+      enable = mkEnableOption (lib.mdDoc "the cinnamon desktop manager");
 
       sessionPath = mkOption {
         default = [];
@@ -58,13 +59,26 @@ in
   };
 
   config = mkMerge [
-    (mkIf (cfg.enable && config.services.xserver.displayManager.lightdm.enable && config.services.xserver.displayManager.lightdm.greeters.gtk.enable) {
-      services.xserver.displayManager.lightdm.greeters.gtk.extraConfig = mkDefault (builtins.readFile "${pkgs.cinnamon.mint-artwork}/etc/lightdm/lightdm-gtk-greeter.conf.d/99_linuxmint.conf");
-      })
-
     (mkIf cfg.enable {
       services.xserver.displayManager.sessionPackages = [ pkgs.cinnamon.cinnamon-common ];
 
+      services.xserver.displayManager.lightdm.greeters.slick = {
+        enable = mkDefault true;
+
+        # Taken from mint-artwork.gschema.override
+        theme = mkIf (notExcluded pkgs.cinnamon.mint-themes) {
+          name = mkDefault "Mint-Y-Aqua";
+          package = mkDefault pkgs.cinnamon.mint-themes;
+        };
+        iconTheme = mkIf (notExcluded pkgs.cinnamon.mint-y-icons) {
+          name = mkDefault "Mint-Y-Aqua";
+          package = mkDefault pkgs.cinnamon.mint-y-icons;
+        };
+        cursorTheme = mkIf (notExcluded pkgs.cinnamon.mint-cursor-themes) {
+          name = mkDefault "Bibata-Modern-Classic";
+          package = mkDefault pkgs.cinnamon.mint-cursor-themes;
+        };
+      };
       services.xserver.displayManager.sessionCommands = ''
         if test "$XDG_CURRENT_DESKTOP" = "Cinnamon"; then
             true
@@ -91,10 +105,11 @@ in
       services.dbus.packages = with pkgs.cinnamon; [
         cinnamon-common
         cinnamon-screensaver
-        nemo
+        nemo-with-extensions
         xapp
       ];
       services.cinnamon.apps.enable = mkDefault true;
+      services.gnome.evolution-data-server.enable = true;
       services.gnome.glib-networking.enable = true;
       services.gnome.gnome-keyring.enable = true;
       services.gvfs.enable = true;
@@ -118,11 +133,8 @@ in
         cinnamon-screensaver = {};
       };
 
-      environment.systemPackages = with pkgs.cinnamon // pkgs; [
+      environment.systemPackages = with pkgs.cinnamon // pkgs; ([
         desktop-file-utils
-        nixos-artwork.wallpapers.simple-dark-gray
-        onboard
-        sound-theme-freedesktop
 
         # common-files
         cinnamon-common
@@ -143,27 +155,45 @@ in
         polkit_gnome
 
         # packages
-        nemo
+        nemo-with-extensions
         cinnamon-control-center
         cinnamon-settings-daemon
         libgnomekbd
-        orca
 
         # theme
         gnome.adwaita-icon-theme
-        hicolor-icon-theme
         gnome.gnome-themes-extra
         gtk3.out
-        mint-artwork
-        mint-themes
-        mint-x-icons
-        mint-y-icons
-        vanilla-dmz
 
         # other
         glib # for gsettings
-        shared-mime-info # for update-mime-database
         xdg-user-dirs
+      ] ++ utils.removePackagesByName [
+        # accessibility
+        onboard
+        orca
+
+        # theme
+        sound-theme-freedesktop
+        nixos-artwork.wallpapers.simple-dark-gray
+        mint-artwork
+        mint-cursor-themes
+        mint-themes
+        mint-x-icons
+        mint-y-icons
+        xapp # provides some xapp-* icons
+      ] config.environment.cinnamon.excludePackages);
+
+      xdg.mime.enable = true;
+      xdg.icons.enable = true;
+
+      xdg.portal.enable = true;
+      xdg.portal.extraPortals = [
+        pkgs.xdg-desktop-portal-xapp
+        (pkgs.xdg-desktop-portal-gtk.override {
+          # Do not build portals that we already have.
+          buildPortalsInGnome = false;
+        })
       ];
 
       # Override GSettings schemas
@@ -178,10 +208,10 @@ in
       programs.bash.vteIntegration = mkDefault true;
       programs.zsh.vteIntegration = mkDefault true;
 
-      # Harmonize Qt5 applications under Pantheon
-      qt5.enable = true;
-      qt5.platformTheme = "gnome";
-      qt5.style = "adwaita";
+      # Harmonize Qt applications under Cinnamon
+      qt.enable = true;
+      qt.platformTheme = "gnome";
+      qt.style = "adwaita";
 
       # Default Fonts
       fonts.fonts = with pkgs; [
@@ -194,7 +224,6 @@ in
       programs.geary.enable = mkDefault true;
       programs.gnome-disks.enable = mkDefault true;
       programs.gnome-terminal.enable = mkDefault true;
-      programs.evince.enable = mkDefault true;
       programs.file-roller.enable = mkDefault true;
 
       environment.systemPackages = with pkgs // pkgs.gnome // pkgs.cinnamon; utils.removePackagesByName [
@@ -212,6 +241,7 @@ in
         # external apps shipped with linux-mint
         hexchat
         gnome-calculator
+        gnome-calendar
         gnome-screenshot
       ] config.environment.cinnamon.excludePackages;
     })
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/deepin.nix
new file mode 100644
index 000000000000..70be6ed7c05e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.deepin;
+
+  nixos-gsettings-overrides = pkgs.deepin.dde-gsettings-schemas.override {
+    extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages;
+    extraGSettingsOverrides = cfg.extraGSettingsOverrides;
+  };
+in
+{
+  options = {
+
+    services.xserver.desktopManager.deepin = {
+      enable = mkEnableOption (lib.mdDoc "Enable Deepin desktop manager");
+      extraGSettingsOverrides = mkOption {
+        default = "";
+        type = types.lines;
+        description = lib.mdDoc "Additional gsettings overrides.";
+      };
+      extraGSettingsOverridePackages = mkOption {
+        default = [ ];
+        type = types.listOf types.path;
+        description = lib.mdDoc "List of packages for which gsettings are overridden.";
+      };
+    };
+
+    environment.deepin.excludePackages = mkOption {
+      default = [ ];
+      type = types.listOf types.package;
+      description = lib.mdDoc "List of default packages to exclude from the configuration";
+    };
+
+  };
+
+  config = mkIf cfg.enable
+    {
+      services.xserver.displayManager.sessionPackages = [ pkgs.deepin.startdde ];
+      services.xserver.displayManager.defaultSession = mkDefault "deepin";
+
+      # Update the DBus activation environment after launching the desktop manager.
+      services.xserver.displayManager.sessionCommands = ''
+        ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
+      '';
+
+      hardware.bluetooth.enable = mkDefault true;
+      hardware.pulseaudio.enable = mkDefault true;
+      security.polkit.enable = true;
+
+      services.deepin.dde-daemon.enable = mkForce true;
+      services.deepin.dde-api.enable = mkForce true;
+      services.deepin.app-services.enable = mkForce true;
+
+      services.colord.enable = mkDefault true;
+      services.accounts-daemon.enable = mkDefault true;
+      services.gvfs.enable = mkDefault true;
+      services.gnome.glib-networking.enable = mkDefault true;
+      services.gnome.gnome-keyring.enable = mkDefault true;
+      services.bamf.enable = mkDefault true;
+
+      services.xserver.libinput.enable = mkDefault true;
+      services.udisks2.enable = true;
+      services.upower.enable = mkDefault config.powerManagement.enable;
+      networking.networkmanager.enable = mkDefault true;
+      programs.dconf.enable = mkDefault true;
+
+      fonts.fonts = with pkgs; [ noto-fonts ];
+      xdg.mime.enable = true;
+      xdg.menus.enable = true;
+      xdg.icons.enable = true;
+      xdg.portal.enable = mkDefault true;
+      xdg.portal.extraPortals = mkDefault [
+        (pkgs.xdg-desktop-portal-gtk.override {
+          buildPortalsInGnome = false;
+        })
+      ];
+
+      environment.sessionVariables = {
+        NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+        DDE_POLKIT_AGENT_PLUGINS_DIRS = [ "${pkgs.deepin.dpa-ext-gnomekeyring}/lib/polkit-1-dde/plugins" ];
+      };
+
+      environment.pathsToLink = [
+        "/lib/dde-dock/plugins"
+        "/lib/dde-control-center"
+        "/lib/dde-session-shell"
+        "/lib/dde-file-manager"
+        "/share/backgrounds"
+        "/share/wallpapers"
+      ];
+
+      environment.etc = {
+        "distribution.info".text = ''
+          [Distribution]
+          Name=NixOS
+          WebsiteName=www.nixos.org
+          Website=https://www.nixos.org
+          Logo=${pkgs.nixos-icons}/share/icons/hicolor/96x96/apps/nix-snowflake.png
+          LogoLight=${pkgs.nixos-icons}/share/icons/hicolor/32x32/apps/nix-snowflake.png
+          LogoTransparent=${pkgs.deepin.deepin-desktop-base}/share/pixmaps/distribution_logo_transparent.svg
+        '';
+        "deepin-installer.conf".text = ''
+          system_info_vendor_name="Copyright (c) 2003-2023 NixOS contributors"
+        '';
+      };
+
+      systemd.tmpfiles.rules = [
+        "d /var/lib/AccountsService 0775 root root - -"
+        "C /var/lib/AccountsService/icons 0775 root root - ${pkgs.deepin.dde-account-faces}/var/lib/AccountsService/icons"
+      ];
+
+      security.pam.services.dde-lock.text = ''
+        # original at {dde-session-shell}/etc/pam.d/dde-lock
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+      '';
+
+      environment.systemPackages = with pkgs; with deepin;
+        let
+          requiredPackages = [
+            pciutils # for dtkcore/startdde
+            xdotool # for dde-daemon
+            glib # for gsettings program / gdbus
+            gtk3 # for gtk-launch program
+            xdg-user-dirs # Update user dirs
+            util-linux # runuser
+            polkit_gnome
+            librsvg # dde-api use rsvg-convert
+            lshw # for dtkcore
+            libsForQt5.kde-gtk-config # deepin-api/gtk-thumbnailer need
+            libsForQt5.kglobalaccel
+            xsettingsd # lightdm-deepin-greeter
+            qt5platform-plugins
+            deepin-pw-check
+            deepin-turbo
+
+            dde-account-faces
+            deepin-icon-theme
+            deepin-sound-theme
+            deepin-gtk-theme
+            deepin-wallpapers
+
+            startdde
+            dde-dock
+            dde-launcher
+            dde-session-ui
+            dde-session-shell
+            dde-file-manager
+            dde-control-center
+            dde-network-core
+            dde-clipboard
+            dde-calendar
+            dde-polkit-agent
+            dpa-ext-gnomekeyring
+            deepin-desktop-schemas
+            deepin-terminal
+            dde-kwin
+            deepin-kwin
+          ];
+          optionalPackages = [
+            onboard # dde-dock plugin
+            deepin-camera
+            deepin-calculator
+            deepin-compressor
+            deepin-editor
+            deepin-picker
+            deepin-draw
+            deepin-album
+            deepin-image-viewer
+            deepin-music
+            deepin-movie-reborn
+            deepin-system-monitor
+            deepin-screen-recorder
+            deepin-shortcut-viewer
+          ];
+        in
+        requiredPackages
+        ++ utils.removePackagesByName optionalPackages config.environment.deepin.excludePackages;
+
+      services.dbus.packages = with pkgs.deepin; [
+        dde-dock
+        dde-launcher
+        dde-session-ui
+        dde-session-shell
+        dde-file-manager
+        dde-control-center
+        dde-calendar
+        dde-clipboard
+        dde-kwin
+        deepin-kwin
+        deepin-pw-check
+      ];
+
+      systemd.packages = with pkgs.deepin; [
+        dde-launcher
+        dde-file-manager
+        dde-calendar
+        dde-clipboard
+        deepin-kwin
+      ];
+    };
+}
+
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
index a0fc3e4b5abb..66cb4ee29c0a 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
@@ -21,7 +21,7 @@ in
     ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./lumina.nix
     ./lxqt.nix ./enlightenment.nix ./gnome.nix ./retroarch.nix ./kodi.nix
     ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix
-    ./cinnamon.nix
+    ./cinnamon.nix ./budgie.nix ./deepin.nix
   ];
 
   options = {
@@ -64,10 +64,10 @@ in
             bgSupport = true;
             start = "...";
           };
-        description = ''
+        description = lib.mdDoc ''
           Internal option used to add some common line to desktop manager
           scripts before forwarding the value to the
-          <varname>displayManager</varname>.
+          `displayManager`.
         '';
         apply = map (d: d // {
           manage = "desktop";
@@ -86,8 +86,8 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "none";
-        description = ''
-          <emphasis role="strong">Deprecated</emphasis>, please use <xref linkend="opt-services.xserver.displayManager.defaultSession"/> instead.
+        description = lib.mdDoc ''
+          **Deprecated**, please use [](#opt-services.xserver.displayManager.defaultSession) instead.
 
           Default desktop manager loaded if none have been chosen.
         '';
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.md b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.md
new file mode 100644
index 000000000000..d9e75bfe6bdd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.md
@@ -0,0 +1,167 @@
+# GNOME Desktop {#chap-gnome}
+
+GNOME provides a simple, yet full-featured desktop environment with a focus on productivity. Its Mutter compositor supports both Wayland and X server, and the GNOME Shell user interface is fully customizable by extensions.
+
+## Enabling GNOME {#sec-gnome-enable}
+
+All of the core apps, optional apps, games, and core developer tools from GNOME are available.
+
+To enable the GNOME desktop use:
+
+```
+services.xserver.desktopManager.gnome.enable = true;
+services.xserver.displayManager.gdm.enable = true;
+```
+
+::: {.note}
+While it is not strictly necessary to use GDM as the display manager with GNOME, it is recommended, as some features such as screen lock [might not work](#sec-gnome-faq-can-i-use-lightdm-with-gnome) without it.
+:::
+
+The default applications used in NixOS are very minimal, inspired by the defaults used in [gnome-build-meta](https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/40.0/elements/core/meta-gnome-core-utilities.bst).
+
+### GNOME without the apps {#sec-gnome-without-the-apps}
+
+If you’d like to only use the GNOME desktop and not the apps, you can disable them with:
+
+```
+services.gnome.core-utilities.enable = false;
+```
+
+and none of them will be installed.
+
+If you’d only like to omit a subset of the core utilities, you can use
+[](#opt-environment.gnome.excludePackages).
+Note that this mechanism can only exclude core utilities, games and core developer tools.
+
+### Disabling GNOME services {#sec-gnome-disabling-services}
+
+It is also possible to disable many of the [core services](https://github.com/NixOS/nixpkgs/blob/b8ec4fd2a4edc4e30d02ba7b1a2cc1358f3db1d5/nixos/modules/services/x11/desktop-managers/gnome.nix#L329-L348). For example, if you do not need indexing files, you can disable Tracker with:
+
+```
+services.gnome.tracker-miners.enable = false;
+services.gnome.tracker.enable = false;
+```
+
+Note, however, that doing so is not supported and might break some applications. Notably, GNOME Music cannot work without Tracker.
+
+### GNOME games {#sec-gnome-games}
+
+You can install all of the GNOME games with:
+
+```
+services.gnome.games.enable = true;
+```
+
+### GNOME core developer tools {#sec-gnome-core-developer-tools}
+
+You can install GNOME core developer tools with:
+
+```
+services.gnome.core-developer-tools.enable = true;
+```
+
+## Enabling GNOME Flashback {#sec-gnome-enable-flashback}
+
+GNOME Flashback provides a desktop environment based on the classic GNOME 2 architecture. You can enable the default GNOME Flashback session, which uses the Metacity window manager, with:
+
+```
+services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
+```
+
+It is also possible to create custom sessions that replace Metacity with a different window manager using [](#opt-services.xserver.desktopManager.gnome.flashback.customSessions).
+
+The following example uses `xmonad` window manager:
+
+```
+services.xserver.desktopManager.gnome.flashback.customSessions = [
+  {
+    wmName = "xmonad";
+    wmLabel = "XMonad";
+    wmCommand = "${pkgs.haskellPackages.xmonad}/bin/xmonad";
+    enableGnomePanel = false;
+  }
+];
+```
+
+## Icons and GTK Themes {#sec-gnome-icons-and-gtk-themes}
+
+Icon themes and GTK themes don’t require any special option to install in NixOS.
+
+You can add them to [](#opt-environment.systemPackages) and switch to them with GNOME Tweaks.
+If you’d like to do this manually in dconf, change the values of the following keys:
+
+```
+/org/gnome/desktop/interface/gtk-theme
+/org/gnome/desktop/interface/icon-theme
+```
+
+in `dconf-editor`
+
+## Shell Extensions {#sec-gnome-shell-extensions}
+
+Most Shell extensions are packaged under the `gnomeExtensions` attribute.
+Some packages that include Shell extensions, like `gnome.gpaste`, don’t have their extension decoupled under this attribute.
+
+You can install them like any other package:
+
+```
+environment.systemPackages = [
+  gnomeExtensions.dash-to-dock
+  gnomeExtensions.gsconnect
+  gnomeExtensions.mpris-indicator-button
+];
+```
+
+Unfortunately, we lack a way for these to be managed in a completely declarative way.
+So you have to enable them manually with an Extensions application.
+It is possible to use a [GSettings override](#sec-gnome-gsettings-overrides) for this on `org.gnome.shell.enabled-extensions`, but that will only influence the default value.
+
+## GSettings Overrides {#sec-gnome-gsettings-overrides}
+
+Majority of software building on the GNOME platform use GLib’s [GSettings](https://developer.gnome.org/gio/unstable/GSettings.html) system to manage runtime configuration. For our purposes, the system consists of XML schemas describing the individual configuration options, stored in the package, and a settings backend, where the values of the settings are stored. On NixOS, like on most Linux distributions, dconf database is used as the backend.
+
+[GSettings vendor overrides](https://developer.gnome.org/gio/unstable/GSettings.html#id-1.4.19.2.9.25) can be used to adjust the default values for settings of the GNOME desktop and apps by replacing the default values specified in the XML schemas. Using overrides will allow you to pre-seed user settings before you even start the session.
+
+::: {.warning}
+Overrides really only change the default values for GSettings keys so if you or an application changes the setting value, the value set by the override will be ignored. Until [NixOS’s dconf module implements changing values](https://github.com/NixOS/nixpkgs/issues/54150), you will either need to keep that in mind and clear the setting from the backend using `dconf reset` command when that happens, or use the [module from home-manager](https://nix-community.github.io/home-manager/options.html#opt-dconf.settings).
+:::
+
+You can override the default GSettings values using the
+[](#opt-services.xserver.desktopManager.gnome.extraGSettingsOverrides) option.
+
+Take note that whatever packages you want to override GSettings for, you need to add them to
+[](#opt-services.xserver.desktopManager.gnome.extraGSettingsOverridePackages).
+
+You can use `dconf-editor` tool to explore which GSettings you can set.
+
+### Example {#sec-gnome-gsettings-overrides-example}
+
+```
+services.xserver.desktopManager.gnome = {
+  extraGSettingsOverrides = ''
+    # Change default background
+    [org.gnome.desktop.background]
+    picture-uri='file://${pkgs.nixos-artwork.wallpapers.mosaic-blue.gnomeFilePath}'
+
+    # Favorite apps in gnome-shell
+    [org.gnome.shell]
+    favorite-apps=['org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop']
+  '';
+
+  extraGSettingsOverridePackages = [
+    pkgs.gsettings-desktop-schemas # for org.gnome.desktop
+    pkgs.gnome.gnome-shell # for org.gnome.shell
+  ];
+};
+```
+
+## Frequently Asked Questions {#sec-gnome-faq}
+
+### Can I use LightDM with GNOME? {#sec-gnome-faq-can-i-use-lightdm-with-gnome}
+
+Yes you can, and any other display-manager in NixOS.
+
+However, it doesn’t work correctly for the Wayland session of GNOME Shell yet, and
+won’t be able to lock your screen.
+
+See [this issue.](https://github.com/NixOS/nixpkgs/issues/56342)
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix
index b69102f046a2..79b2e7c6ead7 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -22,42 +22,14 @@ let
     favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Calendar.desktop', 'org.gnome.Music.desktop', 'org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop' ]
   '';
 
-  nixos-background-ligtht = pkgs.nixos-artwork.wallpapers.simple-blue;
+  nixos-background-light = pkgs.nixos-artwork.wallpapers.simple-blue;
   nixos-background-dark = pkgs.nixos-artwork.wallpapers.simple-dark-gray;
 
-  nixos-gsettings-desktop-schemas = let
-    defaultPackages = with pkgs; [ gsettings-desktop-schemas gnome.gnome-shell ];
-  in
-  pkgs.runCommand "nixos-gsettings-desktop-schemas" { preferLocalBuild = true; }
-    ''
-     mkdir -p $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
-
-     ${concatMapStrings
-        (pkg: "cp -rf ${pkg}/share/gsettings-schemas/*/glib-2.0/schemas/*.xml $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas\n")
-        (defaultPackages ++ cfg.extraGSettingsOverridePackages)}
-
-     cp -f ${pkgs.gnome.gnome-shell}/share/gsettings-schemas/*/glib-2.0/schemas/*.gschema.override $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
-
-     ${optionalString flashbackEnabled ''
-       cp -f ${pkgs.gnome.gnome-flashback}/share/gsettings-schemas/*/glib-2.0/schemas/*.gschema.override $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
-     ''}
-
-     chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides
-     cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF
-       [org.gnome.desktop.background]
-       picture-uri='file://${nixos-background-ligtht.gnomeFilePath}'
-       picture-uri-dark='file://${nixos-background-dark.gnomeFilePath}'
-
-       [org.gnome.desktop.screensaver]
-       picture-uri='file://${nixos-background-dark.gnomeFilePath}'
-
-       ${cfg.favoriteAppsOverride}
-
-       ${cfg.extraGSettingsOverrides}
-     EOF
-
-     ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/
-    '';
+  # TODO: Having https://github.com/NixOS/nixpkgs/issues/54150 would supersede this
+  nixos-gsettings-desktop-schemas = pkgs.gnome.nixos-gsettings-overrides.override {
+    inherit (cfg) extraGSettingsOverrides extraGSettingsOverridePackages favoriteAppsOverride;
+    inherit flashbackEnabled nixos-background-dark nixos-background-light;
+  };
 
   nixos-background-info = pkgs.writeTextFile rec {
     name = "nixos-background-info";
@@ -67,7 +39,7 @@ let
       <wallpapers>
         <wallpaper deleted="false">
           <name>Blobs</name>
-          <filename>${nixos-background-ligtht.gnomeFilePath}</filename>
+          <filename>${nixos-background-light.gnomeFilePath}</filename>
           <filename-dark>${nixos-background-dark.gnomeFilePath}</filename-dark>
           <options>zoom</options>
           <shade_type>solid</shade_type>
@@ -94,7 +66,7 @@ in
 {
 
   meta = {
-    doc = ./gnome.xml;
+    doc = ./gnome.md;
     maintainers = teams.gnome.members;
   };
 
@@ -165,11 +137,11 @@ in
   options = {
 
     services.gnome = {
-      core-os-services.enable = mkEnableOption "essential services for GNOME3";
-      core-shell.enable = mkEnableOption "GNOME Shell services";
-      core-utilities.enable = mkEnableOption "GNOME core utilities";
-      core-developer-tools.enable = mkEnableOption "GNOME core developer tools";
-      games.enable = mkEnableOption "GNOME games";
+      core-os-services.enable = mkEnableOption (lib.mdDoc "essential services for GNOME3");
+      core-shell.enable = mkEnableOption (lib.mdDoc "GNOME Shell services");
+      core-utilities.enable = mkEnableOption (lib.mdDoc "GNOME core utilities");
+      core-developer-tools.enable = mkEnableOption (lib.mdDoc "GNOME core developer tools");
+      games.enable = mkEnableOption (lib.mdDoc "GNOME games");
     };
 
     services.xserver.desktopManager.gnome = {
@@ -201,7 +173,7 @@ in
             favorite-apps=[ 'firefox.desktop', 'org.gnome.Calendar.desktop' ]
           '''
         '';
-        description = "List of desktop files to put as favorite apps into gnome-shell. These need to be installed somehow globally.";
+        description = lib.mdDoc "List of desktop files to put as favorite apps into gnome-shell. These need to be installed somehow globally.";
       };
 
       extraGSettingsOverrides = mkOption {
@@ -216,10 +188,10 @@ in
         description = lib.mdDoc "List of packages for which gsettings are overridden.";
       };
 
-      debug = mkEnableOption "gnome-session debug messages";
+      debug = mkEnableOption (lib.mdDoc "gnome-session debug messages");
 
       flashback = {
-        enableMetacity = mkEnableOption "the standard GNOME Flashback session with Metacity";
+        enableMetacity = mkEnableOption (lib.mdDoc "the standard GNOME Flashback session with Metacity");
 
         customSessions = mkOption {
           type = types.listOf (types.submodule {
@@ -380,8 +352,8 @@ in
         })
       ];
 
-      # Harmonize Qt5 application style and also make them use the portal for file chooser dialog.
-      qt5 = {
+      # Harmonize Qt application style and also make them use the portal for file chooser dialog.
+      qt = {
         enable = mkDefault true;
         platformTheme = mkDefault "gnome";
         style = mkDefault "adwaita";
@@ -417,8 +389,8 @@ in
         ++ utils.removePackagesByName optionalPackages config.environment.gnome.excludePackages;
 
       services.colord.enable = mkDefault true;
-      services.gnome.chrome-gnome-shell.enable = mkDefault true;
       services.gnome.glib-networking.enable = true;
+      services.gnome.gnome-browser-connector.enable = mkDefault true;
       services.gnome.gnome-initial-setup.enable = mkDefault true;
       services.gnome.gnome-remote-desktop.enable = mkDefault true;
       services.gnome.gnome-settings-daemon.enable = true;
@@ -548,7 +520,7 @@ in
 
       # Let nautilus find extensions
       # TODO: Create nautilus-with-extensions package
-      environment.sessionVariables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
+      environment.sessionVariables.NAUTILUS_4_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-4";
 
       # Override default mimeapps for nautilus
       environment.sessionVariables.XDG_DATA_DIRS = [ "${mimeAppsList}/share" ];
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml
deleted file mode 100644
index 807c9d64e204..000000000000
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml
+++ /dev/null
@@ -1,253 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xml:id="chap-gnome">
- <title>GNOME Desktop</title>
- <para>
-  GNOME provides a simple, yet full-featured desktop environment with a focus on productivity. Its Mutter compositor supports both Wayland and X server, and the GNOME Shell user interface is fully customizable by extensions.
- </para>
-
- <section xml:id="sec-gnome-enable">
-  <title>Enabling GNOME</title>
-
-  <para>
-   All of the core apps, optional apps, games, and core developer tools from GNOME are available.
-  </para>
-
-  <para>
-   To enable the GNOME desktop use:
-  </para>
-
-<programlisting>
-<xref linkend="opt-services.xserver.desktopManager.gnome.enable"/> = true;
-<xref linkend="opt-services.xserver.displayManager.gdm.enable"/> = true;
-</programlisting>
-
-  <note>
-   <para>
-    While it is not strictly necessary to use GDM as the display manager with GNOME, it is recommended, as some features such as screen lock <link xlink:href="#sec-gnome-faq-can-i-use-lightdm-with-gnome">might not work</link> without it.
-   </para>
-  </note>
-
-  <para>
-   The default applications used in NixOS are very minimal, inspired by the defaults used in <link xlink:href="https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/40.0/elements/core/meta-gnome-core-utilities.bst">gnome-build-meta</link>.
-  </para>
-
-  <section xml:id="sec-gnome-without-the-apps">
-   <title>GNOME without the apps</title>
-
-   <para>
-    If you’d like to only use the GNOME desktop and not the apps, you can disable them with:
-   </para>
-
-<programlisting>
-<xref linkend="opt-services.gnome.core-utilities.enable"/> = false;
-</programlisting>
-
-   <para>
-    and none of them will be installed.
-   </para>
-
-   <para>
-    If you’d only like to omit a subset of the core utilities, you can use <xref linkend="opt-environment.gnome.excludePackages"/>.
-    Note that this mechanism can only exclude core utilities, games and core developer tools.
-   </para>
-  </section>
-
-  <section xml:id="sec-gnome-disabling-services">
-   <title>Disabling GNOME services</title>
-
-   <para>
-    It is also possible to disable many of the <link xlink:href="https://github.com/NixOS/nixpkgs/blob/b8ec4fd2a4edc4e30d02ba7b1a2cc1358f3db1d5/nixos/modules/services/x11/desktop-managers/gnome.nix#L329-L348">core services</link>. For example, if you do not need indexing files, you can disable Tracker with:
-   </para>
-
-<programlisting>
-<xref linkend="opt-services.gnome.tracker-miners.enable"/> = false;
-<xref linkend="opt-services.gnome.tracker.enable"/> = false;
-</programlisting>
-
-   <para>
-    Note, however, that doing so is not supported and might break some applications. Notably, GNOME Music cannot work without Tracker.
-   </para>
-  </section>
-
-  <section xml:id="sec-gnome-games">
-   <title>GNOME games</title>
-
-   <para>
-    You can install all of the GNOME games with:
-   </para>
-
-<programlisting>
-<xref linkend="opt-services.gnome.games.enable"/> = true;
-</programlisting>
-  </section>
-
-  <section xml:id="sec-gnome-core-developer-tools">
-   <title>GNOME core developer tools</title>
-
-   <para>
-    You can install GNOME core developer tools with:
-   </para>
-
-<programlisting>
-<xref linkend="opt-services.gnome.core-developer-tools.enable"/> = true;
-</programlisting>
-  </section>
- </section>
-
- <section xml:id="sec-gnome-enable-flashback">
-  <title>Enabling GNOME Flashback</title>
-
-  <para>
-   GNOME Flashback provides a desktop environment based on the classic GNOME 2 architecture. You can enable the default GNOME Flashback session, which uses the Metacity window manager, with:
-  </para>
-
-<programlisting>
-<xref linkend="opt-services.xserver.desktopManager.gnome.flashback.enableMetacity"/> = true;
-</programlisting>
-
-  <para>
-   It is also possible to create custom sessions that replace Metacity with a different window manager using <xref linkend="opt-services.xserver.desktopManager.gnome.flashback.customSessions"/>.
-  </para>
-
-  <para>
-   The following example uses <literal>xmonad</literal> window manager:
-  </para>
-
-<programlisting>
-<xref linkend="opt-services.xserver.desktopManager.gnome.flashback.customSessions"/> = [
-  {
-    wmName = "xmonad";
-    wmLabel = "XMonad";
-    wmCommand = "${pkgs.haskellPackages.xmonad}/bin/xmonad";
-    enableGnomePanel = false;
-  }
-];
-</programlisting>
-
- </section>
-
- <section xml:id="sec-gnome-icons-and-gtk-themes">
-  <title>Icons and GTK Themes</title>
-
-  <para>
-   Icon themes and GTK themes don’t require any special option to install in NixOS.
-  </para>
-
-  <para>
-   You can add them to <xref linkend="opt-environment.systemPackages"/> and switch to them with GNOME Tweaks.
-   If you’d like to do this manually in dconf, change the values of the following keys:
-  </para>
-
-<programlisting>
-/org/gnome/desktop/interface/gtk-theme
-/org/gnome/desktop/interface/icon-theme
-</programlisting>
-
-  <para>
-   in <literal>dconf-editor</literal>
-  </para>
- </section>
-
- <section xml:id="sec-gnome-shell-extensions">
-  <title>Shell Extensions</title>
-
-  <para>
-   Most Shell extensions are packaged under the <literal>gnomeExtensions</literal> attribute.
-   Some packages that include Shell extensions, like <literal>gnome.gpaste</literal>, don’t have their extension decoupled under this attribute.
-  </para>
-
-  <para>
-   You can install them like any other package:
-  </para>
-
-<programlisting>
-<xref linkend="opt-environment.systemPackages"/> = [
-  gnomeExtensions.dash-to-dock
-  gnomeExtensions.gsconnect
-  gnomeExtensions.mpris-indicator-button
-];
-</programlisting>
-
-  <para>
-   Unfortunately, we lack a way for these to be managed in a completely declarative way.
-   So you have to enable them manually with an Extensions application.
-   It is possible to use a <link xlink:href="#sec-gnome-gsettings-overrides">GSettings override</link> for this on <literal>org.gnome.shell.enabled-extensions</literal>, but that will only influence the default value.
-  </para>
- </section>
-
- <section xml:id="sec-gnome-gsettings-overrides">
-  <title>GSettings Overrides</title>
-
-  <para>
-   Majority of software building on the GNOME platform use GLib’s <link xlink:href="https://developer.gnome.org/gio/unstable/GSettings.html">GSettings</link> system to manage runtime configuration. For our purposes, the system consists of XML schemas describing the individual configuration options, stored in the package, and a settings backend, where the values of the settings are stored. On NixOS, like on most Linux distributions, dconf database is used as the backend.
-  </para>
-
-  <para>
-   <link xlink:href="https://developer.gnome.org/gio/unstable/GSettings.html#id-1.4.19.2.9.25">GSettings vendor overrides</link> can be used to adjust the default values for settings of the GNOME desktop and apps by replacing the default values specified in the XML schemas. Using overrides will allow you to pre-seed user settings before you even start the session.
-  </para>
-
-  <warning>
-   <para>
-    Overrides really only change the default values for GSettings keys so if you or an application changes the setting value, the value set by the override will be ignored. Until <link xlink:href="https://github.com/NixOS/nixpkgs/issues/54150">NixOS’s dconf module implements changing values</link>, you will either need to keep that in mind and clear the setting from the backend using <literal>dconf reset</literal> command when that happens, or use the <link xlink:href="https://nix-community.github.io/home-manager/options.html#opt-dconf.settings">module from home-manager</link>.
-   </para>
-  </warning>
-
-  <para>
-   You can override the default GSettings values using the <xref linkend="opt-services.xserver.desktopManager.gnome.extraGSettingsOverrides"/> option.
-  </para>
-
-  <para>
-   Take note that whatever packages you want to override GSettings for, you need to add them to
-   <xref linkend="opt-services.xserver.desktopManager.gnome.extraGSettingsOverridePackages"/>.
-  </para>
-
-  <para>
-   You can use <literal>dconf-editor</literal> tool to explore which GSettings you can set.
-  </para>
-
-  <section xml:id="sec-gnome-gsettings-overrides-example">
-   <title>Example</title>
-
-<programlisting>
-services.xserver.desktopManager.gnome = {
-  <link xlink:href="#opt-services.xserver.desktopManager.gnome.extraGSettingsOverrides">extraGSettingsOverrides</link> = ''
-    # Change default background
-    [org.gnome.desktop.background]
-    picture-uri='file://${pkgs.nixos-artwork.wallpapers.mosaic-blue.gnomeFilePath}'
-
-    # Favorite apps in gnome-shell
-    [org.gnome.shell]
-    favorite-apps=['org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop']
-  '';
-
-  <link xlink:href="#opt-services.xserver.desktopManager.gnome.extraGSettingsOverridePackages">extraGSettingsOverridePackages</link> = [
-    pkgs.gsettings-desktop-schemas # for org.gnome.desktop
-    pkgs.gnome.gnome-shell # for org.gnome.shell
-  ];
-};
-</programlisting>
-  </section>
- </section>
-
- <section xml:id="sec-gnome-faq">
-  <title>Frequently Asked Questions</title>
-
-  <section xml:id="sec-gnome-faq-can-i-use-lightdm-with-gnome">
-   <title>Can I use LightDM with GNOME?</title>
-
-   <para>
-    Yes you can, and any other display-manager in NixOS.
-   </para>
-
-   <para>
-    However, it doesn’t work correctly for the Wayland session of GNOME Shell yet, and
-    won’t be able to lock your screen.
-   </para>
-
-   <para>
-    See <link xlink:href="https://github.com/NixOS/nixpkgs/issues/56342">this issue.</link>
-   </para>
-  </section>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
index 1ca47313adc3..c93f120bed7f 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -19,7 +19,7 @@ in
         description = lib.mdDoc "Enable the MATE desktop environment";
       };
 
-      debug = mkEnableOption "mate-session debug messages";
+      debug = mkEnableOption (lib.mdDoc "mate-session debug messages");
     };
 
     environment.mate.excludePackages = mkOption {
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
index b5e498b67a01..074b729cc3f3 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
@@ -8,16 +8,16 @@ in
     services.xserver.desktopManager.runXdgAutostartIfNone = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to run XDG autostart files for sessions without a desktop manager
         (with only a window manager), these sessions usually don't handle XDG
         autostart files by default.
 
-        Some services like <option>i18n.inputMethod</option> and
-        <option>service.earlyoom</option> use XDG autostart files to start.
-        If this option is not set to <literal>true</literal> and you are using
+        Some services like {option}`i18n.inputMethod` and
+        {option}`service.earlyoom` use XDG autostart files to start.
+        If this option is not set to `true` and you are using
         a window manager without a desktop manager, you need to manually start
-        them or running <package>dex</package> somewhere.
+        them or running `dex` somewhere.
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.md b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.md
new file mode 100644
index 000000000000..1c14ede84749
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.md
@@ -0,0 +1,74 @@
+# Pantheon Desktop {#chap-pantheon}
+
+Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK and Granite.
+
+## Enabling Pantheon {#sec-pantheon-enable}
+
+All of Pantheon is working in NixOS and the applications should be available, aside from a few [exceptions](https://github.com/NixOS/nixpkgs/issues/58161). To enable Pantheon, set
+```
+services.xserver.desktopManager.pantheon.enable = true;
+```
+This automatically enables LightDM and Pantheon's LightDM greeter. If you'd like to disable this, set
+```
+services.xserver.displayManager.lightdm.greeters.pantheon.enable = false;
+services.xserver.displayManager.lightdm.enable = false;
+```
+but please be aware using Pantheon without LightDM as a display manager will break screenlocking from the UI. The NixOS module for Pantheon installs all of Pantheon's default applications. If you'd like to not install Pantheon's apps, set
+```
+services.pantheon.apps.enable = false;
+```
+You can also use [](#opt-environment.pantheon.excludePackages) to remove any other app (like `elementary-mail`).
+
+## Wingpanel and Switchboard plugins {#sec-pantheon-wingpanel-switchboard}
+
+Wingpanel and Switchboard work differently than they do in other distributions, as far as using plugins. You cannot install a plugin globally (like with {option}`environment.systemPackages`) to start using it. You should instead be using the following options:
+
+  - [](#opt-services.xserver.desktopManager.pantheon.extraWingpanelIndicators)
+  - [](#opt-services.xserver.desktopManager.pantheon.extraSwitchboardPlugs)
+
+to configure the programs with plugs or indicators.
+
+The difference in NixOS is both these programs are patched to load plugins from a directory that is the value of an environment variable. All of which is controlled in Nix. If you need to configure the particular packages manually you can override the packages like:
+```
+wingpanel-with-indicators.override {
+  indicators = [
+    pkgs.some-special-indicator
+  ];
+};
+
+switchboard-with-plugs.override {
+  plugs = [
+    pkgs.some-special-plug
+  ];
+};
+```
+please note that, like how the NixOS options describe these as extra plugins, this would only add to the default plugins included with the programs. If for some reason you'd like to configure which plugins to use exactly, both packages have an argument for this:
+```
+wingpanel-with-indicators.override {
+  useDefaultIndicators = false;
+  indicators = specialListOfIndicators;
+};
+
+switchboard-with-plugs.override {
+  useDefaultPlugs = false;
+  plugs = specialListOfPlugs;
+};
+```
+this could be most useful for testing a particular plug-in in isolation.
+
+## FAQ {#sec-pantheon-faq}
+
+[I have switched from a different desktop and Pantheon’s theming looks messed up.]{#sec-pantheon-faq-messed-up-theme}
+  : Open Switchboard and go to: Administration → About → Restore Default Settings → Restore Settings. This will reset any dconf settings to their Pantheon defaults. Note this could reset certain GNOME specific preferences if that desktop was used prior.
+
+[I cannot enable both GNOME and Pantheon.]{#sec-pantheon-faq-gnome-and-pantheon}
+  : This is a known [issue](https://github.com/NixOS/nixpkgs/issues/64611) and there is no known workaround.
+
+[Does AppCenter work, or is it available?]{#sec-pantheon-faq-appcenter}
+  : AppCenter has been available since 20.03. Starting from 21.11, the Flatpak backend should work so you can install some Flatpak applications using it. However, due to missing appstream metadata, the Packagekit backend does not function currently. See this [issue](https://github.com/NixOS/nixpkgs/issues/15932).
+
+    If you are using Pantheon, AppCenter should be installed by default if you have [Flatpak support](#module-services-flatpak) enabled. If you also wish to add the `appcenter` Flatpak remote:
+
+    ```ShellSession
+    $ flatpak remote-add --if-not-exists appcenter https://flatpak.elementary.io/repo.flatpakrepo
+    ```
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 94de7f4dd7db..c61c53642f4b 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -17,7 +17,7 @@ in
 {
 
   meta = {
-    doc = ./pantheon.xml;
+    doc = ./pantheon.md;
     maintainers = teams.pantheon.members;
   };
 
@@ -26,10 +26,10 @@ in
     services.pantheon = {
 
       contractor = {
-         enable = mkEnableOption "contractor, a desktop-wide extension service used by Pantheon";
+         enable = mkEnableOption (lib.mdDoc "contractor, a desktop-wide extension service used by Pantheon");
       };
 
-      apps.enable = mkEnableOption "Pantheon default applications";
+      apps.enable = mkEnableOption (lib.mdDoc "Pantheon default applications");
 
     };
 
@@ -76,7 +76,7 @@ in
         description = lib.mdDoc "List of packages for which gsettings are overridden.";
       };
 
-      debug = mkEnableOption "gnome-session debug messages";
+      debug = mkEnableOption (lib.mdDoc "gnome-session debug messages");
 
     };
 
@@ -169,6 +169,9 @@ in
       };
       services.udev.packages = [
         pkgs.pantheon.gnome-settings-daemon
+        # Force enable KMS modifiers for devices that require them.
+        # https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1443
+        pkgs.pantheon.mutter
       ];
       systemd.packages = [
         pkgs.pantheon.gnome-settings-daemon
@@ -224,11 +227,16 @@ in
       xdg.icons.enable = true;
 
       xdg.portal.enable = true;
-      xdg.portal.extraPortals = with pkgs.pantheon; [
+      xdg.portal.extraPortals = [
+        # Some Pantheon apps enforce portal usage, we need this for e.g. notifications.
+        # Currently we have buildPortalsInGnome enabled, if you run into issues related
+        # to https://github.com/flatpak/xdg-desktop-portal/issues/656 please report to us.
+        pkgs.xdg-desktop-portal-gtk
+      ] ++ (with pkgs.pantheon; [
         elementary-files
         elementary-settings-daemon
         xdg-desktop-portal-pantheon
-      ];
+      ]);
 
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
@@ -250,10 +258,10 @@ in
       programs.bash.vteIntegration = mkDefault true;
       programs.zsh.vteIntegration = mkDefault true;
 
-      # Harmonize Qt5 applications under Pantheon
-      qt5.enable = true;
-      qt5.platformTheme = "gnome";
-      qt5.style = "adwaita";
+      # Harmonize Qt applications under Pantheon
+      qt.enable = true;
+      qt.platformTheme = "gnome";
+      qt.style = "adwaita";
 
       # Default Fonts
       fonts.fonts = with pkgs; [
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml
deleted file mode 100644
index 6226f8f6a272..000000000000
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xml:id="chap-pantheon">
- <title>Pantheon Desktop</title>
- <para>
-  Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK and Granite.
- </para>
- <section xml:id="sec-pantheon-enable">
-  <title>Enabling Pantheon</title>
-
-  <para>
-   All of Pantheon is working in NixOS and the applications should be available, aside from a few <link xlink:href="https://github.com/NixOS/nixpkgs/issues/58161">exceptions</link>. To enable Pantheon, set
-<programlisting>
-<xref linkend="opt-services.xserver.desktopManager.pantheon.enable"/> = true;
-</programlisting>
-   This automatically enables LightDM and Pantheon's LightDM greeter. If you'd like to disable this, set
-<programlisting>
-<xref linkend="opt-services.xserver.displayManager.lightdm.greeters.pantheon.enable"/> = false;
-<xref linkend="opt-services.xserver.displayManager.lightdm.enable"/> = false;
-</programlisting>
-   but please be aware using Pantheon without LightDM as a display manager will break screenlocking from the UI. The NixOS module for Pantheon installs all of Pantheon's default applications. If you'd like to not install Pantheon's apps, set
-<programlisting>
-<xref linkend="opt-services.pantheon.apps.enable"/> = false;
-</programlisting>
-   You can also use <xref linkend="opt-environment.pantheon.excludePackages"/> to remove any other app (like <package>elementary-mail</package>).
-  </para>
- </section>
- <section xml:id="sec-pantheon-wingpanel-switchboard">
-  <title>Wingpanel and Switchboard plugins</title>
-
-  <para>
-   Wingpanel and Switchboard work differently than they do in other distributions, as far as using plugins. You cannot install a plugin globally (like with <option>environment.systemPackages</option>) to start using it. You should instead be using the following options:
-   <itemizedlist>
-    <listitem>
-     <para>
-      <xref linkend="opt-services.xserver.desktopManager.pantheon.extraWingpanelIndicators"/>
-     </para>
-    </listitem>
-    <listitem>
-     <para>
-      <xref linkend="opt-services.xserver.desktopManager.pantheon.extraSwitchboardPlugs"/>
-     </para>
-    </listitem>
-   </itemizedlist>
-   to configure the programs with plugs or indicators.
-  </para>
-
-  <para>
-   The difference in NixOS is both these programs are patched to load plugins from a directory that is the value of an environment variable. All of which is controlled in Nix. If you need to configure the particular packages manually you can override the packages like:
-<programlisting>
-wingpanel-with-indicators.override {
-  indicators = [
-    pkgs.some-special-indicator
-  ];
-};
-
-switchboard-with-plugs.override {
-  plugs = [
-    pkgs.some-special-plug
-  ];
-};
-</programlisting>
-   please note that, like how the NixOS options describe these as extra plugins, this would only add to the default plugins included with the programs. If for some reason you'd like to configure which plugins to use exactly, both packages have an argument for this:
-<programlisting>
-wingpanel-with-indicators.override {
-  useDefaultIndicators = false;
-  indicators = specialListOfIndicators;
-};
-
-switchboard-with-plugs.override {
-  useDefaultPlugs = false;
-  plugs = specialListOfPlugs;
-};
-</programlisting>
-   this could be most useful for testing a particular plug-in in isolation.
-  </para>
- </section>
- <section xml:id="sec-pantheon-faq">
-  <title>FAQ</title>
-
-  <variablelist>
-   <varlistentry xml:id="sec-pantheon-faq-messed-up-theme">
-    <term>
-     I have switched from a different desktop and Pantheon’s theming looks messed up.
-    </term>
-    <listitem>
-     <para>
-      Open Switchboard and go to: <guilabel>Administration</guilabel> → <guilabel>About</guilabel> → <guilabel>Restore Default Settings</guilabel> → <guibutton>Restore Settings</guibutton>. This will reset any dconf settings to their Pantheon defaults. Note this could reset certain GNOME specific preferences if that desktop was used prior.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry xml:id="sec-pantheon-faq-gnome-and-pantheon">
-    <term>
-     I cannot enable both GNOME and Pantheon.
-    </term>
-    <listitem>
-     <para>
-      This is a known <link xlink:href="https://github.com/NixOS/nixpkgs/issues/64611">issue</link> and there is no known workaround.
-     </para>
-    </listitem>
-   </varlistentry>
-   <varlistentry xml:id="sec-pantheon-faq-appcenter">
-    <term>
-     Does AppCenter work, or is it available?
-    </term>
-    <listitem>
-     <para>
-      AppCenter has been available since 20.03. Starting from 21.11, the Flatpak backend should work so you can install some Flatpak applications using it. However, due to missing appstream metadata, the Packagekit backend does not function currently. See this <link xlink:href="https://github.com/NixOS/nixpkgs/issues/15932">issue</link>.
-     </para>
-     <para>
-      If you are using Pantheon, AppCenter should be installed by default if you have <link linkend="module-services-flatpak">Flatpak support</link> enabled. If you also wish to add the <literal>appcenter</literal> Flatpak remote:
-     </para>
-<screen>
-<prompt>$ </prompt>flatpak remote-add --if-not-exists appcenter https://flatpak.elementary.io/repo.flatpakrepo
-</screen>
-    </listitem>
-   </varlistentry>
-  </variablelist>
- </section>
-</chapter>
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/phosh.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/phosh.nix
index 0ff5d6fd1b11..3cfa6e044b73 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/phosh.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/phosh.nix
@@ -24,7 +24,7 @@ let
   phocConfigType = types.submodule {
     options = {
       xwayland = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable XWayland support.
 
           To start XWayland immediately, use `immediate`.
@@ -33,14 +33,14 @@ let
         default = "false";
       };
       cursorTheme = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Cursor theme to use in Phosh.
         '';
         type = types.str;
         default = "default";
       };
       outputs = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Output configurations.
         '';
         type = types.attrsOf phocOutputType;
@@ -56,7 +56,7 @@ let
   phocOutputType = types.submodule {
     options = {
       modeline = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           One or more modelines.
         '';
         type = types.either types.str (types.listOf types.str);
@@ -67,7 +67,7 @@ let
         ];
       };
       mode = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Default video mode.
         '';
         type = types.nullOr types.str;
@@ -75,7 +75,7 @@ let
         example = "768x1024";
       };
       scale = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Display scaling factor.
         '';
         type = types.nullOr (
@@ -89,7 +89,7 @@ let
         example = 2;
       };
       rotate = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Screen transformation.
         '';
         type = types.enum [
@@ -173,7 +173,7 @@ in
     systemd.services.phosh = {
       wantedBy = [ "graphical.target" ];
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/phosh";
+        ExecStart = "${cfg.package}/bin/phosh-session";
         User = cfg.user;
         Group = cfg.group;
         PAMName = "login";
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 2c4b0b8f3ba3..38f932ffb420 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -28,51 +28,10 @@ let
 
   libsForQt5 = pkgs.plasma5Packages;
   inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
-  inherit (pkgs) writeText;
   inherit (lib)
     getBin optionalString literalExpression
     mkRemovedOptionModule mkRenamedOptionModule
-    mkDefault mkIf mkMerge mkOption types;
-
-  ini = pkgs.formats.ini { };
-
-  gtkrc2 = writeText "gtkrc-2.0" ''
-    # Default GTK+ 2 config for NixOS Plasma 5
-    include "/run/current-system/sw/share/themes/Breeze/gtk-2.0/gtkrc"
-    style "user-font"
-    {
-      font_name="Sans Serif Regular"
-    }
-    widget_class "*" style "user-font"
-    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 = ini.generate "kcminputrc" {
-    Mouse = {
-      cursorTheme = "breeze_cursors";
-      cursorSize = 0;
-    };
-  };
+    mkDefault mkIf mkMerge mkOption mkPackageOptionMD types;
 
   activationScript = ''
     ${set_XDG_CONFIG_HOME}
@@ -119,125 +78,98 @@ let
     XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
   '';
 
-  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.services.xserver.desktopManager.plasma5 = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
-    };
-
-    phononBackend = mkOption {
-      type = types.enum [ "gstreamer" "vlc" ];
-      default = "gstreamer";
-      example = "vlc";
-      description = lib.mdDoc "Phonon audio backend to install.";
-    };
+  options = {
+    services.xserver.desktopManager.plasma5 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
+      };
 
-    supportDDC = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Support setting monitor brightness via DDC.
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "vlc";
+        example = "gstreamer";
+        description = lib.mdDoc "Phonon audio backend to install.";
+      };
 
-        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 = lib.mdDoc "Enable HiDPI scaling in Qt.";
+      };
 
-    useQtScaling = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc "Enable HiDPI scaling in Qt.";
-    };
+      runUsingSystemd = mkOption {
+        description = lib.mdDoc "Use systemd to manage the Plasma session";
+        type = types.bool;
+        default = true;
+      };
 
-    runUsingSystemd = mkOption {
-      description = lib.mdDoc "Use systemd to manage the Plasma session";
-      type = types.bool;
-      default = true;
-    };
+      notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
+        default = [ "noto-fonts" ];
+        example = "noto-fonts-lgc-plus";
+      };
 
-    excludePackages = mkOption {
-      description = lib.mdDoc "List of default packages to exclude from the configuration";
-      type = types.listOf types.package;
-      default = [];
-      example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
-    };
+      # Internally allows configuring kdeglobals globally
+      kdeglobals = mkOption {
+        internal = true;
+        default = {};
+        type = kdeConfigurationType;
+      };
 
-    # Internally allows configuring kdeglobals globally
-    kdeglobals = mkOption {
-      internal = true;
-      default = {};
-      type = kdeConfigurationType;
-    };
+      # Internally allows configuring kwin globally
+      kwinrc = 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 = lib.mdDoc ''
+          Enable support for running the Plasma Mobile shell.
+        '';
+      };
 
-    mobile.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enable support for running the Plasma Mobile shell.
-      '';
-    };
+      mobile.installRecommendedSoftware = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Installs software recommended for use with Plasma Mobile, but which
+          is not strictly required for Plasma Mobile to run.
+        '';
+      };
 
-    mobile.installRecommendedSoftware = mkOption {
-      type = types.bool;
-      default = true;
-      description = lib.mdDoc ''
-        Installs software recommended for use with Plasma Mobile, but which
-        is not strictly required for Plasma Mobile to run.
-      '';
+      bigscreen.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable support for running the Plasma Bigscreen session.
+        '';
+      };
     };
+    environment.plasma5.excludePackages = mkOption {
+        description = lib.mdDoc "List of default packages to exclude from the configuration";
+        type = types.listOf types.package;
+        default = [];
+        example = literalExpression "[ pkgs.plasma5Packages.oxygen ]";
+      };
   };
 
   imports = [
     (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.")
+    (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "supportDDC" ] "DDC/CI is no longer supported upstream.")
     (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
+    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "excludePackages" ] [ "environment" "plasma5" "excludePackages" ])
   ];
 
   config = mkMerge [
     # Common Plasma dependencies
-    (mkIf (cfg.enable || cfg.mobile.enable) {
+    (mkIf (cfg.enable || cfg.mobile.enable || cfg.bigscreen.enable) {
 
       security.wrappers = {
         kscreenlocker_greet = {
@@ -260,12 +192,6 @@ in
         };
       };
 
-      # DDC support
-      boot.kernelModules = lib.optional cfg.supportDDC "i2c_dev";
-      services.udev.extraRules = lib.optionalString cfg.supportDDC ''
-        KERNEL=="i2c-[0-9]*", TAG+="uaccess"
-      '';
-
       environment.systemPackages =
         with libsForQt5;
         with plasma5; with kdeGear; with kdeFrameworks;
@@ -339,6 +265,8 @@ in
             plasma-workspace
             plasma-workspace-wallpapers
 
+            oxygen-sounds
+
             breeze-icons
             pkgs.hicolor-icon-theme
 
@@ -350,6 +278,7 @@ in
             pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
           ];
           optionalPackages = [
+            pkgs.aha # needed by kinfocenter for fwupd support
             plasma-browser-integration
             konsole
             oxygen
@@ -357,7 +286,7 @@ in
           ];
         in
         requiredPackages
-        ++ utils.removePackagesByName optionalPackages cfg.excludePackages
+        ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages
 
         # Phonon audio backend
         ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
@@ -372,7 +301,13 @@ in
         ++ 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;
+        ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet
+        ++ lib.optional config.services.flatpak.enable flatpak-kcm;
+
+      # Extra services for D-Bus activation
+      services.dbus.packages = [
+        plasma5.kactivitymanagerd
+      ];
 
       environment.pathsToLink = [
         # FIXME: modules should link subdirs of `/share` rather than relying on this
@@ -381,12 +316,23 @@ in
 
       environment.etc."X11/xkb".source = xcfg.xkbDir;
 
-      environment.sessionVariables.PLASMA_USE_QT_SCALING = mkIf cfg.useQtScaling "1";
+      environment.sessionVariables = {
+        PLASMA_USE_QT_SCALING = mkIf cfg.useQtScaling "1";
+
+        # Needed for things that depend on other store.kde.org packages to install correctly,
+        # notably Plasma look-and-feel packages (a.k.a. Global Themes)
+        #
+        # FIXME: this is annoyingly impure and should really be fixed at source level somehow,
+        # but kpackage is a library so we can't just wrap the one thing invoking it and be done.
+        # This also means things won't work for people not on Plasma, but at least this way it
+        # works for SOME people.
+        KPACKAGE_DEP_RESOLVERS_PATH = "${pkgs.plasma5Packages.frameworkintegration.out}/libexec/kf5/kpackagehandlers";
+      };
 
       # Enable GTK applications to load SVG icons
       services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
 
-      fonts.fonts = with pkgs; [ noto-fonts hack-font ];
+      fonts.fonts = with pkgs; [ cfg.notoPackage hack-font ];
       fonts.fontconfig.defaultFonts = {
         monospace = [ "Hack" "Noto Sans Mono" ];
         sansSerif = [ "Noto Sans" ];
@@ -418,12 +364,7 @@ in
 
       security.pam.services.kde = { allowNullPassword = true; };
 
-      # Doing these one by one seems silly, but we currently lack a better
-      # construct for handling common pam configs.
-      security.pam.services.gdm.enableKwallet = true;
-      security.pam.services.kdm.enableKwallet = true;
-      security.pam.services.lightdm.enableKwallet = true;
-      security.pam.services.sddm.enableKwallet = true;
+      security.pam.services.login.enableKwallet = true;
 
       systemd.user.services = {
         plasma-early-setup = mkIf cfg.runUsingSystemd {
@@ -436,17 +377,22 @@ in
 
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ];
+      # xdg-desktop-portal-kde expects PipeWire to be running.
+      # This does not, by default, replace PulseAudio.
+      services.pipewire.enable = mkDefault true;
 
       # Update the start menu for each user that is currently logged in
       system.userActivationScripts.plasmaSetup = activationScript;
-      services.xserver.displayManager.setupCommands = startplasma;
 
       nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
+    })
 
-      environment.etc = {
-        "xdg/kwinrc".text     = lib.generators.toINI {} cfg.kwinrc;
-        "xdg/kdeglobals".text = lib.generators.toINI {} cfg.kdeglobals;
-      };
+    (mkIf (cfg.kwinrc != {}) {
+      environment.etc."xdg/kwinrc".text = lib.generators.toINI {} cfg.kwinrc;
+    })
+
+    (mkIf (cfg.kdeglobals != {}) {
+      environment.etc."xdg/kdeglobals".text = lib.generators.toINI {} cfg.kdeglobals;
     })
 
     # Plasma Desktop
@@ -483,16 +429,19 @@ in
             dolphin-plugins
             ffmpegthumbs
             kdegraphics-thumbnailers
+            kde-inotify-survey
+            kio-admin
             kio-extras
           ];
           optionalPackages = [
+            ark
             elisa
             gwenview
             okular
             khelpcenter
             print-manager
           ];
-      in requiredPackages ++ utils.removePackagesByName optionalPackages cfg.excludePackages;
+      in requiredPackages ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages;
 
       systemd.user.services = {
         plasma-run-with-systemd = {
@@ -524,7 +473,7 @@ in
         }
         {
           # The user interface breaks without pulse
-          assertion = config.hardware.pulseaudio.enable;
+          assertion = config.hardware.pulseaudio.enable || (config.services.pipewire.enable && config.services.pipewire.pulse.enable);
           message = "Plasma Mobile requires pulseaudio.";
         }
       ];
@@ -564,6 +513,8 @@ in
       hardware.bluetooth.enable = true;
       hardware.pulseaudio.enable = true;
       networking.networkmanager.enable = true;
+      # Required for autorotate
+      hardware.sensor.iio.enable = lib.mkDefault true;
 
       # Recommendations can be found here:
       #  - https://invent.kde.org/plasma-mobile/plasma-phone-settings/-/tree/master/etc/xdg
@@ -577,9 +528,9 @@ in
           };
         };
         kwinrc = {
-          Windows = {
-            # Forces windows to be maximized
-            Placement = lib.mkDefault "Maximizing";
+          "Wayland" = {
+            "InputMethod[$e]" = "/run/current-system/sw/share/applications/com.github.maliit.keyboard.desktop";
+            "VirtualKeyboardEnabled" = "true";
           };
           "org.kde.kdecoration2" = {
             # No decorations (title bar)
@@ -590,5 +541,29 @@ in
 
       services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-mobile ];
     })
+
+    # Plasma Bigscreen
+    (mkIf cfg.bigscreen.enable {
+      environment.systemPackages =
+        with pkgs.plasma5Packages;
+        [
+          plasma-nano
+          plasma-settings
+          plasma-bigscreen
+          plasma-remotecontrollers
+
+          aura-browser
+          plank-player
+
+          plasma-pa
+          plasma-nm
+          kdeconnect-kde
+        ];
+
+      services.xserver.displayManager.sessionPackages = [ pkgs.plasma5Packages.plasma-bigscreen ];
+
+      # required for plasma-remotecontrollers to work correctly
+      hardware.uinput.enable = true;
+    })
   ];
 }
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/retroarch.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/retroarch.nix
index c5504e514915..5552f37612a2 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/retroarch.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/retroarch.nix
@@ -6,7 +6,7 @@ let cfg = config.services.xserver.desktopManager.retroarch;
 
 in {
   options.services.xserver.desktopManager.retroarch = {
-    enable = mkEnableOption "RetroArch";
+    enable = mkEnableOption (lib.mdDoc "RetroArch");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
index 7d2ad5a3f2b2..38ebb9d02b4a 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
@@ -45,7 +45,7 @@ let
 in {
   options = {
     services.xserver.desktopManager.surf-display = {
-      enable = mkEnableOption "surf-display as a kiosk browser session";
+      enable = mkEnableOption (lib.mdDoc "surf-display as a kiosk browser session");
 
       defaultWwwUri = mkOption {
         type = types.str;
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix b/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix
index 861976d1186f..00ffd91cb2f6 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix
@@ -2,7 +2,7 @@
 , glib
 , gobject-introspection
 , python3
-, wrapGAppsHook
+, wrapGAppsNoGuiHook
 , lib
 }:
 
@@ -18,7 +18,7 @@ python3.pkgs.buildPythonApplication {
   strictDeps = false;
 
   nativeBuildInputs = [
-    wrapGAppsHook
+    wrapGAppsNoGuiHook
     gobject-introspection
   ];
 
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
index 03bf5d1cd2da..995ecd231c43 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
@@ -24,7 +24,7 @@ let
     Xft.lcdfilter: lcd${fontconfig.subpixel.lcdfilter}
     Xft.hinting: ${if fontconfig.hinting.enable then "1" else "0"}
     Xft.autohint: ${if fontconfig.hinting.autohint then "1" else "0"}
-    Xft.hintstyle: hintslight
+    Xft.hintstyle: ${fontconfig.hinting.style}
   '';
 
   # file provided by services.xserver.displayManager.sessionData.wrapper
@@ -153,7 +153,7 @@ in
         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.";
+        description = lib.mdDoc "Path to the {command}`xauth` program used by display managers.";
       };
 
       xserverBin = mkOption {
@@ -235,15 +235,15 @@ in
               }
             ]
           '';
-        description = ''
+        description = lib.mdDoc ''
           List of sessions supported with the command used to start each
           session.  Each session script can set the
-          <varname>waitPID</varname> shell variable to make this script
+          {var}`waitPID` shell variable to make this script
           wait until the end of the user session.  Each script is used
           to define either a window manager or a desktop manager.  These
           can be differentiated by setting the attribute
-          <varname>manage</varname> either to <literal>"window"</literal>
-          or <literal>"desktop"</literal>.
+          {var}`manage` either to `"window"`
+          or `"desktop"`.
 
           The list of desktop manager and window manager should appear
           inside the display manager with the desktop manager name
@@ -252,7 +252,7 @@ in
       };
 
       sessionData = mkOption {
-        description = "Data exported for display managers’ convenience";
+        description = lib.mdDoc "Data exported for display managers’ convenience";
         internal = true;
         default = {};
         apply = val: {
@@ -285,7 +285,7 @@ in
             defaultSessionFromLegacyOptions
           else
             null;
-        defaultText = literalDocBook ''
+        defaultText = literalMD ''
           Taken from display manager settings or window manager settings, if either is set.
         '';
         example = "gnome";
@@ -299,7 +299,7 @@ in
       importedVariables = mkOption {
         type = types.listOf (types.strMatching "[a-zA-Z_][a-zA-Z0-9_]*");
         visible = false;
-        description = ''
+        description = lib.mdDoc ''
           Environment variables to import into the systemd user environment.
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
index 025d572957e1..f8f82bda3fa4 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
@@ -67,9 +67,9 @@ in
 
     services.xserver.displayManager.gdm = {
 
-      enable = mkEnableOption "GDM, the GNOME Display Manager";
+      enable = mkEnableOption (lib.mdDoc "GDM, the GNOME Display Manager");
 
-      debug = mkEnableOption "debugging messages in GDM";
+      debug = mkEnableOption (lib.mdDoc "debugging messages in GDM");
 
       # Auto login options specific to GDM
       autoLogin.delay = mkOption {
@@ -323,7 +323,7 @@ in
 
         account   sufficient    pam_unix.so
 
-        password  requisite     pam_unix.so nullok sha512
+        password  requisite     pam_unix.so nullok yescrypt
 
         session   optional      pam_keyinit.so revoke
         session   include       login
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix
new file mode 100644
index 000000000000..31cc9b3deaa1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.mobile;
+in
+{
+  options = {
+    services.xserver.displayManager.lightdm.greeters.mobile = {
+      enable = mkEnableOption (lib.mdDoc
+        "lightdm-mobile-greeter as the lightdm greeter"
+      );
+    };
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+    services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
+
+    services.xserver.displayManager.lightdm.greeter = mkDefault {
+      package = pkgs.lightdm-mobile-greeter.xgreeters;
+      name = "lightdm-mobile-greeter";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
new file mode 100644
index 000000000000..4456374cc569
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
@@ -0,0 +1,149 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  ldmcfg = config.services.xserver.displayManager.lightdm;
+  cfg = ldmcfg.greeters.slick;
+
+  inherit (pkgs) writeText;
+
+  theme = cfg.theme.package;
+  icons = cfg.iconTheme.package;
+  font = cfg.font.package;
+  cursors = cfg.cursorTheme.package;
+
+  slickGreeterConf = writeText "slick-greeter.conf" ''
+    [Greeter]
+    background=${ldmcfg.background}
+    theme-name=${cfg.theme.name}
+    icon-theme-name=${cfg.iconTheme.name}
+    font-name=${cfg.font.name}
+    cursor-theme-name=${cfg.cursorTheme.name}
+    cursor-theme-size=${toString cfg.cursorTheme.size}
+    draw-user-backgrounds=${boolToString cfg.draw-user-backgrounds}
+    ${cfg.extraConfig}
+  '';
+in
+{
+  options = {
+    services.xserver.displayManager.lightdm.greeters.slick = {
+      enable = mkEnableOption (lib.mdDoc "lightdm-slick-greeter as the lightdm greeter");
+
+      theme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome.gnome-themes-extra;
+          defaultText = literalExpression "pkgs.gnome.gnome-themes-extra";
+          description = lib.mdDoc ''
+            The package path that contains the theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = lib.mdDoc ''
+            Name of the theme to use for the lightdm-slick-greeter.
+          '';
+        };
+      };
+
+      iconTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome.adwaita-icon-theme;
+          defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
+          description = lib.mdDoc ''
+            The package path that contains the icon theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = lib.mdDoc ''
+            Name of the icon theme to use for the lightdm-slick-greeter.
+          '';
+        };
+      };
+
+      font = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.ubuntu_font_family;
+          defaultText = literalExpression "pkgs.ubuntu_font_family";
+          description = lib.mdDoc ''
+            The package path that contains the font given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Ubuntu 11";
+          description = lib.mdDoc ''
+            Name of the font to use.
+          '';
+        };
+      };
+
+      cursorTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome.adwaita-icon-theme;
+          defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
+          description = lib.mdDoc ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = lib.mdDoc ''
+            Name of the cursor theme to use for the lightdm-slick-greeter.
+          '';
+        };
+
+        size = mkOption {
+          type = types.int;
+          default = 24;
+          description = lib.mdDoc ''
+            Size of the cursor theme to use for the lightdm-slick-greeter.
+          '';
+        };
+      };
+
+      draw-user-backgrounds = mkEnableOption (lib.mdDoc "draw user backgrounds");
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = lib.mdDoc ''
+          Extra configuration that should be put in the lightdm-slick-greeter.conf
+          configuration file.
+        '';
+      };
+    };
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+    services.xserver.displayManager.lightdm = {
+      greeters.gtk.enable = false;
+      greeter = mkDefault {
+        package = pkgs.lightdm-slick-greeter.xgreeters;
+        name = "lightdm-slick-greeter";
+      };
+    };
+
+    environment.systemPackages = [
+      cursors
+      icons
+      theme
+    ];
+
+    fonts.fonts = [ font ];
+
+    environment.etc."lightdm/slick-greeter.conf".source = slickGreeterConf;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
index 2d1ae0ed0a36..548d3c5bc46a 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -32,7 +32,7 @@ let
   usersConf = writeText "users.conf"
     ''
       [UserList]
-      minimum-uid=500
+      minimum-uid=1000
       hidden-users=${concatStringsSep " " dmcfg.hiddenUsers}
       hidden-shells=/run/current-system/sw/bin/nologin
     '';
@@ -82,6 +82,8 @@ in
     ./lightdm-greeters/enso-os.nix
     ./lightdm-greeters/pantheon.nix
     ./lightdm-greeters/tiny.nix
+    ./lightdm-greeters/slick.nix
+    ./lightdm-greeters/mobile.nix
     (mkRenamedOptionModule [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "enable" ] [
       "services"
       "xserver"
@@ -300,7 +302,7 @@ in
 
         account   sufficient    pam_unix.so
 
-        password  requisite     pam_unix.so nullok sha512
+        password  requisite     pam_unix.so nullok yescrypt
 
         session   optional      pam_keyinit.so revoke
         session   include       login
@@ -310,7 +312,6 @@ in
       home = "/var/lib/lightdm";
       group = "lightdm";
       uid = config.ids.uids.lightdm;
-      shell = pkgs.bash;
     };
 
     systemd.tmpfiles.rules = [
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
index 34239221315d..0ddeac0f1098 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
@@ -123,7 +123,7 @@ in
           };
         };
         description = lib.mdDoc ''
-          Extra settings merged in and overwritting defaults in sddm.conf.
+          Extra settings merged in and overwriting defaults in sddm.conf.
         '';
       };
 
@@ -215,10 +215,12 @@ in
     };
 
     security.pam.services = {
-      sddm = {
-        allowNullPassword = true;
-        startSession = true;
-      };
+      sddm.text = ''
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+      '';
 
       sddm-greeter.text = ''
         auth     required       pam_succeed_if.so audit quiet_success user = sddm
@@ -269,20 +271,5 @@ in
     # To enable user switching, allow sddm to allocate TTYs/displays dynamically.
     services.xserver.tty = null;
     services.xserver.display = null;
-
-    systemd.tmpfiles.rules = [
-      # Prior to Qt 5.9.2, there is a QML cache invalidation bug which sometimes
-      # strikes new Plasma 5 releases. If the QML cache is not invalidated, SDDM
-      # will segfault without explanation. We really tore our hair out for awhile
-      # before finding the bug:
-      # https://bugreports.qt.io/browse/QTBUG-62302
-      # We work around the problem by deleting the QML cache before startup.
-      # This was supposedly fixed in Qt 5.9.2 however it has been reported with
-      # 5.10 and 5.11 as well. The initial workaround was to delete the directory
-      # in the Xsetup script but that doesn't do anything.
-      # Instead we use tmpfiles.d to ensure it gets wiped.
-      # This causes a small but perceptible delay when SDDM starts.
-      "e ${config.users.users.sddm.home}/.cache - - - 0"
-    ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix b/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix
index e30977364300..6a7fc1a040e7 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix
@@ -7,8 +7,8 @@ let cfg = config.services.xserver.displayManager.sx;
 in {
   options = {
     services.xserver.displayManager.sx = {
-      enable = mkEnableOption "sx pseudo-display manager" // {
-        description = ''
+      enable = mkEnableOption (lib.mdDoc "sx pseudo-display manager") // {
+        description = lib.mdDoc ''
           Whether to enable the "sx" pseudo-display manager, which allows users
           to start manually via the "sx" command from a vt shell. The X server
           runs under the user's id, not as root. The user must provide a
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix b/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
index 15b3f70d46e0..cb78f52d9b68 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
@@ -40,7 +40,7 @@ in
         description = lib.mdDoc "Authentication to use when connecting to xpra";
       };
 
-      pulseaudio = mkEnableOption "pulseaudio audio streaming";
+      pulseaudio = mkEnableOption (lib.mdDoc "pulseaudio audio streaming");
 
       extraOptions = mkOption {
         description = lib.mdDoc "Extra xpra options";
diff --git a/nixpkgs/nixos/modules/services/x11/extra-layouts.nix b/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
index 574657a50c82..1f48713a68dd 100644
--- a/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
+++ b/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
@@ -106,9 +106,9 @@ in
       description = lib.mdDoc ''
         Extra custom layouts that will be included in the xkb configuration.
         Information on how to create a new layout can be found here:
-        [](https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts).
+        <https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts>.
         For more examples see
-        [](https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples)
+        <https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples>
       '';
     };
 
@@ -121,7 +121,7 @@ in
     environment.sessionVariables = {
       # runtime override supported by multiple libraries e. g. libxkbcommon
       # https://xkbcommon.org/doc/current/group__include-path.html
-      XKB_CONFIG_ROOT = "${xkb_patched}/etc/X11/xkb";
+      XKB_CONFIG_ROOT = config.services.xserver.xkbDir;
     };
 
     services.xserver = {
diff --git a/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix b/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
index c80e2b22792a..9c088e4cc423 100644
--- a/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
+++ b/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -1,34 +1,17 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.xserver.gdk-pixbuf;
 
-  # Get packages to generate the cache for. We always include gdk-pixbuf.
-  effectivePackages = unique ([pkgs.gdk-pixbuf] ++ cfg.modulePackages);
-
-  # Generate the cache file by running gdk-pixbuf-query-loaders for each
-  # package and concatenating the results.
-  loadersCache = pkgs.runCommand "gdk-pixbuf-loaders.cache" { preferLocalBuild = true; } ''
-    (
-      for package in ${concatStringsSep " " effectivePackages}; do
-        module_dir="$package/${pkgs.gdk-pixbuf.moduleDir}"
-        if [[ ! -d $module_dir ]]; then
-          echo "Warning (services.xserver.gdk-pixbuf): missing module directory $module_dir" 1>&2
-          continue
-        fi
-        GDK_PIXBUF_MODULEDIR="$module_dir" \
-          ${pkgs.stdenv.hostPlatform.emulator pkgs.buildPackages} ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders
-      done
-    ) > "$out"
-  '';
+  loadersCache = pkgs.gnome._gdkPixbufCacheBuilder_DO_NOT_USE {
+    extraLoaders = lib.unique (cfg.modulePackages);
+  };
 in
 
 {
   options = {
-    services.xserver.gdk-pixbuf.modulePackages = mkOption {
-      type = types.listOf types.package;
+    services.xserver.gdk-pixbuf.modulePackages = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
       default = [ ];
       description = lib.mdDoc "Packages providing GDK-Pixbuf modules, for cache generation.";
     };
@@ -37,8 +20,8 @@ in
   # If there is any package configured in modulePackages, we generate the
   # loaders.cache based on that and set the environment variable
   # GDK_PIXBUF_MODULE_FILE to point to it.
-  config = mkIf (cfg.modulePackages != []) {
-    environment.variables = {
+  config = lib.mkIf (cfg.modulePackages != []) {
+    environment.sessionVariables = {
       GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
     };
   };
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix b/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix
index b1b1682f00b2..f82aac41a320 100644
--- a/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix
+++ b/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix
@@ -16,7 +16,7 @@ in
 
     services.xserver.digimend = {
 
-      enable = mkEnableOption "the digimend drivers for Huion/XP-Pen/etc. tablets";
+      enable = mkEnableOption (lib.mdDoc "the digimend drivers for Huion/XP-Pen/etc. tablets");
 
     };
 
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
index 6603498eeafa..d2a5b5895e0a 100644
--- a/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
@@ -156,6 +156,14 @@ let cfg = config.services.xserver.libinput;
           '';
       };
 
+      tappingButtonMap = mkOption {
+        type = types.nullOr (types.enum [ "lrm" "lmr" ]);
+        default = null;
+        description = lib.mdDoc ''
+          Set the button mapping for 1/2/3-finger taps to left/right/middle or left/middle/right, respectively.
+        '';
+      };
+
       tappingDragLock = mkOption {
         type = types.bool;
         default = true;
@@ -163,7 +171,7 @@ let cfg = config.services.xserver.libinput;
           lib.mdDoc ''
             Enables or disables drag lock during tapping behavior. When enabled, a finger up during tap-
             and-drag will not immediately release the button. If the finger is set down again within the
-            timeout, the draging process continues.
+            timeout, the dragging process continues.
           '';
       };
 
@@ -220,6 +228,7 @@ let cfg = config.services.xserver.libinput;
       Option "HorizontalScrolling" "${xorgBool cfg.${deviceType}.horizontalScrolling}"
       Option "SendEventsMode" "${cfg.${deviceType}.sendEventsMode}"
       Option "Tapping" "${xorgBool cfg.${deviceType}.tapping}"
+      ${optionalString (cfg.${deviceType}.tappingButtonMap != null) ''Option "TappingButtonMap" "${cfg.${deviceType}.tappingButtonMap}"''}
       Option "TappingDragLock" "${xorgBool cfg.${deviceType}.tappingDragLock}"
       Option "DisableWhileTyping" "${xorgBool cfg.${deviceType}.disableWhileTyping}"
       ${cfg.${deviceType}.additionalOptions}
@@ -241,6 +250,7 @@ in {
       "horizontalScrolling"
       "sendEventsMode"
       "tapping"
+      "tappingButtonMap"
       "tappingDragLock"
       "transformationMatrix"
       "disableWhileTyping"
@@ -250,7 +260,10 @@ in {
   options = {
 
     services.xserver.libinput = {
-      enable = mkEnableOption "libinput";
+      enable = mkEnableOption (lib.mdDoc "libinput") // {
+        default = config.services.xserver.enable;
+        defaultText = lib.literalExpression "config.services.xserver.enable";
+      };
       mouse = mkConfigForDevice "mouse";
       touchpad = mkConfigForDevice "touchpad";
     };
diff --git a/nixpkgs/nixos/modules/services/x11/imwheel.nix b/nixpkgs/nixos/modules/services/x11/imwheel.nix
index 9f4fc7e90c44..133e64c65cdd 100644
--- a/nixpkgs/nixos/modules/services/x11/imwheel.nix
+++ b/nixpkgs/nixos/modules/services/x11/imwheel.nix
@@ -6,7 +6,7 @@ in
   {
     options = {
       services.xserver.imwheel = {
-        enable = mkEnableOption "IMWheel service";
+        enable = mkEnableOption (lib.mdDoc "IMWheel service");
 
         extraOptions = mkOption {
           type = types.listOf types.str;
@@ -37,8 +37,8 @@ in
             Window class translation rules.
             /etc/X11/imwheelrc is generated based on this config
             which means this config is global for all users.
-            See [offical man pages](http://imwheel.sourceforge.net/imwheel.1.html)
-            for more informations.
+            See [official man pages](http://imwheel.sourceforge.net/imwheel.1.html)
+            for more information.
           '';
         };
       };
diff --git a/nixpkgs/nixos/modules/services/x11/picom.nix b/nixpkgs/nixos/modules/services/x11/picom.nix
index 99bde5e1f0ce..1d6f3daa4022 100644
--- a/nixpkgs/nixos/modules/services/x11/picom.nix
+++ b/nixpkgs/nixos/modules/services/x11/picom.nix
@@ -11,15 +11,6 @@ let
     addCheck (listOf x) (y: length y == 2)
     // { description = "pair of ${x.description}"; };
 
-  floatBetween = a: b: with types;
-    let
-      # toString prints floats with hardcoded high precision
-      floatToString = f: builtins.toJSON f;
-    in
-      addCheck float (x: x <= b && x >= a)
-      // { description = "a floating point number in " +
-                         "range [${floatToString a}, ${floatToString b}]"; };
-
   mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
 
   # Basically a tinkered lib.generators.mkKeyValueDefault
@@ -50,12 +41,15 @@ let
 in {
 
   imports = [
-    (mkAliasOptionModule [ "services" "compton" ] [ "services" "picom" ])
+    (mkAliasOptionModuleMD [ "services" "compton" ] [ "services" "picom" ])
     (mkRemovedOptionModule [ "services" "picom" "refreshRate" ] ''
       This option corresponds to `refresh-rate`, which has been unused
       since picom v6 and was subsequently removed by upstream.
       See https://github.com/yshui/picom/commit/bcbc410
     '')
+    (mkRemovedOptionModule [ "services" "picom" "experimentalBackends" ] ''
+      This option was removed by upstream since picom v10.
+    '')
   ];
 
   options.services.picom = {
@@ -67,14 +61,6 @@ in {
       '';
     };
 
-    experimentalBackends = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Whether to use the unstable new reimplementation of the backends.
-      '';
-    };
-
     fade = mkOption {
       type = types.bool;
       default = false;
@@ -93,7 +79,7 @@ in {
     };
 
     fadeSteps = mkOption {
-      type = pairOf (floatBetween 0.01 1);
+      type = pairOf (types.numbers.between 0.01 1);
       default = [ 0.028 0.03 ];
       example = [ 0.04 0.04 ];
       description = lib.mdDoc ''
@@ -133,7 +119,7 @@ in {
     };
 
     shadowOpacity = mkOption {
-      type = floatBetween 0 1;
+      type = types.numbers.between 0 1;
       default = 0.75;
       example = 0.8;
       description = lib.mdDoc ''
@@ -156,7 +142,7 @@ in {
     };
 
     activeOpacity = mkOption {
-      type = floatBetween 0 1;
+      type = types.numbers.between 0 1;
       default = 1.0;
       example = 0.8;
       description = lib.mdDoc ''
@@ -165,7 +151,7 @@ in {
     };
 
     inactiveOpacity = mkOption {
-      type = floatBetween 0.1 1;
+      type = types.numbers.between 0.1 1;
       default = 1.0;
       example = 0.8;
       description = lib.mdDoc ''
@@ -174,7 +160,7 @@ in {
     };
 
     menuOpacity = mkOption {
-      type = floatBetween 0 1;
+      type = types.numbers.between 0 1;
       default = 1.0;
       example = 0.8;
       description = lib.mdDoc ''
@@ -213,10 +199,10 @@ in {
     };
 
     backend = mkOption {
-      type = types.enum [ "glx" "xrender" "xr_glx_hybrid" ];
+      type = types.enum [ "egl" "glx" "xrender" "xr_glx_hybrid" ];
       default = "xrender";
       description = lib.mdDoc ''
-        Backend to use: `glx`, `xrender` or `xr_glx_hybrid`.
+        Backend to use: `egl`, `glx`, `xrender` or `xr_glx_hybrid`.
       '';
     };
 
@@ -315,8 +301,7 @@ in {
       };
 
       serviceConfig = {
-        ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}"
-          + (optionalString cfg.experimentalBackends " --experimental-backends");
+        ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}";
         RestartSec = 3;
         Restart = "always";
       };
diff --git a/nixpkgs/nixos/modules/services/x11/touchegg.nix b/nixpkgs/nixos/modules/services/x11/touchegg.nix
index 905e8521cf77..f1103c054c57 100644
--- a/nixpkgs/nixos/modules/services/x11/touchegg.nix
+++ b/nixpkgs/nixos/modules/services/x11/touchegg.nix
@@ -11,7 +11,7 @@ in {
 
   ###### interface
   options.services.touchegg = {
-    enable = mkEnableOption "touchegg, a multi-touch gesture recognizer";
+    enable = mkEnableOption (lib.mdDoc "touchegg, a multi-touch gesture recognizer");
 
     package = mkOption {
       type = types.package;
diff --git a/nixpkgs/nixos/modules/services/x11/urserver.nix b/nixpkgs/nixos/modules/services/x11/urserver.nix
index 0beb62eb766a..d0b6e0775e5d 100644
--- a/nixpkgs/nixos/modules/services/x11/urserver.nix
+++ b/nixpkgs/nixos/modules/services/x11/urserver.nix
@@ -5,7 +5,7 @@ let
   cfg = config.services.urserver;
 in {
 
-  options.services.urserver.enable = lib.mkEnableOption "urserver";
+  options.services.urserver.enable = lib.mkEnableOption (lib.mdDoc "urserver");
 
   config = lib.mkIf cfg.enable {
 
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
index fdbdf35b0f5a..8483a74b9f6c 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
@@ -13,7 +13,7 @@ in
   ###### interface
 
   options = {
-    services.xserver.windowManager."2bwm".enable = mkEnableOption "2bwm";
+    services.xserver.windowManager."2bwm".enable = mkEnableOption (lib.mdDoc "2bwm");
   };
 
 
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix b/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
index ba88a64c702a..a06063597971 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.afterstep.enable = mkEnableOption "afterstep";
+    services.xserver.windowManager.afterstep.enable = mkEnableOption (lib.mdDoc "afterstep");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix b/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
index 8db7d86c72c6..c1231d3fbf38 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.xserver.windowManager.awesome;
   awesome = cfg.package;
-  getLuaPath = lib : dir : "${lib}/${dir}/lua/${pkgs.luaPackages.lua.luaversion}";
+  getLuaPath = lib: dir: "${lib}/${dir}/lua/${awesome.lua.luaversion}";
   makeSearchPath = lib.concatMapStrings (path:
     " --search " + (getLuaPath path "share") +
     " --search " + (getLuaPath path "lib")
@@ -21,7 +21,7 @@ in
 
     services.xserver.windowManager.awesome = {
 
-      enable = mkEnableOption "Awesome window manager";
+      enable = mkEnableOption (lib.mdDoc "Awesome window manager");
 
       luaModules = mkOption {
         default = [];
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix b/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix
index 0d2285e7a60e..eb5528602677 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.berry.enable = mkEnableOption "berry";
+    services.xserver.windowManager.berry.enable = mkEnableOption (lib.mdDoc "berry");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
index 4fcd2b7c720d..c403f744cd43 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.xserver.windowManager.bspwm = {
-      enable = mkEnableOption "bspwm";
+      enable = mkEnableOption (lib.mdDoc "bspwm");
 
       package = mkOption {
         type        = types.package;
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
index cf8eec249c6c..f2e4c2f91c9d 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.xserver.windowManager.clfswm = {
-      enable = mkEnableOption "clfswm";
+      enable = mkEnableOption (lib.mdDoc "clfswm");
       package = mkOption {
         type        = types.package;
         default     = pkgs.lispPackages.clfswm;
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix
index 03375a226bb6..9a143e7bccc3 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options = {
-    services.xserver.windowManager.cwm.enable = mkEnableOption "cwm";
+    services.xserver.windowManager.cwm.enable = mkEnableOption (lib.mdDoc "cwm");
   };
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = singleton
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/default.nix b/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
index 52083dcaaa25..ce1d4115f225 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
@@ -14,6 +14,7 @@ in
     ./bspwm.nix
     ./cwm.nix
     ./clfswm.nix
+    ./dk.nix
     ./dwm.nix
     ./e16.nix
     ./evilwm.nix
@@ -23,6 +24,7 @@ in
     ./fvwm3.nix
     ./hackedbox.nix
     ./herbstluftwm.nix
+    ./hypr.nix
     ./i3.nix
     ./jwm.nix
     ./leftwm.nix
@@ -59,10 +61,10 @@ in
           name = "wmii";
           start = "...";
         }];
-        description = ''
+        description = lib.mdDoc ''
           Internal option used to add some common line to window manager
           scripts before forwarding the value to the
-          <varname>displayManager</varname>.
+          `displayManager`.
         '';
         apply = map (d: d // {
           manage = "window";
@@ -73,8 +75,8 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "wmii";
-        description = ''
-          <emphasis role="strong">Deprecated</emphasis>, please use <xref linkend="opt-services.xserver.displayManager.defaultSession"/> instead.
+        description = lib.mdDoc ''
+          **Deprecated**, please use [](#opt-services.xserver.displayManager.defaultSession) instead.
 
           Default window manager loaded if none have been chosen.
         '';
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/dk.nix b/nixpkgs/nixos/modules/services/x11/window-managers/dk.nix
new file mode 100644
index 000000000000..152c7bc8117b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/dk.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.xserver.windowManager.dk;
+in
+
+{
+  options = {
+    services.xserver.windowManager.dk = {
+      enable = lib.mkEnableOption (lib.mdDoc "dk");
+
+      package = lib.mkPackageOptionMD pkgs "dk" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.xserver.windowManager.session = lib.singleton {
+      name = "dk";
+      start = ''
+        export _JAVA_AWT_WM_NONREPARENTING=1
+        ${cfg.package}/bin/dk &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
index 7777913ce1e6..1881826944aa 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
@@ -13,7 +13,27 @@ in
   ###### interface
 
   options = {
-    services.xserver.windowManager.dwm.enable = mkEnableOption "dwm";
+    services.xserver.windowManager.dwm = {
+      enable = mkEnableOption (lib.mdDoc "dwm");
+      package = mkOption {
+        type        = types.package;
+        default     = pkgs.dwm;
+        defaultText = literalExpression "pkgs.dwm";
+        example     = literalExpression ''
+          pkgs.dwm.overrideAttrs (oldAttrs: rec {
+            patches = [
+              (super.fetchpatch {
+                url = "https://dwm.suckless.org/patches/steam/dwm-steam-6.2.diff";
+                sha256 = "1ld1z3fh6p5f8gr62zknx3axsinraayzxw3rz1qwg73mx2zk5y1f";
+              })
+            ];
+          })
+        '';
+        description = lib.mdDoc ''
+          dwm package to use.
+        '';
+      };
+    };
   };
 
 
@@ -30,7 +50,7 @@ in
           '';
       };
 
-    environment.systemPackages = [ pkgs.dwm ];
+    environment.systemPackages = [ cfg.package ];
 
   };
 
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/e16.nix b/nixpkgs/nixos/modules/services/x11/window-managers/e16.nix
index 3e1a22c4dabd..000feea12c2c 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/e16.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/e16.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.e16.enable = mkEnableOption "e16";
+    services.xserver.windowManager.e16.enable = mkEnableOption (lib.mdDoc "e16");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
index 6f1db2110f87..842f84c2cfbe 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.evilwm.enable = mkEnableOption "evilwm";
+    services.xserver.windowManager.evilwm.enable = mkEnableOption (lib.mdDoc "evilwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
index 5b0a15804ef2..a97ed74ae881 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
@@ -18,7 +18,7 @@ in
 {
   options = {
     services.xserver.windowManager.exwm = {
-      enable = mkEnableOption "exwm";
+      enable = mkEnableOption (lib.mdDoc "exwm");
       loadScript = mkOption {
         default = "(require 'exwm)";
         type = types.lines;
@@ -48,10 +48,10 @@ in
             epkgs.proofgeneral
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra packages available to Emacs. The value must be a
           function which receives the attrset defined in
-          <varname>emacs.pkgs</varname> as the sole argument.
+          {var}`emacs.pkgs` as the sole argument.
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
index b409335702af..24165fb6fb07 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.fluxbox.enable = mkEnableOption "fluxbox";
+    services.xserver.windowManager.fluxbox.enable = mkEnableOption (lib.mdDoc "fluxbox");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fvwm2.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm2.nix
index b5ef36f58d54..aaf3c5c46906 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/fvwm2.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm2.nix
@@ -19,7 +19,7 @@ in
 
   options = {
     services.xserver.windowManager.fvwm2 = {
-      enable = mkEnableOption "Fvwm2 window manager";
+      enable = mkEnableOption (lib.mdDoc "Fvwm2 window manager");
 
       gestures = mkOption {
         default = false;
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fvwm3.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm3.nix
index 43111f917d49..50c76b67eea3 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/fvwm3.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm3.nix
@@ -13,7 +13,7 @@ in
 
   options = {
     services.xserver.windowManager.fvwm3 = {
-      enable = mkEnableOption "Fvwm3 window manager";
+      enable = mkEnableOption (lib.mdDoc "Fvwm3 window manager");
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/hackedbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/hackedbox.nix
index 641cf1bdcbe2..61e911961f51 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/hackedbox.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/hackedbox.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.hackedbox.enable = mkEnableOption "hackedbox";
+    services.xserver.windowManager.hackedbox.enable = mkEnableOption (lib.mdDoc "hackedbox");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
index af077c4d2289..816cbb36cafd 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.xserver.windowManager.herbstluftwm = {
-      enable = mkEnableOption "herbstluftwm";
+      enable = mkEnableOption (lib.mdDoc "herbstluftwm");
 
       package = mkOption {
         type = types.package;
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/hypr.nix b/nixpkgs/nixos/modules/services/x11/window-managers/hypr.nix
new file mode 100644
index 000000000000..4c1fea71f93e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/hypr.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.hypr;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.hypr.enable = mkEnableOption (lib.mdDoc "hypr");
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "hypr";
+      start = ''
+        ${pkgs.hypr}/bin/Hypr &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.hypr ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix b/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
index 87479f2ac452..5bb73cd0bfb1 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
@@ -8,7 +8,7 @@ in
 
 {
   options.services.xserver.windowManager.i3 = {
-    enable = mkEnableOption "i3 window manager";
+    enable = mkEnableOption (lib.mdDoc "i3 window manager");
 
     configFile = mkOption {
       default     = null;
@@ -31,7 +31,6 @@ in
       type        = types.package;
       default     = pkgs.i3;
       defaultText = literalExpression "pkgs.i3";
-      example     = literalExpression "pkgs.i3-gaps";
       description = lib.mdDoc ''
         i3 package to use.
       '';
@@ -73,6 +72,6 @@ in
 
   imports = [
     (mkRemovedOptionModule [ "services" "xserver" "windowManager" "i3-gaps" "enable" ]
-      "Use services.xserver.windowManager.i3.enable and set services.xserver.windowManager.i3.package to pkgs.i3-gaps to use i3-gaps.")
+      "i3-gaps was merged into i3. Use services.xserver.windowManager.i3.enable instead.")
   ];
 }
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
index f4ae9222df67..48741aa41d85 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.icewm.enable = mkEnableOption "icewm";
+    services.xserver.windowManager.icewm.enable = mkEnableOption (lib.mdDoc "icewm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
index 0e8dab2e9224..40758029bc65 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.jwm.enable = mkEnableOption "jwm";
+    services.xserver.windowManager.jwm.enable = mkEnableOption (lib.mdDoc "jwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/katriawm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/katriawm.nix
new file mode 100644
index 000000000000..9a3fd5f3ca44
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/katriawm.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mdDoc mkEnableOption mkIf mkPackageOptionMD singleton;
+  cfg = config.services.xserver.windowManager.katriawm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.katriawm = {
+      enable = mkEnableOption (mdDoc "katriawm");
+      package = mkPackageOptionMD pkgs "katriawm" {};
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "katriawm";
+      start = ''
+        ${cfg.package}/bin/katriawm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
index 3ef40df95df2..2571735ba8bf 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.leftwm.enable = mkEnableOption "leftwm";
+    services.xserver.windowManager.leftwm.enable = mkEnableOption (lib.mdDoc "leftwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix
index e2aa062fd13b..517abb23d4af 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.lwm.enable = mkEnableOption "lwm";
+    services.xserver.windowManager.lwm.enable = mkEnableOption (lib.mdDoc "lwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix b/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
index 600afe759b2c..1f69147af5bc 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
@@ -10,7 +10,7 @@ in
 
 {
   options = {
-    services.xserver.windowManager.metacity.enable = mkEnableOption "metacity";
+    services.xserver.windowManager.metacity.enable = mkEnableOption (lib.mdDoc "metacity");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/mlvwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/mlvwm.nix
index 0ee1d7b097ea..fe0433c24b60 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/mlvwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/mlvwm.nix
@@ -8,7 +8,7 @@ in
 {
 
   options.services.xserver.windowManager.mlvwm = {
-    enable = mkEnableOption "Macintosh-like Virtual Window Manager";
+    enable = mkEnableOption (lib.mdDoc "Macintosh-like Virtual Window Manager");
 
     configFile = mkOption {
       default = null;
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
index 31f7b725f747..9f8dc0939e5e 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.mwm.enable = mkEnableOption "mwm";
+    services.xserver.windowManager.mwm.enable = mkEnableOption (lib.mdDoc "mwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/nimdow.nix b/nixpkgs/nixos/modules/services/x11/window-managers/nimdow.nix
new file mode 100644
index 000000000000..de3192876024
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/nimdow.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.nimdow;
+in
+{
+  options = {
+    services.xserver.windowManager.nimdow.enable = mkEnableOption (lib.mdDoc "nimdow");
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "nimdow";
+      start = ''
+        ${pkgs.nimdow}/bin/nimdow &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.nimdow ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix b/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
index 4ece0d241c90..0015e90a41c5 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
@@ -8,7 +8,7 @@ in
 
 {
   options = {
-    services.xserver.windowManager.notion.enable = mkEnableOption "notion";
+    services.xserver.windowManager.notion.enable = mkEnableOption (lib.mdDoc "notion");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
index 165772d1aa09..bf5a500f431a 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
@@ -7,7 +7,7 @@ in
 
 {
   options = {
-    services.xserver.windowManager.openbox.enable = mkEnableOption "openbox";
+    services.xserver.windowManager.openbox.enable = mkEnableOption (lib.mdDoc "openbox");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix b/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
index bd7e3396864b..654b8708e48f 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.oroborus.enable = mkEnableOption "oroborus";
+    services.xserver.windowManager.oroborus.enable = mkEnableOption (lib.mdDoc "oroborus");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
index 850335ce7ddf..8818f568647a 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.pekwm.enable = mkEnableOption "pekwm";
+    services.xserver.windowManager.pekwm.enable = mkEnableOption (lib.mdDoc "pekwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix b/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
index 4d455fdf7b2d..a362d5cdbeee 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
@@ -1,23 +1,62 @@
-{ config, lib, pkgs, ... }:
+{ config, pkgs, lib, ... }:
 
 with lib;
 
 let
   cfg = config.services.xserver.windowManager.qtile;
+  pyEnv = pkgs.python3.withPackages (p: [ (cfg.package.unwrapped or cfg.package) ] ++ (cfg.extraPackages p));
 in
 
 {
   options.services.xserver.windowManager.qtile = {
-    enable = mkEnableOption "qtile";
+    enable = mkEnableOption (lib.mdDoc "qtile");
 
-    package = mkPackageOption pkgs "qtile" { };
+    package = mkPackageOptionMD pkgs "qtile-unwrapped" { };
+
+    configFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      example = literalExpression "./your_config.py";
+      description = lib.mdDoc ''
+          Path to the qtile configuration file.
+          If null, $XDG_CONFIG_HOME/qtile/config.py will be used.
+      '';
+    };
+
+    backend = mkOption {
+      type = types.enum [ "x11" "wayland" ];
+      default = "x11";
+      description = lib.mdDoc ''
+          Backend to use in qtile: `x11` or `wayland`.
+      '';
+    };
+
+    extraPackages = mkOption {
+        type = types.functionTo (types.listOf types.package);
+        default = _: [];
+        defaultText = literalExpression ''
+          python3Packages: with python3Packages; [];
+        '';
+        description = lib.mdDoc ''
+          Extra Python packages available to Qtile.
+          An example would be to include `python3Packages.qtile-extras`
+          for additional unofficial widgets.
+        '';
+        example = literalExpression ''
+          python3Packages: with python3Packages; [
+            qtile-extras
+          ];
+        '';
+      };
   };
 
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = [{
       name = "qtile";
       start = ''
-        ${cfg.package}/bin/qtile start &
+        ${pyEnv}/bin/qtile start -b ${cfg.backend} \
+        ${optionalString (cfg.configFile != null)
+        "--config \"${cfg.configFile}\""} &
         waitPID=$!
       '';
     }];
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix b/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
index 0d58481d4579..1de0fad3e54d 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.ratpoison.enable = mkEnableOption "ratpoison";
+    services.xserver.windowManager.ratpoison.enable = mkEnableOption (lib.mdDoc "ratpoison");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix b/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
index b988b5e1829e..1945a1af6763 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.sawfish.enable = mkEnableOption "sawfish";
+    services.xserver.windowManager.sawfish.enable = mkEnableOption (lib.mdDoc "sawfish");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix
index 091ba4f92b94..e92b18690d8a 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.smallwm.enable = mkEnableOption "smallwm";
+    services.xserver.windowManager.smallwm.enable = mkEnableOption (lib.mdDoc "smallwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
index a1dc298d2426..c464803a0b6a 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
@@ -9,7 +9,7 @@ in
 
 {
   options = {
-    services.xserver.windowManager.spectrwm.enable = mkEnableOption "spectrwm";
+    services.xserver.windowManager.spectrwm.enable = mkEnableOption (lib.mdDoc "spectrwm");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
index 27a17178476a..c6fc49f5821b 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
@@ -8,17 +8,17 @@ in
 
 {
   options = {
-    services.xserver.windowManager.stumpwm.enable = mkEnableOption "stumpwm";
+    services.xserver.windowManager.stumpwm.enable = mkEnableOption (lib.mdDoc "stumpwm");
   };
 
   config = mkIf cfg.enable {
     services.xserver.windowManager.session = singleton {
       name = "stumpwm";
       start = ''
-        ${pkgs.lispPackages.stumpwm}/bin/stumpwm &
+        ${pkgs.sbclPackages.stumpwm}/bin/stumpwm &
         waitPID=$!
       '';
     };
-    environment.systemPackages = [ pkgs.lispPackages.stumpwm ];
+    environment.systemPackages = [ pkgs.sbclPackages.stumpwm ];
   };
 }
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix
index 8e5d9b9170ca..7418a6ddc760 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.tinywm.enable = mkEnableOption "tinywm";
+    services.xserver.windowManager.tinywm.enable = mkEnableOption (lib.mdDoc "tinywm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
index fc09901aae3b..231817a26e66 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
@@ -13,7 +13,7 @@ in
   ###### interface
 
   options = {
-    services.xserver.windowManager.twm.enable = mkEnableOption "twm";
+    services.xserver.windowManager.twm.enable = mkEnableOption (lib.mdDoc "twm");
   };
 
 
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix b/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
index fb891a39fa41..9a0646b6ee7d 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
@@ -7,7 +7,7 @@ in
 {
   options = {
     services.xserver.windowManager.windowlab.enable =
-      lib.mkEnableOption "windowlab";
+      lib.mkEnableOption (lib.mdDoc "windowlab");
   };
 
   config = lib.mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix b/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
index b62723758056..a679e2b5bc80 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.windowmaker.enable = mkEnableOption "windowmaker";
+    services.xserver.windowManager.windowmaker.enable = mkEnableOption (lib.mdDoc "windowmaker");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/wmderland.nix b/nixpkgs/nixos/modules/services/x11/window-managers/wmderland.nix
index 835c1b302817..ed515741f62e 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/wmderland.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/wmderland.nix
@@ -8,7 +8,7 @@ in
 
 {
   options.services.xserver.windowManager.wmderland = {
-    enable = mkEnableOption "wmderland";
+    enable = mkEnableOption (lib.mdDoc "wmderland");
 
     extraSessionCommands = mkOption {
       default = "";
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix b/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
index 9b50a99bf23f..090aa31610ab 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options = {
-    services.xserver.windowManager.wmii.enable = mkEnableOption "wmii";
+    services.xserver.windowManager.wmii.enable = mkEnableOption (lib.mdDoc "wmii");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
index f616802acc44..c35446bf405b 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -41,18 +41,18 @@ in {
 
   options = {
     services.xserver.windowManager.xmonad = {
-      enable = mkEnableOption "xmonad";
+      enable = mkEnableOption (lib.mdDoc "xmonad");
 
       haskellPackages = mkOption {
         default = pkgs.haskellPackages;
         defaultText = literalExpression "pkgs.haskellPackages";
-        example = literalExpression "pkgs.haskell.packages.ghc8107";
+        example = literalExpression "pkgs.haskell.packages.ghc810";
         type = types.attrs;
-        description = ''
+        description = lib.mdDoc ''
           haskellPackages used to build Xmonad and other packages.
           This can be used to change the GHC version used to build
           Xmonad and the packages listed in
-          <varname>extraPackages</varname>.
+          {var}`extraPackages`.
         '';
       };
 
@@ -66,10 +66,10 @@ in {
             haskellPackages.monad-logger
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra packages available to ghc when rebuilding Xmonad. The
           value must be a function which receives the attrset defined
-          in <varname>haskellPackages</varname> as the sole argument.
+          in {var}`haskellPackages` as the sole argument.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix
index 351bd7dfe48b..9b40cecace26 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix
@@ -8,7 +8,7 @@ in
 {
   ###### interface
   options = {
-    services.xserver.windowManager.yeahwm.enable = mkEnableOption "yeahwm";
+    services.xserver.windowManager.yeahwm.enable = mkEnableOption (lib.mdDoc "yeahwm");
   };
 
   ###### implementation
diff --git a/nixpkgs/nixos/modules/services/x11/xautolock.nix b/nixpkgs/nixos/modules/services/x11/xautolock.nix
index ca3909d7b80c..5b8b748a086b 100644
--- a/nixpkgs/nixos/modules/services/x11/xautolock.nix
+++ b/nixpkgs/nixos/modules/services/x11/xautolock.nix
@@ -8,9 +8,9 @@ in
   {
     options = {
       services.xserver.xautolock = {
-        enable = mkEnableOption "xautolock";
-        enableNotifier = mkEnableOption "xautolock.notify" // {
-          description = ''
+        enable = mkEnableOption (lib.mdDoc "xautolock");
+        enableNotifier = mkEnableOption (lib.mdDoc "xautolock.notify") // {
+          description = lib.mdDoc ''
             Whether to enable the notifier feature of xautolock.
             This publishes a notification before the autolock.
           '';
@@ -71,7 +71,7 @@ in
           type = types.nullOr types.str;
 
           description = lib.mdDoc ''
-            The script to use when nothing has happend for as long as {option}`killtime`
+            The script to use when nothing has happened for as long as {option}`killtime`
           '';
         };
 
diff --git a/nixpkgs/nixos/modules/services/x11/xbanish.nix b/nixpkgs/nixos/modules/services/x11/xbanish.nix
index f494f2054a40..de893fae75a1 100644
--- a/nixpkgs/nixos/modules/services/x11/xbanish.nix
+++ b/nixpkgs/nixos/modules/services/x11/xbanish.nix
@@ -7,7 +7,7 @@ let cfg = config.services.xbanish;
 in {
   options.services.xbanish = {
 
-    enable = mkEnableOption "xbanish";
+    enable = mkEnableOption (lib.mdDoc "xbanish");
 
     arguments = mkOption {
       description = lib.mdDoc "Arguments to pass to xbanish command";
diff --git a/nixpkgs/nixos/modules/services/x11/xserver.nix b/nixpkgs/nixos/modules/services/x11/xserver.nix
index dc48f3ac030a..6d2321be8ef7 100644
--- a/nixpkgs/nixos/modules/services/x11/xserver.nix
+++ b/nixpkgs/nixos/modules/services/x11/xserver.nix
@@ -121,7 +121,7 @@ let
           fi
         done
 
-        for i in $(find ${toString cfg.modules} -type d); do
+        for i in $(find ${toString cfg.modules} -type d | sort); do
           if test $(echo $i/*.so* | wc -w) -ne 0; then
             echo "  ModulePath \"$i\"" >> $out
           fi
@@ -138,6 +138,26 @@ let
     concatMapStringsSep "\n" (line: prefix + line) (splitString "\n" str);
 
   indent = prefixStringLines "  ";
+
+  # A scalable variant of the X11 "core" cursor
+  #
+  # If not running a fancy desktop environment, the cursor is likely set to
+  # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very
+  # small and almost invisible on 4K displays.
+  fontcursormisc_hidpi = pkgs.xorg.fontxfree86type1.overrideAttrs (old:
+    let
+      # The scaling constant is 230/96: the scalable `left_ptr` glyph at
+      # about 23 points is rendered as 17px, on a 96dpi display.
+      # Note: the XLFD font size is in decipoints.
+      size = 2.39583 * cfg.dpi;
+      sizeString = builtins.head (builtins.split "\\." (toString size));
+    in
+    {
+      postInstall = ''
+        alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific'
+        echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias
+      '';
+    });
 in
 
 {
@@ -151,8 +171,10 @@ in
       (mkRemovedOptionModule
         [ "services" "xserver" "startDbusSession" ]
         "The user D-Bus session is now always socket activated and this option can safely be removed.")
-      (mkRemovedOptionModule ["services" "xserver" "useXFS" ]
+      (mkRemovedOptionModule [ "services" "xserver" "useXFS" ]
         "Use services.xserver.fontPath instead of useXFS")
+      (mkRemovedOptionModule [ "services" "xserver" "useGlamor" ]
+        "Option services.xserver.useGlamor was removed because it is unnecessary. Drivers that uses Glamor will use it automatically.")
     ];
 
 
@@ -254,7 +276,7 @@ in
 
       videoDrivers = mkOption {
         type = types.listOf types.str;
-        default = [ "amdgpu" "radeon" "nouveau" "modesetting" "fbdev" ];
+        default = [ "modesetting" "fbdev" ];
         example = [
           "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
           "amdgpu-pro"
@@ -292,7 +314,7 @@ in
       drivers = mkOption {
         type = types.listOf types.attrs;
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           A list of attribute sets specifying drivers to be loaded by
           the X11 server.
         '';
@@ -429,23 +451,25 @@ in
           firstPrimary = head heads // { primary = true; };
           newHeads = singleton firstPrimary ++ tail heads;
         in if heads != [] && !hasPrimary then newHeads else heads;
-        description = ''
+        description = lib.mdDoc ''
           Multiple monitor configuration, just specify a list of XRandR
           outputs. The individual elements should be either simple strings or
           an attribute set of output options.
 
           If the element is a string, it is denoting the physical output for a
           monitor, if it's an attribute set, you must at least provide the
-          <option>output</option> option.
+          {option}`output` option.
 
           The monitors will be mapped from left to right in the order of the
           list.
 
           By default, the first monitor will be set as the primary monitor if
           none of the elements contain an option that has set
-          <option>primary</option> to <literal>true</literal>.
+          {option}`primary` to `true`.
 
-          <note><para>Only one monitor is allowed to be primary.</para></note>
+          ::: {.note}
+          Only one monitor is allowed to be primary.
+          :::
 
           Be careful using this option with multiple graphic adapters or with
           drivers that have poor support for XRandR, unexpected things might
@@ -555,15 +579,6 @@ in
         '';
       };
 
-      useGlamor = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to use the Glamor module for 2D acceleration,
-          if possible.
-        '';
-      };
-
       enableCtrlAltBackspace = mkOption {
         type = types.bool;
         default = false;
@@ -581,6 +596,15 @@ in
           Whether to terminate X upon server reset.
         '';
       };
+
+      upscaleDefaultCursor = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Upscale the default X cursor to be more visible on high-density displays.
+          Requires `config.services.xserver.dpi` to be set.
+        '';
+      };
     };
 
   };
@@ -597,7 +621,8 @@ in
                     || dmConf.sddm.enable
                     || dmConf.xpra.enable
                     || dmConf.sx.enable
-                    || dmConf.startx.enable);
+                    || dmConf.startx.enable
+                    || config.services.greetd.enable);
       in mkIf (default) (mkDefault true);
 
     # so that the service won't be enabled when only startx is used
@@ -631,6 +656,10 @@ in
                 + "${toString (length primaryHeads)} heads set to primary: "
                 + concatMapStringsSep ", " (x: x.output) primaryHeads;
       })
+      {
+        assertion = cfg.upscaleDefaultCursor -> cfg.dpi != null;
+        message = "Specify `config.services.xserver.dpi` to upscale the default cursor.";
+      }
     ];
 
     environment.etc =
@@ -747,7 +776,7 @@ in
         xorg.xf86inputevdev.out
       ];
 
-    system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
+    system.checks = singleton (pkgs.runCommand "xkb-validated" {
       inherit (cfg) xkbModel layout xkbVariant xkbOptions;
       nativeBuildInputs = with pkgs.buildPackages; [ xkbvalidate ];
       preferLocalBuild = true;
@@ -794,13 +823,6 @@ in
           '')}
         EndSection
 
-        ${if cfg.useGlamor then ''
-          Section "Module"
-            Load "dri2"
-            Load "glamoregl"
-          EndSection
-        '' else ""}
-
         # For each supported driver, add a "Device" and "Screen"
         # section.
         ${flip concatMapStrings cfg.drivers (driver: ''
@@ -808,7 +830,6 @@ in
           Section "Device"
             Identifier "Device-${driver.name}[0]"
             Driver "${driver.driverName or driver.name}"
-            ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
           ${indent cfg.deviceSection}
           ${indent (driver.deviceSection or "")}
           ${indent xrandrDeviceSection}
@@ -863,6 +884,10 @@ in
       '';
 
     fonts.enableDefaultFonts = mkDefault true;
+    fonts.fonts = [
+      (if cfg.upscaleDefaultCursor then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc)
+      pkgs.xorg.fontmiscmisc
+    ];
 
   };
 
diff --git a/nixpkgs/nixos/modules/system/activation/activation-script.nix b/nixpkgs/nixos/modules/system/activation/activation-script.nix
index 88b3ac1d18e8..f23d4809e356 100644
--- a/nixpkgs/nixos/modules/system/activation/activation-script.nix
+++ b/nixpkgs/nixos/modules/system/activation/activation-script.nix
@@ -78,22 +78,22 @@ let
       { deps = mkOption
           { type = types.listOf types.str;
             default = [ ];
-            description = "List of dependencies. The script will run after these.";
+            description = lib.mdDoc "List of dependencies. The script will run after these.";
           };
         text = mkOption
           { type = types.lines;
-            description = "The content of the script.";
+            description = lib.mdDoc "The content of the script.";
           };
       } // optionalAttrs withDry {
         supportsDryActivation = mkOption
           { type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether this activation script supports being dry-activated.
               These activation scripts will also be executed on dry-activate
               activations with the environment variable
-              <literal>NIXOS_ACTION</literal> being set to <literal>dry-activate
-              </literal>.  it's important that these activation scripts  don't
+              `NIXOS_ACTION` being set to `dry-activate`.
+              it's important that these activation scripts  don't
               modify anything about the system when the variable is set.
             '';
           };
@@ -139,11 +139,11 @@ in
     };
 
     system.dryActivationScript = mkOption {
-      description = "The shell script that is to be run when dry-activating a system.";
+      description = lib.mdDoc "The shell script that is to be run when dry-activating a system.";
       readOnly = true;
       internal = true;
       default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
-      defaultText = literalDocBook "generated activation script";
+      defaultText = literalMD "generated activation script";
     };
 
     system.userActivationScripts = mkOption {
@@ -199,9 +199,9 @@ in
       example = literalExpression ''"''${pkgs.busybox}/bin/env"'';
       type = types.nullOr types.path;
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         The env(1) executable that is linked system-wide to
-        <literal>/usr/bin/env</literal>.
+        `/usr/bin/env`.
       '';
     };
   };
@@ -217,7 +217,8 @@ in
       ''
         # Various log/runtime directories.
 
-        mkdir -m 1777 -p /var/tmp
+        mkdir -p /var/tmp
+        chmod 1777 /var/tmp
 
         # Empty, immutable home directory of many system accounts.
         mkdir -p /var/empty
@@ -231,7 +232,8 @@ in
 
     system.activationScripts.usrbinenv = if config.environment.usrbinenv != null
       then ''
-        mkdir -m 0755 -p /usr/bin
+        mkdir -p /usr/bin
+        chmod 0755 /usr/bin
         ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
         mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
       ''
@@ -251,7 +253,8 @@ in
           if mountpoint -q "$mountPoint"; then
             local options="remount,$options"
           else
-            mkdir -m 0755 -p "$mountPoint"
+            mkdir -p "$mountPoint"
+            chmod 0755 "$mountPoint"
           fi
           mount -t "$fsType" -o "$options" "$device" "$mountPoint"
         }
diff --git a/nixpkgs/nixos/modules/system/activation/bootspec.cue b/nixpkgs/nixos/modules/system/activation/bootspec.cue
new file mode 100644
index 000000000000..1f7b4afa87ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/bootspec.cue
@@ -0,0 +1,31 @@
+import "struct"
+
+#BootspecV1: {
+	system:         string
+	init:           string
+	initrd?:        string
+	initrdSecrets?: string
+	kernel:         string
+	kernelParams: [...string]
+	label:    string
+	toplevel: string
+}
+
+// A restricted document does not allow any official specialisation
+// information in it to avoid "recursive specialisations".
+#RestrictedDocument: struct.MinFields(1) & {
+	"org.nixos.bootspec.v1": #BootspecV1
+	[=~"^"]:                 #BootspecExtension
+}
+
+// Specialisations are a hashmap of strings
+#BootspecSpecialisationV1: [string]: #RestrictedDocument
+
+// Bootspec extensions are defined by the extension author.
+#BootspecExtension: {...}
+
+// A "full" document allows official specialisation information
+// in the top-level with a reserved namespaced key.
+Document: #RestrictedDocument & {
+	"org.nixos.specialisation.v1"?: #BootspecSpecialisationV1
+}
diff --git a/nixpkgs/nixos/modules/system/activation/bootspec.nix b/nixpkgs/nixos/modules/system/activation/bootspec.nix
new file mode 100644
index 000000000000..9e1fa309d5db
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/bootspec.nix
@@ -0,0 +1,118 @@
+# Note that these schemas are defined by RFC-0125.
+# This document is considered a stable API, and is depended upon by external tooling.
+# Changes to the structure of the document, or the semantics of the values should go through an RFC.
+#
+# See: https://github.com/NixOS/rfcs/pull/125
+{ config
+, pkgs
+, lib
+, ...
+}:
+let
+  cfg = config.boot.bootspec;
+  children = lib.mapAttrs (childName: childConfig: childConfig.configuration.system.build.toplevel) config.specialisation;
+  schemas = {
+    v1 = rec {
+      filename = "boot.json";
+      json =
+        pkgs.writeText filename
+        (builtins.toJSON
+          # Merge extensions first to not let them shadow NixOS bootspec data.
+          (cfg.extensions //
+          {
+            "org.nixos.bootspec.v1" = {
+              system = config.boot.kernelPackages.stdenv.hostPlatform.system;
+              kernel = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
+              kernelParams = config.boot.kernelParams;
+              label = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";
+            } // lib.optionalAttrs config.boot.initrd.enable {
+              initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
+              initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
+            };
+          }));
+
+      generator =
+        let
+          # NOTE: Be careful to not introduce excess newlines at the end of the
+          # injectors, as that may affect the pipes and redirects.
+
+          # Inject toplevel and init into the bootspec.
+          # This can only be done here because we *cannot* depend on $out
+          # referring to the toplevel, except by living in the toplevel itself.
+          toplevelInjector = lib.escapeShellArgs [
+            "${pkgs.buildPackages.jq}/bin/jq"
+            ''
+              ."org.nixos.bootspec.v1".toplevel = $toplevel |
+              ."org.nixos.bootspec.v1".init = $init
+            ''
+            "--sort-keys"
+            "--arg" "toplevel" "${placeholder "out"}"
+            "--arg" "init" "${placeholder "out"}/init"
+          ] + " < ${json}";
+
+          # We slurp all specialisations and inject them as values, such that
+          # `.specialisations.${name}` embeds the specialisation's bootspec
+          # document.
+          specialisationInjector =
+            let
+              specialisationLoader = (lib.mapAttrsToList
+                (childName: childToplevel: lib.escapeShellArgs [ "--slurpfile" childName "${childToplevel}/${filename}" ])
+                children);
+            in
+            lib.escapeShellArgs [
+              "${pkgs.buildPackages.jq}/bin/jq"
+              "--sort-keys"
+              ''."org.nixos.specialisation.v1" = ($ARGS.named | map_values(. | first))''
+            ] + " ${lib.concatStringsSep " " specialisationLoader}";
+        in
+        "${toplevelInjector} | ${specialisationInjector} > $out/${filename}";
+
+      validator = pkgs.writeCueValidator ./bootspec.cue {
+        document = "Document"; # Universal validator for any version as long the schema is correctly set.
+      };
+    };
+  };
+in
+{
+  options.boot.bootspec = {
+    enable = lib.mkEnableOption (lib.mdDoc "the generation of RFC-0125 bootspec in $system/boot.json, e.g. /run/current-system/boot.json")
+      // { default = true; internal = true; };
+    enableValidation = lib.mkEnableOption (lib.mdDoc ''the validation of bootspec documents for each build.
+      This will introduce Go in the build-time closure as we are relying on [Cuelang](https://cuelang.org/) for schema validation.
+      Enable this option if you want to ascertain that your documents are correct.
+      ''
+    );
+
+    extensions = lib.mkOption {
+      # NOTE(RaitoBezarius): this is not enough to validate: extensions."osRelease" = drv; those are picked up by cue validation.
+      type = lib.types.attrsOf lib.types.anything; # <namespace>: { ...namespace-specific fields }
+      default = { };
+      description = lib.mdDoc ''
+        User-defined data that extends the bootspec document.
+
+        To reduce incompatibility and prevent names from clashing
+        between applications, it is **highly recommended** to use a
+        unique namespace for your extensions.
+      '';
+    };
+
+    # This will be run as a part of the `systemBuilder` in ./top-level.nix. This
+    # means `$out` points to the output of `config.system.build.toplevel` and can
+    # be used for a variety of things (though, for now, it's only used to report
+    # the path of the `toplevel` itself and the `init` executable).
+    writer = lib.mkOption {
+      internal = true;
+      default = schemas.v1.generator;
+    };
+
+    validator = lib.mkOption {
+      internal = true;
+      default = schemas.v1.validator;
+    };
+
+    filename = lib.mkOption {
+      internal = true;
+      default = schemas.v1.filename;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/activation/specialisation.nix b/nixpkgs/nixos/modules/system/activation/specialisation.nix
new file mode 100644
index 000000000000..86603c847641
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/specialisation.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, extendModules, noUserModules, ... }:
+
+let
+  inherit (lib)
+    concatStringsSep
+    mapAttrs
+    mapAttrsToList
+    mkOption
+    types
+    ;
+
+  # This attribute is responsible for creating boot entries for
+  # child configuration. They are only (directly) accessible
+  # when the parent configuration is boot default. For example,
+  # you can provide an easy way to boot the same configuration
+  # as you use, but with another kernel
+  # !!! fix this
+  children =
+    mapAttrs
+      (childName: childConfig: childConfig.configuration.system.build.toplevel)
+      config.specialisation;
+
+in
+{
+  options = {
+
+    specialisation = mkOption {
+      default = { };
+      example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.settings = { core = 0; max-jobs = 1; }; }; }";
+      description = lib.mdDoc ''
+        Additional configurations to build. If
+        `inheritParentConfig` is true, the system
+        will be based on the overall system configuration.
+
+        To switch to a specialised configuration
+        (e.g. `fewJobsManyCores`) at runtime, run:
+
+        ```
+        sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test
+        ```
+      '';
+      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;
+            description = lib.mdDoc "Include the entire system's configuration. Set to false to make a completely differently configured system.";
+          };
+
+          options.configuration = mkOption {
+            default = { };
+            description = lib.mdDoc ''
+              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;
+          };
+        }
+      ));
+    };
+
+  };
+
+  config = {
+    system.systemBuilderCommands = ''
+      mkdir $out/specialisation
+      ${concatStringsSep "\n"
+      (mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)}
+    '';
+  };
+
+  # uses extendModules to generate a type
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl b/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
index f39549db883d..de6e43dd30d8 100755
--- a/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
@@ -84,7 +84,7 @@ EOF
 
 # This is a NixOS installation if it has /etc/NIXOS or a proper
 # /etc/os-release.
-if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // "") !~ /^ID="?nixos"?/msx) {
+if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // "") !~ /^ID="?@distroId@"?/msx) {
     die("This is not a NixOS installation!\n");
 }
 
@@ -167,7 +167,7 @@ sub get_active_units {
 # Takes the name of the unit as an argument and returns a bool whether the unit is active or not.
 sub unit_is_active {
     my ($unit_name) = @_;
-    my $units = busctl_call_systemd1_mgr("ListUnitsByNames", "as", 1, $unit_name)->{data}->[0];
+    my $units = busctl_call_systemd1_mgr("ListUnitsByNames", "as", 1, , "--", $unit_name)->{data}->[0];
     if (scalar(@{$units}) == 0) {
         return 0;
     }
diff --git a/nixpkgs/nixos/modules/system/activation/test.nix b/nixpkgs/nixos/modules/system/activation/test.nix
new file mode 100644
index 000000000000..8cf000451c6e
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/activation/test.nix
@@ -0,0 +1,27 @@
+{ lib
+, nixos
+, expect
+, testers
+}:
+let
+  node-forbiddenDependencies-fail = nixos ({ ... }: {
+    system.forbiddenDependenciesRegex = "-dev$";
+    environment.etc."dev-dependency" = {
+      text = "${expect.dev}";
+    };
+    documentation.enable = false;
+    fileSystems."/".device = "ignore-root-device";
+    boot.loader.grub.enable = false;
+  });
+  node-forbiddenDependencies-succeed = nixos ({ ... }: {
+    system.forbiddenDependenciesRegex = "-dev$";
+    system.extraDependencies = [ expect.dev ];
+    documentation.enable = false;
+    fileSystems."/".device = "ignore-root-device";
+    boot.loader.grub.enable = false;
+  });
+in
+lib.recurseIntoAttrs {
+  test-forbiddenDependencies-fail = testers.testBuildFailure node-forbiddenDependencies-fail.config.system.build.toplevel;
+  test-forbiddenDependencies-succeed = node-forbiddenDependencies-succeed.config.system.build.toplevel;
+}
diff --git a/nixpkgs/nixos/modules/system/activation/top-level.nix b/nixpkgs/nixos/modules/system/activation/top-level.nix
index 76150cc95999..c4427149d9c9 100644
--- a/nixpkgs/nixos/modules/system/activation/top-level.nix
+++ b/nixpkgs/nixos/modules/system/activation/top-level.nix
@@ -1,21 +1,8 @@
-{ config, lib, pkgs, extendModules, noUserModules, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
-
-
-  # This attribute is responsible for creating boot entries for
-  # child configuration. They are only (directly) accessible
-  # when the parent configuration is boot default. For example,
-  # you can provide an easy way to boot the same configuration
-  # as you use, but with another kernel
-  # !!! fix this
-  children =
-    mapAttrs
-      (childName: childConfig: childConfig.configuration.system.build.toplevel)
-      config.specialisation;
-
   systemBuilder =
     let
       kernelPath = "${config.boot.kernelPackages.kernel}/" +
@@ -27,7 +14,7 @@ let
 
       # Containers don't have their own kernel or initrd.  They boot
       # directly into stage 2.
-      ${optionalString (!config.boot.isContainer) ''
+      ${optionalString config.boot.kernel.enable ''
         if [ ! -f ${kernelPath} ]; then
           echo "The bootloader cannot find the proper kernel image."
           echo "(Expecting ${kernelPath})"
@@ -72,17 +59,13 @@ let
       ln -s ${config.system.path} $out/sw
       ln -s "$systemd" $out/systemd
 
-      echo -n "$configurationName" > $out/configuration-name
       echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
       echo -n "$nixosLabel" > $out/nixos-version
       echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
 
-      mkdir $out/specialisation
-      ${concatStringsSep "\n"
-      (mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)}
-
       mkdir $out/bin
       export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
+      export distroId=${config.system.nixos.distroId};
       substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
       chmod +x $out/bin/switch-to-configuration
       ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
@@ -93,7 +76,15 @@ let
         fi
       ''}
 
-      echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies
+      ${config.system.systemBuilderCommands}
+
+      cp "$extraDependenciesPath" "$out/extra-dependencies"
+
+      ${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
+        ${config.boot.bootspec.writer}
+        ${optionalString config.boot.bootspec.enableValidation
+          ''${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"''}
+      ''}
 
       ${config.system.extraSystemBuilderCmds}
     '';
@@ -103,10 +94,11 @@ let
   # kernel, systemd units, init scripts, etc.) as well as a script
   # `switch-to-configuration' that activates the configuration and
   # makes it bootable.
-  baseSystem = pkgs.stdenvNoCC.mkDerivation {
+  baseSystem = pkgs.stdenvNoCC.mkDerivation ({
     name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
     preferLocalBuild = true;
     allowSubstitutes = false;
+    passAsFile = [ "extraDependencies" ];
     buildCommand = systemBuilder;
 
     inherit (pkgs) coreutils;
@@ -121,11 +113,11 @@ let
     dryActivationScript = config.system.dryActivationScript;
     nixosLabel = config.system.nixos.label;
 
-    configurationName = config.boot.loader.grub.configurationName;
+    inherit (config.system) extraDependencies;
 
     # Needed by switch-to-configuration.
     perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
-  };
+  } // config.system.systemBuilderArgs);
 
   # Handle assertions and warnings
 
@@ -140,15 +132,12 @@ let
       pkgs.replaceDependency { inherit oldDependency newDependency drv; }
     ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
 
-  /* Workaround until https://github.com/NixOS/nixpkgs/pull/156533
-     Call can be replaced by argument when that's merged.
-  */
-  tmpFixupSubmoduleBoundary = subopts:
-    lib.mkOption {
-      type = lib.types.submoduleWith {
-        modules = [ { options = subopts; } ];
-      };
-    };
+  systemWithBuildDeps = system.overrideAttrs (o: {
+    systemBuildClosure = pkgs.closureInfo { rootPaths = [ system.drvPath ]; };
+    buildCommand = o.buildCommand + ''
+      ln -sn $systemBuildClosure $out/build-closure
+    '';
+  });
 
 in
 
@@ -161,53 +150,10 @@ in
 
   options = {
 
-    specialisation = mkOption {
-      default = {};
-      example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.settings = { core = 0; max-jobs = 1; }; }; }";
-      description = ''
-        Additional configurations to build. If
-        <literal>inheritParentConfig</literal> is true, the system
-        will be based on the overall system configuration.
-
-        To switch to a specialised configuration
-        (e.g. <literal>fewJobsManyCores</literal>) at runtime, run:
-
-        <screen>
-        <prompt># </prompt>sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test
-        </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;
-            description = lib.mdDoc "Include the entire system's configuration. Set to false to make a completely differently configured system.";
-          };
-
-          options.configuration = mkOption {
-            default = {};
-            description = lib.mdDoc ''
-              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;
-          };
-        })
-      );
-    };
-
     system.boot.loader.id = mkOption {
       internal = true;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Id string of the used bootloader.
       '';
     };
@@ -217,7 +163,7 @@ in
       default = pkgs.stdenv.hostPlatform.linux-kernel.target;
       defaultText = literalExpression "pkgs.stdenv.hostPlatform.linux-kernel.target";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Name of the kernel file to be passed to the bootloader.
       '';
     };
@@ -226,22 +172,22 @@ in
       internal = true;
       default = "initrd";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Name of the initrd file to be passed to the bootloader.
       '';
     };
 
-    system.build = tmpFixupSubmoduleBoundary {
+    system.build = {
       installBootLoader = mkOption {
         internal = true;
         # "; true" => make the `$out` argument from switch-to-configuration.pl
         #             go to `true` instead of `echo`, hiding the useless path
         #             from the log.
         default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
-        description = ''
+        description = lib.mdDoc ''
           A program that writes a bootloader installation script to the path passed in the first command line argument.
 
-          See <literal>nixos/modules/system/activation/switch-to-configuration.pl</literal>.
+          See `nixos/modules/system/activation/switch-to-configuration.pl`.
         '';
         type = types.unique {
           message = ''
@@ -276,7 +222,7 @@ in
       '';
     };
 
-    system.extraSystemBuilderCmds = mkOption {
+    system.systemBuilderCommands = mkOption {
       type = types.lines;
       internal = true;
       default = "";
@@ -285,13 +231,56 @@ in
       '';
     };
 
+    system.systemBuilderArgs = mkOption {
+      type = types.attrsOf types.unspecified;
+      internal = true;
+      default = {};
+      description = lib.mdDoc ''
+        `lib.mkDerivation` attributes that will be passed to the top level system builder.
+      '';
+    };
+
+    system.forbiddenDependenciesRegex = mkOption {
+      default = "";
+      example = "-dev$";
+      type = types.str;
+      description = lib.mdDoc ''
+        A POSIX Extended Regular Expression that matches store paths that
+        should not appear in the system closure, with the exception of {option}`system.extraDependencies`, which is not checked.
+      '';
+    };
+
+    system.extraSystemBuilderCmds = mkOption {
+      type = types.lines;
+      internal = true;
+      default = "";
+      description = lib.mdDoc ''
+        This code will be added to the builder creating the system store path.
+      '';
+    };
+
     system.extraDependencies = mkOption {
       type = types.listOf types.package;
       default = [];
       description = lib.mdDoc ''
         A list of packages that should be included in the system
-        closure but not otherwise made available to users. This is
-        primarily used by the installation tests.
+        closure but generally not visible to users.
+
+        This option has also been used for build-time checks, but the
+        `system.checks` option is more appropriate for that purpose as checks
+        should not leave a trace in the built system configuration.
+      '';
+    };
+
+    system.checks = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      description = lib.mdDoc ''
+        Packages that are added as dependencies of the system's build, usually
+        for the purpose of validating some part of the configuration.
+
+        Unlike `system.extraDependencies`, these store paths do not
+        become part of the built system configuration.
       '';
     };
 
@@ -341,22 +330,76 @@ in
       '';
     };
 
+    system.includeBuildDependencies = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to include the build closure of the whole system in
+        its runtime closure.  This can be useful for making changes
+        fully offline, as it includes all sources, patches, and
+        intermediate outputs required to build all the derivations
+        that the system depends on.
+
+        Note that this includes _all_ the derivations, down from the
+        included applications to their sources, the compilers used to
+        build them, and even the bootstrap compiler used to compile
+        the compilers. This increases the size of the system and the
+        time needed to download its dependencies drastically: a
+        minimal configuration with no extra services enabled grows
+        from ~670MiB in size to 13.5GiB, and takes proportionally
+        longer to download.
+      '';
+    };
+
   };
 
 
   config = {
+    assertions = [
+      {
+        assertion = config.system.copySystemConfiguration -> !lib.inPureEvalMode;
+        message = "system.copySystemConfiguration is not supported with flakes";
+      }
+    ];
 
     system.extraSystemBuilderCmds =
       optionalString
         config.system.copySystemConfiguration
         ''ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \
             "$out/configuration.nix"
+        '' +
+      optionalString
+        (config.system.forbiddenDependenciesRegex != "")
+        ''
+          if [[ $forbiddenDependenciesRegex != "" && -n $closureInfo ]]; then
+            if forbiddenPaths="$(grep -E -- "$forbiddenDependenciesRegex" $closureInfo/store-paths)"; then
+              echo -e "System closure $out contains the following disallowed paths:\n$forbiddenPaths"
+              exit 1
+            fi
+          fi
         '';
 
-    system.build.toplevel = system;
+    system.systemBuilderArgs = {
+      # Not actually used in the builder. `passedChecks` is just here to create
+      # the build dependencies. Checks are similar to build dependencies in the
+      # sense that if they fail, the system build fails. However, checks do not
+      # produce any output of value, so they are not used by the system builder.
+      # In fact, using them runs the risk of accidentally adding unneeded paths
+      # to the system closure, which defeats the purpose of the `system.checks`
+      # option, as opposed to `system.extraDependencies`.
+      passedChecks = concatStringsSep " " config.system.checks;
+    }
+    // lib.optionalAttrs (config.system.forbiddenDependenciesRegex != "") {
+      inherit (config.system) forbiddenDependenciesRegex;
+      closureInfo = pkgs.closureInfo { rootPaths = [
+        # override to avoid  infinite recursion (and to allow using extraDependencies to add forbidden dependencies)
+        (config.system.build.toplevel.overrideAttrs (_: { extraDependencies = []; closureInfo = null; }))
+      ]; };
+    };
+
+
+    system.build.toplevel = if config.system.includeBuildDependencies then systemWithBuildDeps else system;
 
   };
 
-  # uses extendModules to generate a type
-  meta.buildDocsInSandbox = false;
 }
diff --git a/nixpkgs/nixos/modules/system/boot/binfmt.nix b/nixpkgs/nixos/modules/system/boot/binfmt.nix
index 4d95af61ac94..bf1688feb19e 100644
--- a/nixpkgs/nixos/modules/system/boot/binfmt.nix
+++ b/nixpkgs/nixos/modules/system/boot/binfmt.nix
@@ -1,6 +1,6 @@
 { config, lib, pkgs, ... }:
 let
-  inherit (lib) mkOption types optionalString stringAfter;
+  inherit (lib) mkOption mkDefault types optionalString stringAfter;
 
   cfg = config.boot.binfmt;
 
@@ -125,6 +125,10 @@ let
       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
     };
+    loongarch64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
     wasm32-wasi = {
       magicOrExtension = ''\x00asm'';
       mask = ''\xff\xff\xff\xff'';
@@ -134,11 +138,11 @@ let
       mask = ''\xff\xff\xff\xff'';
     };
     x86_64-windows = {
-      magicOrExtension = ".exe";
+      magicOrExtension = "exe";
       recognitionType = "extension";
     };
     i686-windows = {
-      magicOrExtension = ".exe";
+      magicOrExtension = "exe";
       recognitionType = "extension";
     };
   };
@@ -256,7 +260,7 @@ in {
             interpreterSandboxPath = mkOption {
               internal = true;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Path of the interpreter to expose in the build sandbox.
               '';
               type = types.nullOr types.path;
@@ -281,7 +285,7 @@ in {
   config = {
     boot.binfmt.registrations = builtins.listToAttrs (map (system: {
       name = system;
-      value = let
+      value = { config, ... }: let
         interpreter = getEmulator system;
         qemuArch = getQemuArch system;
 
@@ -292,13 +296,13 @@ in {
         in
           if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
           else interpreter;
-      in {
-        inherit preserveArgvZero;
+      in ({
+        preserveArgvZero = mkDefault preserveArgvZero;
 
-        interpreter = interpreterReg;
-        wrapInterpreterInShell = !preserveArgvZero;
-        interpreterSandboxPath = dirOf (dirOf interpreterReg);
-      } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
+        interpreter = mkDefault interpreterReg;
+        wrapInterpreterInShell = mkDefault (!config.preserveArgvZero);
+        interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter));
+      } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")));
     }) cfg.emulatedSystems);
     nix.settings = lib.mkIf (cfg.emulatedSystems != []) {
       extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
@@ -313,13 +317,17 @@ in {
     environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
       (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
     system.activationScripts.binfmt = stringAfter [ "specialfs" ] ''
-      mkdir -p -m 0755 /run/binfmt
+      mkdir -p /run/binfmt
+      chmod 0755 /run/binfmt
       ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
     '';
-    systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {}) [
-      "proc-sys-fs-binfmt_misc.automount"
-      "proc-sys-fs-binfmt_misc.mount"
-      "systemd-binfmt.service"
-    ];
+    systemd = lib.mkIf (config.boot.binfmt.registrations != {}) {
+      additionalUpstreamSystemUnits = [
+        "proc-sys-fs-binfmt_misc.automount"
+        "proc-sys-fs-binfmt_misc.mount"
+        "systemd-binfmt.service"
+      ];
+      services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+    };
   };
 }
diff --git a/nixpkgs/nixos/modules/system/boot/grow-partition.nix b/nixpkgs/nixos/modules/system/boot/grow-partition.nix
index 87c981b24cec..a2764187a533 100644
--- a/nixpkgs/nixos/modules/system/boot/grow-partition.nix
+++ b/nixpkgs/nixos/modules/system/boot/grow-partition.nix
@@ -12,11 +12,16 @@ with lib;
   ];
 
   options = {
-    boot.growPartition = mkEnableOption "grow the root partition on boot";
+    boot.growPartition = mkEnableOption (lib.mdDoc "grow the root partition on boot");
   };
 
   config = mkIf config.boot.growPartition {
 
+    assertions = [{
+      assertion = !config.boot.initrd.systemd.enable;
+      message = "systemd stage 1 does not support 'boot.growPartition' yet.";
+    }];
+
     boot.initrd.extraUtilsCommands = ''
       copy_bin_and_libs ${pkgs.gawk}/bin/gawk
       copy_bin_and_libs ${pkgs.gnused}/bin/sed
diff --git a/nixpkgs/nixos/modules/system/boot/initrd-network.nix b/nixpkgs/nixos/modules/system/boot/initrd-network.nix
index a1017c3e2420..e8bbf1d04032 100644
--- a/nixpkgs/nixos/modules/system/boot/initrd-network.nix
+++ b/nixpkgs/nixos/modules/system/boot/initrd-network.nix
@@ -67,11 +67,15 @@ in
 
     boot.initrd.network.flushBeforeStage2 = mkOption {
       type = types.bool;
-      default = true;
+      default = !config.boot.initrd.systemd.enable;
+      defaultText = "!config.boot.initrd.systemd.enable";
       description = lib.mdDoc ''
         Whether to clear the configuration of the interfaces that were set up in
         the initrd right before stage 2 takes over. Stage 2 will do the regular network
         configuration based on the NixOS networking options.
+
+        The default is false when systemd is enabled in initrd,
+        because the systemd-networkd documentation suggests it.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix b/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix
index 9f476e07208a..2530240628e4 100644
--- a/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix
+++ b/nixpkgs/nixos/modules/system/boot/initrd-openvpn.nix
@@ -25,15 +25,13 @@ in
 
     boot.initrd.network.openvpn.configuration = mkOption {
       type = types.path; # Same type as boot.initrd.secrets
-      description = ''
+      description = lib.mdDoc ''
         The configuration file for OpenVPN.
 
-        <warning>
-          <para>
-            Unless your bootloader supports initrd secrets, this configuration
-            is stored insecurely in the global Nix store.
-          </para>
-        </warning>
+        ::: {.warning}
+        Unless your bootloader supports initrd secrets, this configuration
+        is stored insecurely in the global Nix store.
+        :::
       '';
       example = literalExpression "./configuration.ovpn";
     };
@@ -53,7 +51,7 @@ in
 
     # Add openvpn and ip binaries to the initrd
     # The shared libraries are required for DNS resolution
-    boot.initrd.extraUtilsCommands = ''
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn
       copy_bin_and_libs ${pkgs.iproute2}/bin/ip
 
@@ -61,21 +59,33 @@ in
       cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib
     '';
 
+    boot.initrd.systemd.storePaths = [
+      "${pkgs.openvpn}/bin/openvpn"
+      "${pkgs.iproute2}/bin/ip"
+      "${pkgs.glibc}/lib/libresolv.so.2"
+      "${pkgs.glibc}/lib/libnss_dns.so.2"
+    ];
+
     boot.initrd.secrets = {
       "/etc/initrd.ovpn" = cfg.configuration;
     };
 
     # openvpn --version would exit with 1 instead of 0
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       $out/bin/openvpn --show-gateway
     '';
 
-    # Add `iproute /bin/ip` to the config, to ensure that openvpn
-    # is able to set the routes
-    boot.initrd.network.postCommands = ''
-      (cat /etc/initrd.ovpn; echo -e '\niproute /bin/ip') | \
-        openvpn /dev/stdin &
+    boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      openvpn /etc/initrd.ovpn &
     '';
+
+    boot.initrd.systemd.services.openvpn = {
+      wantedBy = [ "initrd.target" ];
+      path = [ pkgs.iproute2 ];
+      after = [ "network.target" "initrd-nixos-copy-secrets.service" ];
+      serviceConfig.ExecStart = "${pkgs.openvpn}/bin/openvpn /etc/initrd.ovpn";
+      serviceConfig.Type = "notify";
+    };
   };
 
 }
diff --git a/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix b/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix
index 265399e562fc..60c5ff62ffff 100644
--- a/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixpkgs/nixos/modules/system/boot/initrd-ssh.nix
@@ -5,6 +5,10 @@ with lib;
 let
 
   cfg = config.boot.initrd.network.ssh;
+  shell = if cfg.shell == null then "/bin/ash" else cfg.shell;
+  inherit (config.programs.ssh) package;
+
+  enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable;
 
 in
 
@@ -25,7 +29,7 @@ in
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 22;
       description = lib.mdDoc ''
         Port on which SSH initrd service should listen.
@@ -33,8 +37,9 @@ in
     };
 
     shell = mkOption {
-      type = types.str;
-      default = "/bin/ash";
+      type = types.nullOr types.str;
+      default = null;
+      defaultText = ''"/bin/ash"'';
       description = lib.mdDoc ''
         Login shell of the remote user. Can be used to limit actions user can do.
       '';
@@ -47,31 +52,38 @@ in
         "/etc/secrets/initrd/ssh_host_rsa_key"
         "/etc/secrets/initrd/ssh_host_ed25519_key"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Specify SSH host keys to import into the initrd.
 
         To generate keys, use
-        <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>:
-
-        <screen>
-        <prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
-        <prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
-        </screen>
-
-        <warning>
-          <para>
-            Unless your bootloader supports initrd secrets, these keys
-            are stored insecurely in the global Nix store. Do NOT use
-            your regular SSH host private keys for this purpose or
-            you'll expose them to regular users!
-          </para>
-          <para>
-            Additionally, even if your initrd supports secrets, if
-            you're using initrd SSH to unlock an encrypted disk then
-            using your regular host keys exposes the private keys on
-            your unencrypted boot partition.
-          </para>
-        </warning>
+        {manpage}`ssh-keygen(1)`
+        as root:
+
+        ```
+        ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
+        ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
+        ```
+
+        ::: {.warning}
+        Unless your bootloader supports initrd secrets, these keys
+        are stored insecurely in the global Nix store. Do NOT use
+        your regular SSH host private keys for this purpose or
+        you'll expose them to regular users!
+
+        Additionally, even if your initrd supports secrets, if
+        you're using initrd SSH to unlock an encrypted disk then
+        using your regular host keys exposes the private keys on
+        your unencrypted boot partition.
+        :::
+      '';
+    };
+
+    ignoreEmptyHostKeys = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Allow leaving {option}`config.boot.initrd.network.ssh` empty,
+        to deploy ssh host keys out of band.
       '';
     };
 
@@ -112,22 +124,24 @@ in
     sshdCfg = config.services.openssh;
 
     sshdConfig = ''
+      UsePAM no
       Port ${toString cfg.port}
 
       PasswordAuthentication no
+      AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u
       ChallengeResponseAuthentication no
 
       ${flip concatMapStrings cfg.hostKeys (path: ''
         HostKey ${initrdKeyPath path}
       '')}
 
-      KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms}
-      Ciphers ${concatStringsSep "," sshdCfg.ciphers}
-      MACs ${concatStringsSep "," sshdCfg.macs}
+      KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms}
+      Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers}
+      MACs ${concatStringsSep "," sshdCfg.settings.Macs}
 
-      LogLevel ${sshdCfg.logLevel}
+      LogLevel ${sshdCfg.settings.LogLevel}
 
-      ${if sshdCfg.useDns then ''
+      ${if sshdCfg.settings.UseDns then ''
         UseDNS yes
       '' else ''
         UseDNS no
@@ -135,7 +149,7 @@ in
 
       ${cfg.extraConfig}
     '';
-  in mkIf (config.boot.initrd.network.enable && cfg.enable) {
+  in mkIf enabled {
     assertions = [
       {
         assertion = cfg.authorizedKeys != [];
@@ -143,21 +157,26 @@ in
       }
 
       {
-        assertion = cfg.hostKeys != [];
+        assertion = (cfg.hostKeys != []) || cfg.ignoreEmptyHostKeys;
         message = ''
           You must now pre-generate the host keys for initrd SSH.
           See the boot.initrd.network.ssh.hostKeys documentation
           for instructions.
         '';
       }
+
+      {
+        assertion = config.boot.initrd.systemd.enable -> cfg.shell == null;
+        message = "systemd stage 1 does not support boot.initrd.network.ssh.shell";
+      }
     ];
 
-    boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.openssh}/bin/sshd
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      copy_bin_and_libs ${package}/bin/sshd
       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
     '';
 
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       # sshd requires a host key to check config, so we pass in the test's
       tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
       cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
@@ -169,9 +188,9 @@ in
       rm "$tmpkey"
     '';
 
-    boot.initrd.network.postCommands = ''
-      echo '${cfg.shell}' > /etc/shells
-      echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
+    boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      echo '${shell}' > /etc/shells
+      echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd
       echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
       echo 'passwd: files' > /etc/nsswitch.conf
 
@@ -197,7 +216,7 @@ in
       /bin/sshd -e
     '';
 
-    boot.initrd.postMountCommands = ''
+    boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       # Stop sshd cleanly before stage 2.
       #
       # If you want to keep it around to debug post-mount SSH issues,
@@ -210,6 +229,38 @@ in
 
     boot.initrd.secrets = listToAttrs
       (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
+
+    # Systemd initrd stuff
+    boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable {
+      users.sshd = { uid = 1; group = "sshd"; };
+      groups.sshd = { gid = 1; };
+
+      contents."/etc/ssh/authorized_keys.d/root".text =
+        concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys;
+      contents."/etc/ssh/sshd_config".text = sshdConfig;
+      storePaths = ["${package}/bin/sshd"];
+
+      services.sshd = {
+        description = "SSH Daemon";
+        wantedBy = ["initrd.target"];
+        after = ["network.target" "initrd-nixos-copy-secrets.service"];
+
+        # Keys from Nix store are world-readable, which sshd doesn't
+        # like. If this were a real nix store and not the initrd, we
+        # neither would nor could do this
+        preStart = flip concatMapStrings cfg.hostKeys (path: ''
+          /bin/chmod 0600 "${initrdKeyPath path}"
+        '');
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config";
+          Type = "simple";
+          KillMode = "process";
+          Restart = "on-failure";
+        };
+      };
+    };
+
   };
 
 }
diff --git a/nixpkgs/nixos/modules/system/boot/kernel.nix b/nixpkgs/nixos/modules/system/boot/kernel.nix
index 33e9eca62b07..0298e28f3289 100644
--- a/nixpkgs/nixos/modules/system/boot/kernel.nix
+++ b/nixpkgs/nixos/modules/system/boot/kernel.nix
@@ -20,12 +20,15 @@ in
   ###### interface
 
   options = {
+    boot.kernel.enable = mkEnableOption (lib.mdDoc "the Linux kernel. This is useful for systemd-like containers which do not require a kernel") // {
+      default = true;
+    };
 
     boot.kernel.features = mkOption {
       default = {};
       example = literalExpression "{ debug = true; }";
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         This option allows to enable or disable certain kernel features.
         It's not API, because it's about kernel feature sets, that
         make sense for specific use cases. Mostly along with programs,
@@ -48,34 +51,77 @@ in
       # - some of it might not even evaluate correctly.
       defaultText = literalExpression "pkgs.linuxPackages";
       example = literalExpression "pkgs.linuxKernel.packages.linux_5_10";
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to override the Linux kernel used by
         NixOS.  Since things like external kernel module packages are
         tied to the kernel you're using, it also overrides those.
         This option is a function that takes Nixpkgs as an argument
         (as a convenience), and returns an attribute set containing at
-        the very least an attribute <varname>kernel</varname>.
+        the very least an attribute {var}`kernel`.
         Additional attributes may be needed depending on your
         configuration.  For instance, if you use the NVIDIA X driver,
         then it also needs to contain an attribute
-        <varname>nvidia_x11</varname>.
+        {var}`nvidia_x11`.
+
+        Please note that we strictly support kernel versions that are
+        maintained by the Linux developers only. More information on the
+        availability of kernel versions is documented
+        [in the Linux section of the manual](https://nixos.org/manual/nixos/unstable/index.html#sec-kernel-config).
       '';
     };
 
     boot.kernelPatches = mkOption {
       type = types.listOf types.attrs;
       default = [];
-      example = literalExpression "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
-      description = lib.mdDoc "A list of additional patches to apply to the kernel.";
+      example = literalExpression ''
+        [
+          {
+            name = "foo";
+            patch = ./foo.patch;
+            extraStructuredConfig.FOO = lib.kernel.yes;
+            features.foo = true;
+          }
+        ]
+      '';
+      description = lib.mdDoc ''
+        A list of additional patches to apply to the kernel.
+
+        Every item should be an attribute set with the following attributes:
+
+        ```nix
+        {
+          name = "foo";                 # descriptive name, required
+
+          patch = ./foo.patch;          # path or derivation that contains the patch source
+                                        # (required, but can be null if only config changes
+                                        # are needed)
+
+          extraStructuredConfig = {     # attrset of extra configuration parameters
+            FOO = lib.kernel.yes;       # (without the CONFIG_ prefix, optional)
+          };                            # values should generally be lib.kernel.yes,
+                                        # lib.kernel.no or lib.kernel.module
+
+          features = {                  # attrset of extra "features" the kernel is considered to have
+            foo = true;                 # (may be checked by other NixOS modules, optional)
+          };
+
+          extraConfig = "CONFIG_FOO y"; # extra configuration options in string form
+                                        # (deprecated, use extraStructuredConfig instead, optional)
+        }
+        ```
+
+        There's a small set of existing kernel patches in Nixpkgs, available as `pkgs.kernelPatches`,
+        that follow this format and can be used directly.
+      '';
     };
 
     boot.kernel.randstructSeed = mkOption {
       type = types.str;
       default = "";
       example = "my secret seed";
-      description = ''
-        Provides a custom seed for the <varname>RANDSTRUCT</varname> security
-        option of the Linux kernel. Note that <varname>RANDSTRUCT</varname> is
+      description = lib.mdDoc ''
+        Provides a custom seed for the {var}`RANDSTRUCT` security
+        option of the Linux kernel. Note that {var}`RANDSTRUCT` is
         only enabled in NixOS hardened kernels. Using a custom seed requires
         building the kernel and dependent packages locally, since this
         customization happens at build time.
@@ -173,7 +219,7 @@ in
       type = types.listOf types.path;
       internal = true;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Tree of kernel modules.  This includes the kernel, plus modules
         built outside of the kernel.  Combine these into a single tree of
         symlinks because modprobe only supports one directory.
@@ -193,7 +239,7 @@ in
       '';
       internal = true;
       type = types.listOf types.attrs;
-      description = ''
+      description = lib.mdDoc ''
         This option allows modules to specify the kernel config options that
         must be set (or unset) for the module to work. Please use the
         lib.kernelConfig functions to build list elements.
@@ -258,7 +304,7 @@ in
           ];
       })
 
-      (mkIf (!config.boot.isContainer) {
+      (mkIf config.boot.kernel.enable {
         system.build = { inherit kernel; };
 
         system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
diff --git a/nixpkgs/nixos/modules/system/boot/kernel_config.nix b/nixpkgs/nixos/modules/system/boot/kernel_config.nix
index 448835c3e625..31e9ec626ca6 100644
--- a/nixpkgs/nixos/modules/system/boot/kernel_config.nix
+++ b/nixpkgs/nixos/modules/system/boot/kernel_config.nix
@@ -14,7 +14,7 @@ let
         default = null;
         internal = true;
         visible = true;
-        description = ''
+        description = lib.mdDoc ''
           Use this field for tristate kernel options expecting a "y" or "m" or "n".
         '';
       };
@@ -91,7 +91,7 @@ in
         USB? y
         DEBUG n
       '';
-      description = ''
+      description = lib.mdDoc ''
         The result of converting the structured kernel configuration in settings
         to an intermediate string that can be parsed by generate-config.pl to
         answer the kernel `make defconfig`.
diff --git a/nixpkgs/nixos/modules/system/boot/loader/external/external.md b/nixpkgs/nixos/modules/system/boot/loader/external/external.md
new file mode 100644
index 000000000000..4f5b559dfc40
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/external/external.md
@@ -0,0 +1,26 @@
+# External Bootloader Backends {#sec-bootloader-external}
+
+NixOS has support for several bootloader backends by default: systemd-boot, grub, uboot, etc.
+The built-in bootloader backend support is generic and supports most use cases.
+Some users may prefer to create advanced workflows around managing the bootloader and bootable entries.
+
+You can replace the built-in bootloader support with your own tooling using the "external" bootloader option.
+
+Imagine you have created a new package called FooBoot.
+FooBoot provides a program at `${pkgs.fooboot}/bin/fooboot-install` which takes the system closure's path as its only argument and configures the system's bootloader.
+
+You can enable FooBoot like this:
+
+```nix
+{ pkgs, ... }: {
+  boot.loader.external = {
+    enable = true;
+    installHook = "${pkgs.fooboot}/bin/fooboot-install";
+  };
+}
+```
+
+## Developing Custom Bootloader Backends {#sec-bootloader-external-developing}
+
+Bootloaders should use [RFC-0125](https://github.com/NixOS/rfcs/pull/125)'s Bootspec format and synthesis tools to identify the key properties for bootable system generations.
+
diff --git a/nixpkgs/nixos/modules/system/boot/loader/external/external.nix b/nixpkgs/nixos/modules/system/boot/loader/external/external.nix
new file mode 100644
index 000000000000..926cbd2b4b3f
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/loader/external/external.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.loader.external;
+in
+{
+  meta = {
+    maintainers = with maintainers; [ cole-h grahamc raitobezarius ];
+    doc = ./external.md;
+  };
+
+  options.boot.loader.external = {
+    enable = mkEnableOption (lib.mdDoc "use an external tool to install your bootloader");
+
+    installHook = mkOption {
+      type = with types; path;
+      description = lib.mdDoc ''
+        The full path to a program of your choosing which performs the bootloader installation process.
+
+        The program will be called with an argument pointing to the output of the system's toplevel.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.loader = {
+      grub.enable = mkDefault false;
+      systemd-boot.enable = mkDefault false;
+      supportsInitrdSecrets = mkDefault false;
+    };
+
+    system.build.installBootLoader = cfg.installHook;
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
index 1ad7cd810948..9f80b40d116c 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 with lib;
 
@@ -12,13 +12,8 @@ let
     # Package set of targeted architecture
     if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
 
-  realGrub = if cfg.version == 1 then grubPkgs.grub
-    else if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
-    else if cfg.trustedBoot.enable
-         then if cfg.trustedBoot.isHPLaptop
-              then grubPkgs.trustedGrub-for-HP
-              else grubPkgs.trustedGrub
-         else grubPkgs.grub2;
+  realGrub = if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
+    else grubPkgs.grub2;
 
   grub =
     # Don't include GRUB if we're only generating a GRUB menu (e.g.,
@@ -28,17 +23,16 @@ let
     else realGrub;
 
   grubEfi =
-    # EFI version of Grub v2
-    if cfg.efiSupport && (cfg.version == 2)
+    if cfg.efiSupport
     then realGrub.override { efiSupport = cfg.efiSupport; }
     else null;
 
-  f = x: if x == null then "" else "" + x;
+  f = x: optionalString (x != null) ("" + x);
 
   grubConfig = args:
     let
       efiSysMountPoint = if args.efiSysMountPoint == null then args.path else args.efiSysMountPoint;
-      efiSysMountPoint' = replaceChars [ "/" ] [ "-" ] efiSysMountPoint;
+      efiSysMountPoint' = replaceStrings [ "/" ] [ "-" ] efiSysMountPoint;
     in
     pkgs.writeText "grub-config.xml" (builtins.toXML
     { splashImage = f cfg.splashImage;
@@ -52,24 +46,24 @@ let
       fullName = lib.getName realGrub;
       fullVersion = lib.getVersion realGrub;
       grubEfi = f grubEfi;
-      grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
+      grubTargetEfi = optionalString cfg.efiSupport (f (grubEfi.grubTarget or ""));
       bootPath = args.path;
       storePath = config.boot.loader.grub.storePath;
-      bootloaderId = if args.efiBootloaderId == null then "NixOS${efiSysMountPoint'}" else args.efiBootloaderId;
+      bootloaderId = if args.efiBootloaderId == null then "${config.system.nixos.distroName}${efiSysMountPoint'}" else args.efiBootloaderId;
       timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
-      users = if cfg.users == {} || cfg.version != 1 then cfg.users else throw "GRUB version 1 does not support user accounts.";
       theme = f cfg.theme;
       inherit efiSysMountPoint;
       inherit (args) devices;
       inherit (efi) canTouchEfiVariables;
       inherit (cfg)
-        version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
+        extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
         extraGrubInstallArgs
         extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
-        default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
+        default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios
+        users;
       path = with pkgs; makeBinPath (
         [ coreutils gnused gnugrep findutils diffutils btrfs-progs util-linux mdadm ]
-        ++ optional (cfg.efiSupport && (cfg.version == 2)) efibootmgr
+        ++ optional cfg.efiSupport efibootmgr
         ++ optionals cfg.useOSProber [ busybox os-prober ]);
       font = if cfg.font == null then ""
         else (if lib.last (lib.splitString "." cfg.font) == "pf2"
@@ -109,14 +103,8 @@ in
       };
 
       version = mkOption {
-        default = 2;
-        example = 1;
+        visible = false;
         type = types.int;
-        description = lib.mdDoc ''
-          The version of GRUB to use: `1` for GRUB
-          Legacy (versions 0.9x), or `2` (the
-          default) for GRUB 2.
-        '';
       };
 
       device = mkOption {
@@ -417,23 +405,23 @@ in
       splashImage = mkOption {
         type = types.nullOr types.path;
         example = literalExpression "./my-background.png";
-        description = ''
+        description = lib.mdDoc ''
           Background image used for GRUB.
-          Set to <literal>null</literal> to run GRUB in text mode.
+          Set to `null` to run GRUB in text mode.
 
-          <note><para>
+          ::: {.note}
           For grub 1:
           It must be a 640x480,
           14-colour image in XPM format, optionally compressed with
-          <command>gzip</command> or <command>bzip2</command>.
-          </para></note>
+          {command}`gzip` or {command}`bzip2`.
+          :::
 
-          <note><para>
+          ::: {.note}
           For grub 2:
           File must be one of .png, .tga, .jpg, or .jpeg. JPEG images must
           not be progressive.
           The image will be scaled if necessary to fit the screen.
-          </para></note>
+          :::
         '';
       };
 
@@ -441,36 +429,36 @@ in
         type = types.nullOr types.str;
         example = "#7EBAE4";
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Background color to be used for GRUB to fill the areas the image isn't filling.
 
-          <note><para>
+          ::: {.note}
           This options has no effect for GRUB 1.
-          </para></note>
+          :::
         '';
       };
 
       entryOptions = mkOption {
         default = "--class nixos --unrestricted";
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Options applied to the primary NixOS menu entry.
 
-          <note><para>
+          ::: {.note}
           This options has no effect for GRUB 1.
-          </para></note>
+          :::
         '';
       };
 
       subEntryOptions = mkOption {
         default = "--class nixos";
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Options applied to the secondary NixOS submenu entry.
 
-          <note><para>
+          ::: {.note}
           This options has no effect for GRUB 1.
-          </para></note>
+          :::
         '';
       };
 
@@ -478,24 +466,24 @@ in
         type = types.nullOr types.path;
         example = literalExpression "pkgs.nixos-grub2-theme";
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Grub theme to be used.
 
-          <note><para>
+          ::: {.note}
           This options has no effect for GRUB 1.
-          </para></note>
+          :::
         '';
       };
 
       splashMode = mkOption {
         type = types.enum [ "normal" "stretch" ];
         default = "stretch";
-        description = ''
+        description = lib.mdDoc ''
           Whether to stretch the image or show the image in the top-left corner unstretched.
 
-          <note><para>
+          ::: {.note}
           This options has no effect for GRUB 1.
-          </para></note>
+          :::
         '';
       };
 
@@ -622,37 +610,35 @@ in
       efiInstallAsRemovable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Whether to invoke <literal>grub-install</literal> with
-          <literal>--removable</literal>.
+        description = lib.mdDoc ''
+          Whether to invoke `grub-install` with
+          `--removable`.
 
           Unless you turn this on, GRUB will install itself somewhere in
-          <literal>boot.loader.efi.efiSysMountPoint</literal> (exactly where
+          `boot.loader.efi.efiSysMountPoint` (exactly where
           depends on other config variables). If you've set
-          <literal>boot.loader.efi.canTouchEfiVariables</literal> *AND* you
+          `boot.loader.efi.canTouchEfiVariables` *AND* you
           are currently booted in UEFI mode, then GRUB will use
-          <literal>efibootmgr</literal> to modify the boot order in the
+          `efibootmgr` to modify the boot order in the
           EFI variables of your firmware to include this location. If you are
           *not* booted in UEFI mode at the time GRUB is being installed, the
           NVRAM will not be modified, and your system will not find GRUB at
           boot time. However, GRUB will still return success so you may miss
-          the warning that gets printed ("<literal>efibootmgr: EFI variables
-          are not supported on this system.</literal>").
+          the warning that gets printed ("`efibootmgr: EFI variables
+          are not supported on this system.`").
 
           If you turn this feature on, GRUB will install itself in a
-          special location within <literal>efiSysMountPoint</literal> (namely
-          <literal>EFI/boot/boot$arch.efi</literal>) which the firmwares
+          special location within `efiSysMountPoint` (namely
+          `EFI/boot/boot$arch.efi`) which the firmwares
           are hardcoded to try first, regardless of NVRAM EFI variables.
 
           To summarize, turn this on if:
-          <itemizedlist>
-            <listitem><para>You are installing NixOS and want it to boot in UEFI mode,
-            but you are currently booted in legacy mode</para></listitem>
-            <listitem><para>You want to make a drive that will boot regardless of
-            the NVRAM state of the computer (like a USB "removable" drive)</para></listitem>
-            <listitem><para>You simply dislike the idea of depending on NVRAM
-            state to make your drive bootable</para></listitem>
-          </itemizedlist>
+          - You are installing NixOS and want it to boot in UEFI mode,
+            but you are currently booted in legacy mode
+          - You want to make a drive that will boot regardless of
+            the NVRAM state of the computer (like a USB "removable" drive)
+          - You simply dislike the idea of depending on NVRAM
+            state to make your drive bootable
         '';
       };
 
@@ -684,39 +670,6 @@ in
         '';
       };
 
-      trustedBoot = {
-
-        enable = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc ''
-            Enable trusted boot. GRUB will measure all critical components during
-            the boot process to offer TCG (TPM) support.
-          '';
-        };
-
-        systemHasTPM = mkOption {
-          default = "";
-          example = "YES_TPM_is_activated";
-          type = types.str;
-          description = lib.mdDoc ''
-            Assertion that the target system has an activated TPM. It is a safety
-            check before allowing the activation of 'trustedBoot.enable'. TrustedBoot
-            WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available.
-          '';
-        };
-
-        isHPLaptop = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc ''
-            Use a special version of TrustedGRUB that is needed by some HP laptops
-            and works only for the HP laptops.
-          '';
-        };
-
-      };
-
     };
 
   };
@@ -726,14 +679,7 @@ in
 
   config = mkMerge [
 
-    { boot.loader.grub.splashImage = mkDefault (
-        if cfg.version == 1 then pkgs.fetchurl {
-          url = "http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz";
-          sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59";
-        }
-        # GRUB 1.97 doesn't support gzipped XPMs.
-        else defaultSplash);
-    }
+    { boot.loader.grub.splashImage = mkDefault defaultSplash; }
 
     (mkIf (cfg.splashImage == defaultSplash) {
       boot.loader.grub.backgroundColor = mkDefault "#2F302F";
@@ -750,12 +696,18 @@ in
 
       boot.loader.supportsInitrdSecrets = true;
 
+      system.systemBuilderArgs.configurationName = cfg.configurationName;
+      system.systemBuilderCommands = ''
+        echo -n "$configurationName" > $out/configuration-name
+      '';
+
       system.build.installBootLoader =
         let
           install-grub-pl = pkgs.substituteAll {
             src = ./install-grub.pl;
             utillinux = pkgs.util-linux;
             btrfsprogs = pkgs.btrfs-progs;
+            inherit (config.system.nixos) distroName;
           };
           perl = pkgs.perl.withPackages (p: with p; [
             FileSlurp FileCopyRecursive
@@ -785,10 +737,6 @@ in
 
       assertions = [
         {
-          assertion = !cfg.zfsSupport || cfg.version == 2;
-          message = "Only GRUB version 2 provides ZFS support";
-        }
-        {
           assertion = cfg.mirroredBoots != [ ];
           message = "You must set the option ‘boot.loader.grub.devices’ or "
             + "'boot.loader.grub.mirroredBoots' to make the system bootable.";
@@ -798,22 +746,6 @@ in
           message = "You cannot have duplicated devices in mirroredBoots";
         }
         {
-          assertion = !cfg.trustedBoot.enable || cfg.version == 2;
-          message = "Trusted GRUB is only available for GRUB 2";
-        }
-        {
-          assertion = !cfg.efiSupport || !cfg.trustedBoot.enable;
-          message = "Trusted GRUB does not have EFI support";
-        }
-        {
-          assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable;
-          message = "Trusted GRUB does not have ZFS support";
-        }
-        {
-          assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated";
-          message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'.";
-        }
-        {
           assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport;
           message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport";
         }
@@ -821,6 +753,10 @@ in
           assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables;
           message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables";
         }
+        {
+          assertion = !(options.boot.loader.grub.version.isDefined && cfg.version == 1);
+          message = "Support for version 0.9x of GRUB was removed after being unsupported upstream for around a decade";
+        }
       ] ++ flip concatMap cfg.mirroredBoots (args: [
         {
           assertion = args.devices != [ ];
@@ -840,6 +776,11 @@ in
       }));
     })
 
+    (mkIf options.boot.loader.grub.version.isDefined {
+      warnings = [ ''
+        The boot.loader.grub.version option does not have any effect anymore, please remove it from your configuration.
+      '' ];
+    })
   ];
 
 
@@ -851,6 +792,10 @@ in
       (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ])
       (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ])
       (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ])
+      (mkRemovedOptionModule [ "boot" "loader" "grub" "trustedBoot" ] ''
+        Support for Trusted GRUB has been removed, because the project
+        has been retired upstream.
+      '')
       (mkRemovedOptionModule [ "boot" "loader" "grub" "extraInitrd" ] ''
         This option has been replaced with the bootloader agnostic
         boot.initrd.secrets option. To migrate to the initrd secrets system,
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 d5f019423b64..27f03f2fb58c 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -34,28 +34,33 @@ sub getList {
 }
 
 sub readFile {
-    my ($fn) = @_; local $/ = undef;
-    open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
-    local $/ = "\n"; chomp $s; return $s;
+    my ($fn) = @_;
+    # enable slurp mode: read entire file in one go
+    local $/ = undef;
+    open my $fh, "<$fn" or return undef;
+    my $s = <$fh>;
+    close $fh;
+    # disable slurp mode
+    local $/ = "\n";
+    chomp $s;
+    return $s;
 }
 
 sub writeFile {
     my ($fn, $s) = @_;
-    open FILE, ">$fn" or die "cannot create $fn: $!\n";
-    print FILE $s or die;
-    close FILE or die;
+    open my $fh, ">$fn" or die "cannot create $fn: $!\n";
+    print $fh $s or die "cannot write to $fn: $!\n";
+    close $fh or die "cannot close $fn: $!\n";
 }
 
 sub runCommand {
-    my ($cmd) = @_;
-    open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n";
-    my @ret = <FILE>;
-    close FILE;
+    open(my $fh, "-|", @_) or die "Failed to execute: $@_\n";
+    my @ret = $fh->getlines();
+    close $fh;
     return ($?, @ret);
 }
 
 my $grub = get("grub");
-my $grubVersion = int(get("version"));
 my $grubTarget = get("grubTarget");
 my $extraConfig = get("extraConfig");
 my $extraPrepareConfig = get("extraPrepareConfig");
@@ -90,9 +95,7 @@ my $theme = get("theme");
 my $saveDefault = $defaultEntry eq "saved";
 $ENV{'PATH'} = get("path");
 
-die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
-
-print STDERR "updating GRUB $grubVersion menu...\n";
+print STDERR "updating GRUB 2 menu...\n";
 
 mkpath("$bootPath/grub", 0, 0700);
 
@@ -170,76 +173,74 @@ sub GrubFs {
     }
     my $search = "";
 
-    if ($grubVersion > 1) {
-        # ZFS is completely separate logic as zpools are always identified by a label
-        # or custom UUID
-        if ($fs->type eq 'zfs') {
-            my $sid = index($fs->device, '/');
-
-            if ($sid < 0) {
-                $search = '--label ' . $fs->device;
-                $path = '/@' . $path;
-            } else {
-                $search = '--label ' . substr($fs->device, 0, $sid);
-                $path = '/' . substr($fs->device, $sid) . '/@' . $path;
+    # ZFS is completely separate logic as zpools are always identified by a label
+    # or custom UUID
+    if ($fs->type eq 'zfs') {
+        my $sid = index($fs->device, '/');
+
+        if ($sid < 0) {
+            $search = '--label ' . $fs->device;
+            $path = '/@' . $path;
+        } else {
+            $search = '--label ' . substr($fs->device, 0, $sid);
+            $path = '/' . substr($fs->device, $sid) . '/@' . $path;
+        }
+    } else {
+        my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
+
+        if ($fsIdentifier eq 'provided') {
+            # If the provided dev is identifying the partition using a label or uuid,
+            # we should get the label / uuid and do a proper search
+            my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
+            if ($#matches > 1) {
+                die "Too many matched devices"
+            } elsif ($#matches == 1) {
+                $search = "$types{$matches[0]} $matches[1]"
             }
         } else {
-            my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
-
-            if ($fsIdentifier eq 'provided') {
-                # If the provided dev is identifying the partition using a label or uuid,
-                # we should get the label / uuid and do a proper search
-                my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
-                if ($#matches > 1) {
-                    die "Too many matched devices"
-                } elsif ($#matches == 1) {
-                    $search = "$types{$matches[0]} $matches[1]"
-                }
-            } else {
-                # Determine the identifying type
-                $search = $types{$fsIdentifier} . ' ';
+            # Determine the identifying type
+            $search = $types{$fsIdentifier} . ' ';
 
-                # Based on the type pull in the identifier from the system
-                my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid -o export @{[$fs->device]}");
-                if ($status != 0) {
-                    die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
-                }
-                my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
-                if ($#matches != 0) {
-                    die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
-                }
-                $search .= $matches[0];
+            # Based on the type pull in the identifier from the system
+            my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid", "-o", "export", @{[$fs->device]});
+            if ($status != 0) {
+                die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
+            }
+            my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
+            if ($#matches != 0) {
+                die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
             }
+            $search .= $matches[0];
+        }
 
-            # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes
-            if ($fs->type eq 'btrfs') {
-                my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs subvol show @{[$fs->mount]}");
+        # BTRFS is a special case in that we need to fix the referenced path based on subvolumes
+        if ($fs->type eq 'btrfs') {
+            my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "show", @{[$fs->mount]});
+            if ($status != 0) {
+                die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
+            }
+            my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
+            if ($#ids > 0) {
+                die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
+            } elsif ($#ids == 0) {
+                my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "list", @{[$fs->mount]});
                 if ($status != 0) {
-                    die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
+                    die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
                 }
-                my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
-                if ($#ids > 0) {
-                    die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
-                } elsif ($#ids == 0) {
-                    my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs subvol list @{[$fs->mount]}");
-                    if ($status != 0) {
-                        die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
-                    }
-                    my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
-                    if ($#paths > 0) {
-                        die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
-                    } elsif ($#paths != 0) {
-                        die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
-                    }
-                    $path = "/$paths[0]$path";
+                my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
+                if ($#paths > 0) {
+                    die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
+                } elsif ($#paths != 0) {
+                    die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
                 }
+                $path = "/$paths[0]$path";
             }
         }
-        if (not $search eq "") {
-            $search = "search --set=drive$driveid " . $search;
-            $path = "(\$drive$driveid)$path";
-            $driveid += 1;
-        }
+    }
+    if (not $search eq "") {
+        $search = "search --set=drive$driveid " . $search;
+        $path = "(\$drive$driveid)$path";
+        $driveid += 1;
     }
     return Grub->new(path => $path, search => $search);
 }
@@ -252,166 +253,151 @@ if ($copyKernels == 0) {
 # Generate the header.
 my $conf .= "# Automatically generated.  DO NOT EDIT THIS FILE!\n";
 
-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
-    ";
-    if ($splashImage) {
-        copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
-        $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
+my @users = ();
+foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
+    my $name = $user->findvalue('@name') or die;
+    my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
+    my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
+    my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
+    my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
+
+    if ($hashedPasswordFile) {
+        open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
+        $hashedPassword = <$f>;
+        chomp $hashedPassword;
+    }
+    if ($passwordFile) {
+        open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
+        $password = <$f>;
+        chomp $password;
     }
-}
-
-else {
-    my @users = ();
-    foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
-        my $name = $user->findvalue('@name') or die;
-        my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
-        my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
-        my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
-        my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
-
-        if ($hashedPasswordFile) {
-            open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
-            $hashedPassword = <$f>;
-            chomp $hashedPassword;
-        }
-        if ($passwordFile) {
-            open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
-            $password = <$f>;
-            chomp $password;
-        }
 
-        if ($hashedPassword) {
-            if (index($hashedPassword, "grub.pbkdf2.") == 0) {
-                $conf .= "\npassword_pbkdf2 $name $hashedPassword";
-            }
-            else {
-                die "Password hash for GRUB user '$name' is not valid!";
-            }
-        }
-        elsif ($password) {
-            $conf .= "\npassword $name $password";
+    if ($hashedPassword) {
+        if (index($hashedPassword, "grub.pbkdf2.") == 0) {
+            $conf .= "\npassword_pbkdf2 $name $hashedPassword";
         }
         else {
-            die "GRUB user '$name' has no password!";
+            die "Password hash for GRUB user '$name' is not valid!";
         }
-        push(@users, $name);
     }
-    if (@users) {
-        $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
+    elsif ($password) {
+        $conf .= "\npassword $name $password";
     }
-
-    if ($copyKernels == 0) {
-        $conf .= "
-            " . $grubStore->search;
+    else {
+        die "GRUB user '$name' has no password!";
     }
-    # FIXME: should use grub-mkconfig.
-    my $defaultEntryText = $defaultEntry;
-    if ($saveDefault) {
-        $defaultEntryText = "\"\${saved_entry}\"";
-    }
-    $conf .= "
-        " . $grubBoot->search . "
-        if [ -s \$prefix/grubenv ]; then
-          load_env
-        fi
+    push(@users, $name);
+}
+if (@users) {
+    $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
+}
 
-        # ‘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
+if ($copyKernels == 0) {
+    $conf .= "
+        " . $grubStore->search;
+}
+# FIXME: should use grub-mkconfig.
+my $defaultEntryText = $defaultEntry;
+if ($saveDefault) {
+    $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
         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
+    # 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
     ";
-
-    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
-        ";
+}
+if ($splashImage) {
+    # Keeps the image's extension.
+    my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
+    # The module for jpg is jpeg.
+    if ($suffix eq ".jpg") {
+        $suffix = ".jpeg";
     }
-    if ($splashImage) {
-        # Keeps the image's extension.
-        my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
-        # The module for jpg is jpeg.
-        if ($suffix eq ".jpg") {
-            $suffix = ".jpeg";
-        }
-        if ($backgroundColor) {
-            $conf .= "
-            background_color '$backgroundColor'
-            ";
-        }
-        copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
+    if ($backgroundColor) {
         $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
+        background_color '$backgroundColor'
         ";
     }
+    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
+    ";
+}
 
-    rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
+rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
 
-    if ($theme) {
-        # Copy theme
-        rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
-        $conf .= "
-            # Sets theme.
-            set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
-            export theme
-            # Load theme fonts, if any
-        ";
+if ($theme) {
+    # Copy theme
+    rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
+    $conf .= "
+        # Sets theme.
+        set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
+        export theme
+        # Load theme fonts, if any
+    ";
 
-        find( { wanted => sub {
-            if ($_ =~ /\.pf2$/i) {
-                $font = File::Spec->abs2rel($File::Find::name, $theme);
-                $conf .= "
-                    loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
-                ";
-            }
-        }, no_chdir => 1 }, $theme );
-    }
+    find( { wanted => sub {
+        if ($_ =~ /\.pf2$/i) {
+            $font = File::Spec->abs2rel($File::Find::name, $theme);
+            $conf .= "
+                loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
+            ";
+        }
+    }, no_chdir => 1 }, $theme );
 }
 
 $conf .= "$extraConfig\n";
@@ -442,7 +428,7 @@ sub copyToKernelsDir {
 }
 
 sub addEntry {
-    my ($name, $path, $options) = @_;
+    my ($name, $path, $options, $current) = @_;
     return unless -e "$path/kernel" && -e "$path/initrd";
 
     my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
@@ -450,20 +436,28 @@ sub addEntry {
 
     # Include second initrd with secrets
     if (-e -x "$path/append-initrd-secrets") {
-        my $initrdName = basename($initrd);
-        my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets";
+        # Name the initrd secrets after the system from which they're derived.
+        my $systemName = basename(Cwd::abs_path("$path"));
+        my $initrdSecretsPath = "$bootPath/kernels/$systemName-secrets";
 
         mkpath(dirname($initrdSecretsPath), 0, 0755);
         my $oldUmask = umask;
         # Make sure initrd is not world readable (won't work if /boot is FAT)
         umask 0137;
         my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
-        system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
+        if (system("$path/append-initrd-secrets", $initrdSecretsPathTemp) != 0) {
+          if ($current) {
+              die "failed to create initrd secrets $!\n";
+          } else {
+              say STDERR "warning: failed to create initrd secrets for \"$name\", an older generation";
+              say STDERR "note: this is normal after having removed or renamed a file in `boot.initrd.secrets`";
+          }
+        }
         # Check whether any secrets were actually added
         if (-e $initrdSecretsPathTemp && ! -z _) {
             rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
             $copied{$initrdSecretsPath} = 1;
-            $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
+            $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$systemName-secrets";
         } else {
             unlink $initrdSecretsPathTemp;
             rmdir dirname($initrdSecretsPathTemp);
@@ -480,38 +474,26 @@ sub addEntry {
         readFile("$path/kernel-params");
     my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
 
-    if ($grubVersion == 1) {
-        $conf .= "title $name\n";
-        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
-        $conf .= "  kernel $xen $xenParams\n" if $xen;
-        $conf .= "  " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n";
-        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
-        if ($saveDefault) {
-            $conf .= "  savedefault\n";
-        }
-        $conf .= "\n";
-    } else {
-        $conf .= "menuentry \"$name\" " . ($options||"") . " {\n";
-        if ($saveDefault) {
-            $conf .= "  savedefault\n";
-        }
-        $conf .= $grubBoot->search . "\n";
-        if ($copyKernels == 0) {
-            $conf .= $grubStore->search . "\n";
-        }
-        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
-        $conf .= "  multiboot $xen $xenParams\n" if $xen;
-        $conf .= "  " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
-        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
-        $conf .= "}\n\n";
+    $conf .= "menuentry \"$name\" " . $options . " {\n";
+    if ($saveDefault) {
+        $conf .= "  savedefault\n";
     }
+    $conf .= $grubBoot->search . "\n";
+    if ($copyKernels == 0) {
+        $conf .= $grubStore->search . "\n";
+    }
+    $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
+    $conf .= "  multiboot $xen $xenParams\n" if $xen;
+    $conf .= "  " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
+    $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
+    $conf .= "}\n\n";
 }
 
 
 # Add default entries.
 $conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
 
-addEntry("NixOS - Default", $defaultConfig, $entryOptions);
+addEntry("@distroName@ - Default", $defaultConfig, $entryOptions, 1);
 
 $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
 
@@ -536,7 +518,7 @@ foreach my $link (@links) {
         my $linkname = basename($link);
         $entryName = "($linkname - $date - $version)";
     }
-    addEntry("NixOS - $entryName", $link);
+    addEntry("@distroName@ - $entryName", $link, "", 1);
 }
 
 my $grubBootPath = $grubBoot->path;
@@ -548,7 +530,7 @@ sub addProfile {
     my ($profile, $description) = @_;
 
     # Add entries for all generations of this profile.
-    $conf .= "submenu \"$description\" --class submenu {\n" if $grubVersion == 2;
+    $conf .= "submenu \"$description\" --class submenu {\n";
 
     sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
 
@@ -568,20 +550,18 @@ sub addProfile {
             -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, $subEntryOptions);
+        addEntry("@distroName@ - Configuration " . nrFromGen($link) . " ($date - $version)", $link, $subEntryOptions, 0);
     }
 
-    $conf .= "}\n" if $grubVersion == 2;
+    $conf .= "}\n";
 }
 
-addProfile "/nix/var/nix/profiles/system", "NixOS - All configurations";
+addProfile "/nix/var/nix/profiles/system", "@distroName@ - All configurations";
 
-if ($grubVersion == 2) {
-    for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
-        my $name = basename($profile);
-        next unless $name =~ /^\w+$/;
-        addProfile $profile, "NixOS - Profile '$name'";
-    }
+for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
+    my $name = basename($profile);
+    next unless $name =~ /^\w+$/;
+    addProfile $profile, "@distroName@ - Profile '$name'";
 }
 
 # extraPrepareConfig could refer to @bootPath@, which we have to substitute
@@ -593,22 +573,20 @@ if ($extraPrepareConfig ne "") {
 }
 
 # write the GRUB config.
-my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg";
+my $confFile = "$bootPath/grub/grub.cfg";
 my $tmpFile = $confFile . ".tmp";
 writeFile($tmpFile, $conf);
 
 
 # check whether to install GRUB EFI or not
 sub getEfiTarget {
-    if ($grubVersion == 1) {
-        return "no"
-    } elsif (($grub ne "") && ($grubEfi ne "")) {
+    if (($grub ne "") && ($grubEfi ne "")) {
         # EFI can only be installed when target is set;
         # A target is also required then for non-EFI grub
         if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
         else { return "both" }
     } elsif (($grub ne "") && ($grubEfi eq "")) {
-        # TODO: It would be safer to disallow non-EFI grub installation if no taget is given.
+        # TODO: It would be safer to disallow non-EFI grub installation if no target is given.
         #       If no target is given, then grub auto-detects the target which can lead to errors.
         #       E.g. it seems as if grub would auto-detect a EFI target based on the availability
         #       of a EFI partition.
@@ -727,7 +705,7 @@ symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!";
 if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
     foreach my $dev (@deviceTargets) {
         next if $dev eq "nodev";
-        print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
+        print STDERR "installing the GRUB 2 boot loader on $dev...\n";
         my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs);
         if ($forceInstall eq "true") {
             push @command, "--force";
@@ -742,7 +720,7 @@ if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
 
 # install EFI GRUB
 if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
-    print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
+    print STDERR "installing the GRUB 2 boot loader into $efiSysMountPoint...\n";
     my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs);
     if ($forceInstall eq "true") {
         push @command, "--force";
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
index adddcbee0164..d926b7ceaa6e 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/ipxe.nix
@@ -46,11 +46,7 @@ in
 
   config = mkIf (builtins.length scripts != 0) {
 
-    boot.loader.grub.extraEntries =
-      if config.boot.loader.grub.version == 2 then
-        toString (map grubEntry scripts)
-      else
-        throw "iPXE is not supported with GRUB 1.";
+    boot.loader.grub.extraEntries = toString (map grubEntry scripts);
 
     boot.loader.grub.extraFiles =
       { "ipxe.lkrn" = "${pkgs.ipxe}/ipxe.lkrn"; }
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
index 150068e0e953..ee969e9bff5b 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -31,45 +31,29 @@ in
         default = [];
         example = [ "console=ttyS0,115200" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Parameters added to the Memtest86+ command line. As of memtest86+ 5.01
           the following list of (apparently undocumented) parameters are
           accepted:
 
-          <itemizedlist>
-
-          <listitem>
-            <para><literal>console=...</literal>, set up a serial console.
+          - `console=...`, set up a serial console.
             Examples:
-            <literal>console=ttyS0</literal>,
-            <literal>console=ttyS0,9600</literal> or
-            <literal>console=ttyS0,115200n8</literal>.</para>
-          </listitem>
-
-          <listitem>
-            <para><literal>btrace</literal>, enable boot trace.</para>
-          </listitem>
+            `console=ttyS0`,
+            `console=ttyS0,9600` or
+            `console=ttyS0,115200n8`.
 
-          <listitem>
-            <para><literal>maxcpus=N</literal>, limit number of CPUs.</para>
-          </listitem>
+          - `btrace`, enable boot trace.
 
-          <listitem>
-            <para><literal>onepass</literal>, run one pass and exit if there
-            are no errors.</para>
-          </listitem>
+          - `maxcpus=N`, limit number of CPUs.
 
-          <listitem>
-            <para><literal>tstlist=...</literal>, list of tests to run.
-            Example: <literal>0,1,2</literal>.</para>
-          </listitem>
+          - `onepass`, run one pass and exit if there
+            are no errors.
 
-          <listitem>
-            <para><literal>cpumask=...</literal>, set a CPU mask, to select CPUs
-            to use for testing.</para>
-          </listitem>
+          - `tstlist=...`, list of tests to run.
+            Example: `0,1,2`.
 
-          </itemizedlist>
+          - `cpumask=...`, set a CPU mask, to select CPUs
+            to use for testing.
 
           This list of command line options was obtained by reading the
           Memtest86+ source code.
@@ -100,15 +84,11 @@ in
     })
 
     (mkIf (cfg.enable && !efiSupport) {
-      boot.loader.grub.extraEntries =
-        if config.boot.loader.grub.version == 2 then
-          ''
-            menuentry "Memtest86+" {
-              linux16 @bootRoot@/memtest.bin ${toString cfg.params}
-            }
-          ''
-        else
-          throw "Memtest86+ is not supported with GRUB 1.";
+      boot.loader.grub.extraEntries = ''
+        menuentry "Memtest86+" {
+          linux16 @bootRoot@/memtest.bin ${toString cfg.params}
+        }
+      '';
 
       boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
     })
diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
index bd3fc64999da..755ea259c425 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
+++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script-builder.sh
@@ -64,13 +64,13 @@ addEntry() {
 
 mkdir -p /boot /sbin
 
-addEntry "NixOS - Default" $defaultConfig ""
+addEntry "@distroName@ - Default" $defaultConfig ""
 
 # Add all generations of the system profile to the menu, in reverse
 # (most recent to least recent) order.
 for link in $((ls -d $defaultConfig/specialisation/* ) | sort -n); do
     date=$(stat --printf="%y\n" $link | sed 's/\..*//')
-    addEntry "NixOS - variation" $link ""
+    addEntry "@distroName@ - variation" $link ""
 done
 
 for generation in $(
@@ -85,7 +85,7 @@ for generation in $(
     else
       suffix="($date)"
     fi
-    addEntry "NixOS - Configuration $generation $suffix" $link "$generation ($date)"
+    addEntry "@distroName@ - Configuration $generation $suffix" $link "$generation ($date)"
 done
 
 mv $tmpOther $targetOther
diff --git a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
index 8287131d3213..4d33ed6b665b 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -8,6 +8,7 @@ let
     src = ./init-script-builder.sh;
     isExecutable = true;
     inherit (pkgs) bash;
+    inherit (config.system.nixos) distroName;
     path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
   };
 
diff --git a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 426aa021c8bf..1dde55074336 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -58,7 +58,7 @@ in
       version = mkOption {
         default = 2;
         type = types.enum [ 0 1 2 3 4 ];
-        description = "";
+        description = lib.mdDoc "";
       };
 
       uboot = {
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 77280a9680e3..a040518a5a57 100644..100755
--- 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
@@ -16,6 +16,7 @@ import datetime
 import glob
 import os.path
 from typing import NamedTuple, List, Optional
+from packaging import version
 
 class SystemIdentifier(NamedTuple):
     profile: Optional[str]
@@ -41,7 +42,7 @@ def system_dir(profile: Optional[str], generation: int, specialisation: Optional
     else:
         return d
 
-BOOT_ENTRY = """title NixOS{profile}{specialisation}
+BOOT_ENTRY = """title {title}
 version Generation {generation} {description}
 linux {kernel}
 initrd {initrd}
@@ -84,51 +85,64 @@ def copy_from_profile(profile: Optional[str], generation: int, specialisation: O
     return efi_file_path
 
 
-def describe_generation(generation_dir: str) -> str:
+def describe_generation(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
     try:
-        with open("%s/nixos-version" % generation_dir) as f:
+        with open(profile_path(profile, generation, specialisation, "nixos-version")) as f:
             nixos_version = f.read()
     except IOError:
         nixos_version = "Unknown"
 
-    kernel_dir = os.path.dirname(os.path.realpath("%s/kernel" % generation_dir))
+    kernel_dir = os.path.dirname(profile_path(profile, generation, specialisation, "kernel"))
     module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
     kernel_version = os.path.basename(module_dir)
 
-    build_time = int(os.path.getctime(generation_dir))
+    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
     build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
 
-    description = "NixOS {}, Linux Kernel {}, Built on {}".format(
+    description = "@distroName@ {}, Linux Kernel {}, Built on {}".format(
         nixos_version, kernel_version, build_date
     )
 
     return description
 
 
-def write_entry(profile: Optional[str], generation: int, specialisation: Optional[str], machine_id: str) -> None:
+def write_entry(profile: Optional[str], generation: int, specialisation: Optional[str],
+                machine_id: str, current: bool) -> None:
     kernel = copy_from_profile(profile, generation, specialisation, "kernel")
     initrd = copy_from_profile(profile, generation, specialisation, "initrd")
+
+    title = "@distroName@{profile}{specialisation}".format(
+        profile=" [" + profile + "]" if profile else "",
+        specialisation=" (%s)" % specialisation if specialisation else "")
+
     try:
         append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
         subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
     except FileNotFoundError:
         pass
+    except subprocess.CalledProcessError:
+        if current:
+            print("failed to create initrd secrets!", file=sys.stderr)
+            sys.exit(1)
+        else:
+            print("warning: failed to create initrd secrets "
+                  f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr)
+            print("note: this is normal after having removed "
+                  "or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
     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
+    kernel_params = "init=%s " % profile_path(profile, generation, specialisation, "init")
 
-    with open("%s/kernel-params" % (generation_dir)) as params_file:
+    with open(profile_path(profile, generation, specialisation, "kernel-params")) as params_file:
         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 "",
+        f.write(BOOT_ENTRY.format(title=title,
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
                     kernel_params=kernel_params,
-                    description=describe_generation(generation_dir)))
+                    description=describe_generation(profile, generation, specialisation)))
         if machine_id is not None:
             f.write("machine-id %s\n" % machine_id)
     os.rename(tmp_path, entry_file)
@@ -175,22 +189,22 @@ def get_specialisations(profile: Optional[str], generation: int, _: Optional[str
 
 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$")
+    rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
     known_paths = []
     for gen in gens:
         known_paths.append(copy_from_profile(*gen, "kernel", True))
         known_paths.append(copy_from_profile(*gen, "initrd", True))
     for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"):
+        if rex_profile.match(path):
+            prof = rex_profile.sub(r"\1", path)
+        else:
+            prof = None
         try:
-            if rex_profile.match(path):
-                prof = rex_profile.sub(r"\1", path)
-            else:
-                prof = "system"
             gen_number = int(rex_generation.sub(r"\1", path))
-            if not (prof, gen_number) in gens:
-                os.unlink(path)
         except ValueError:
-            pass
+            continue
+        if not (prof, gen_number, None) in gens:
+            os.unlink(path)
     for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
         if not path in known_paths and not os.path.isdir(path):
             os.unlink(path)
@@ -205,8 +219,8 @@ def get_profiles() -> List[str]:
         return []
 
 def main() -> None:
-    parser = argparse.ArgumentParser(description='Update NixOS-related systemd-boot files')
-    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot')
+    parser = argparse.ArgumentParser(description='Update @distroName@-related systemd-boot files')
+    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default @distroName@ config to boot')
     args = parser.parse_args()
 
     try:
@@ -227,24 +241,25 @@ def main() -> None:
         warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning)
         os.environ["NIXOS_INSTALL_BOOTLOADER"] = "1"
 
+    # flags to pass to bootctl install/update
+    bootctl_flags = []
+
+    if "@canTouchEfiVariables@" != "1":
+        bootctl_flags.append("--no-variables")
+
+    if "@graceful@" == "1":
+        bootctl_flags.append("--graceful")
+
     if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
         # bootctl uses fopen() with modes "wxe" and fails if the file exists.
         if os.path.exists("@efiSysMountPoint@/loader/loader.conf"):
             os.unlink("@efiSysMountPoint@/loader/loader.conf")
 
-        flags = []
-
-        if "@canTouchEfiVariables@" != "1":
-            flags.append("--no-variables")
-
-        if "@graceful@" == "1":
-            flags.append("--graceful")
-
-        subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@"] + flags + ["install"])
+        subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["install"])
     else:
         # Update bootloader to latest if needed
         available_out = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
-        installed_out = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True)
+        installed_out = subprocess.check_output(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@", "status"], universal_newlines=True)
 
         # See status_binaries() in systemd bootctl.c for code which generates this
         installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
@@ -258,12 +273,18 @@ def main() -> None:
         if available_match is None:
             raise Exception("could not determine systemd-boot version")
 
-        installed_version = installed_match.group(1)
-        available_version = available_match.group(1)
+        installed_version = version.parse(installed_match.group(1))
+        available_version = version.parse(available_match.group(1))
 
+        # systemd 252 has a regression that leaves some machines unbootable, so we skip that update.
+        # The fix is in 252.2
+        # See https://github.com/systemd/systemd/issues/25363 and https://github.com/NixOS/nixpkgs/pull/201558#issuecomment-1348603263
         if installed_version < available_version:
-            print("updating systemd-boot from %s to %s" % (installed_version, available_version))
-            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
+            if version.parse('252') <= available_version < version.parse('252.2'):
+                print("skipping systemd-boot update to %s because of known regression" % available_version)
+            else:
+                print("updating systemd-boot from %s to %s" % (installed_version, available_version))
+                subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["update"])
 
     mkdir_p("@efiSysMountPoint@/efi/nixos")
     mkdir_p("@efiSysMountPoint@/loader/entries")
@@ -274,14 +295,19 @@ def main() -> None:
     remove_old_entries(gens)
     for gen in gens:
         try:
-            write_entry(*gen, machine_id)
+            is_default = os.path.dirname(profile_path(*gen, "init")) == args.default_config
+            write_entry(*gen, machine_id, current=is_default)
             for specialisation in get_specialisations(*gen):
-                write_entry(*specialisation, machine_id)
-            if os.readlink(system_dir(*gen)) == args.default_config:
+                write_entry(*specialisation, machine_id, current=is_default)
+            if is_default:
                 write_loader_conf(*gen)
         except OSError as e:
-            profile = f"profile '{gen.profile}'" if gen.profile else "default profile"
-            print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr)
+            # See https://github.com/NixOS/nixpkgs/issues/114552
+            if e.errno == errno.EINVAL:
+                profile = f"profile '{gen.profile}'" if gen.profile else "default profile"
+                print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr)
+            else:
+                raise e
 
     for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False):
         relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/")
diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index baf0a9fe9c48..8a3e89e5888b 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -7,18 +7,20 @@ let
 
   efi = config.boot.loader.efi;
 
+  python3 = pkgs.python3.withPackages (ps: [ ps.packaging ]);
+
   systemdBootBuilder = pkgs.substituteAll {
     src = ./systemd-boot-builder.py;
 
     isExecutable = true;
 
-    inherit (pkgs) python3;
+    inherit python3;
 
     systemd = config.systemd.package;
 
     nix = config.nix.package.out;
 
-    timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else "";
+    timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout;
 
     editor = if cfg.editor then "True" else "False";
 
@@ -28,9 +30,11 @@ let
 
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
 
-    memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
+    inherit (config.system.nixos) distroName;
+
+    memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86-efi;
 
-    netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else "";
+    netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi;
 
     copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
       empty_file=$(${pkgs.coreutils}/bin/mktemp)
@@ -48,7 +52,7 @@ let
   };
 
   checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
-    nativeBuildInputs = [ pkgs.mypy ];
+    nativeBuildInputs = [ pkgs.mypy python3 ];
   } ''
     install -m755 ${systemdBootBuilder} $out
     mypy \
@@ -57,6 +61,12 @@ let
       --disallow-untyped-defs \
       $out
   '';
+
+  finalSystemdBootBuilder = pkgs.writeScript "install-systemd-boot.sh" ''
+    #!${pkgs.runtimeShell}
+    ${checkedSystemdBootBuilder} "$@"
+    ${cfg.extraInstallCommands}
+  '';
 in {
 
   imports =
@@ -99,34 +109,36 @@ in {
       '';
     };
 
+    extraInstallCommands = mkOption {
+      default = "";
+      example = ''
+        default_cfg=$(cat /boot/loader/loader.conf | grep default | awk '{print $2}')
+        init_value=$(cat /boot/loader/entries/$default_cfg | grep init= | awk '{print $2}')
+        sed -i "s|@INIT@|$init_value|g" /boot/custom/config_with_placeholder.conf
+      '';
+      type = types.lines;
+      description = lib.mdDoc ''
+        Additional shell commands inserted in the bootloader installer
+        script after generating menu entries. It can be used to expand
+        on extra boot entries that cannot incorporate certain pieces of
+        information (such as the resulting `init=` kernel parameter).
+      '';
+    };
+
     consoleMode = mkOption {
       default = "keep";
 
       type = types.enum [ "0" "1" "2" "auto" "max" "keep" ];
 
-      description = ''
+      description = lib.mdDoc ''
         The resolution of the console. The following values are valid:
 
-        <itemizedlist>
-          <listitem><para>
-            <literal>"0"</literal>: Standard UEFI 80x25 mode
-          </para></listitem>
-          <listitem><para>
-            <literal>"1"</literal>: 80x50 mode, not supported by all devices
-          </para></listitem>
-          <listitem><para>
-            <literal>"2"</literal>: The first non-standard mode provided by the device firmware, if any
-          </para></listitem>
-          <listitem><para>
-            <literal>"auto"</literal>: Pick a suitable mode automatically using heuristics
-          </para></listitem>
-          <listitem><para>
-            <literal>"max"</literal>: Pick the highest-numbered available mode
-          </para></listitem>
-          <listitem><para>
-            <literal>"keep"</literal>: Keep the mode selected by firmware (the default)
-          </para></listitem>
-        </itemizedlist>
+        - `"0"`: Standard UEFI 80x25 mode
+        - `"1"`: 80x50 mode, not supported by all devices
+        - `"2"`: The first non-standard mode provided by the device firmware, if any
+        - `"auto"`: Pick a suitable mode automatically using heuristics
+        - `"max"`: Pick the highest-numbered available mode
+        - `"keep"`: Keep the mode selected by firmware (the default)
       '';
     };
 
@@ -291,7 +303,7 @@ in {
     ];
 
     system = {
-      build.installBootLoader = checkedSystemdBootBuilder;
+      build.installBootLoader = finalSystemdBootBuilder;
 
       boot.loader.id = "systemd-boot";
 
diff --git a/nixpkgs/nixos/modules/system/boot/luksroot.nix b/nixpkgs/nixos/modules/system/boot/luksroot.nix
index 78301a57bd97..71036044a2dc 100644
--- a/nixpkgs/nixos/modules/system/boot/luksroot.nix
+++ b/nixpkgs/nixos/modules/system/boot/luksroot.nix
@@ -130,7 +130,7 @@ let
     ''}
 
     # Disable all input echo for the whole stage. We could use read -s
-    # instead but that would ocasionally leak characters between read
+    # instead but that would occasionally leak characters between read
     # invocations.
     stty -echo
   '';
@@ -148,6 +148,7 @@ let
            + optionalString dev.bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue"
            + optionalString (dev.header != null) " --header=${dev.header}";
     cschange = "cryptsetup luksChangeKey ${dev.device} ${optionalString (dev.header != null) "--header=${dev.header}"}";
+    fido2luksCredentials = dev.fido2.credentials ++ optional (dev.fido2.credential != null) dev.fido2.credential;
   in ''
     # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
     # if on a USB drive.
@@ -157,6 +158,20 @@ let
       wait_target "header" ${dev.header} || die "${dev.header} is unavailable"
     ''}
 
+    try_empty_passphrase() {
+        ${if dev.tryEmptyPassphrase then ''
+             echo "Trying empty passphrase!"
+             echo "" | ${csopen}
+             cs_status=$?
+             if [ $cs_status -eq 0 ]; then
+                 return 0
+             else
+                 return 1
+             fi
+        '' else "return 1"}
+    }
+
+
     do_open_passphrase() {
         local passphrase
 
@@ -211,13 +226,27 @@ let
             ${csopen} --key-file=${dev.keyFile} \
               ${optionalString (dev.keyFileSize != null) "--keyfile-size=${toString dev.keyFileSize}"} \
               ${optionalString (dev.keyFileOffset != null) "--keyfile-offset=${toString dev.keyFileOffset}"}
+            cs_status=$?
+            if [ $cs_status -ne 0 ]; then
+              echo "Key File ${dev.keyFile} failed!"
+              if ! try_empty_passphrase; then
+                ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
+                echo " - failing back to interactive password prompt"
+                do_open_passphrase
+              fi
+            fi
         else
-            ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
-            echo " - failing back to interactive password prompt"
-            do_open_passphrase
+            # If the key file never shows up we should also try the empty passphrase
+            if ! try_empty_passphrase; then
+               ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
+               echo " - failing back to interactive password prompt"
+               do_open_passphrase
+            fi
         fi
         '' else ''
-        do_open_passphrase
+           if ! try_empty_passphrase; then
+              do_open_passphrase
+           fi
         ''}
     }
 
@@ -417,7 +446,7 @@ let
     }
     ''}
 
-    ${optionalString (luks.fido2Support && (dev.fido2.credential != null)) ''
+    ${optionalString (luks.fido2Support && fido2luksCredentials != []) ''
 
     open_with_hardware() {
       local passsphrase
@@ -433,7 +462,7 @@ let
           echo "Please move your mouse to create needed randomness."
         ''}
           echo "Waiting for your FIDO2 device..."
-          fido2luks open${optionalString dev.allowDiscards " --allow-discards"} ${dev.device} ${dev.name} ${dev.fido2.credential} --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase
+          fido2luks open${optionalString dev.allowDiscards " --allow-discards"} ${dev.device} ${dev.name} "${builtins.concatStringsSep "," fido2luksCredentials}" --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase
         if [ $? -ne 0 ]; then
           echo "No FIDO2 key found, falling back to normal open procedure"
           open_normally
@@ -444,7 +473,7 @@ let
     # commands to run right before we mount our device
     ${dev.preOpenCommands}
 
-    ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && (dev.fido2.credential != null)) then ''
+    ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && fido2luksCredentials != []) then ''
     open_with_hardware
     '' else ''
     open_normally
@@ -475,13 +504,16 @@ let
   preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
   postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
 
+
   stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let
     opts = v.crypttabExtraOpts
       ++ optional v.allowDiscards "discard"
       ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
       ++ optional (v.header != null) "header=${v.header}"
-      ++ optional (v.keyFileOffset != null) "keyfile-offset=${v.keyFileOffset}"
-      ++ optional (v.keyFileSize != null) "keyfile-size=${v.keyFileSize}"
+      ++ optional (v.keyFileOffset != null) "keyfile-offset=${toString v.keyFileOffset}"
+      ++ optional (v.keyFileSize != null) "keyfile-size=${toString v.keyFileSize}"
+      ++ optional (v.keyFileTimeout != null) "keyfile-timeout=${builtins.toString v.keyFileTimeout}s"
+      ++ optional (v.tryEmptyPassphrase) "try-empty-password=true"
     ;
   in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
 
@@ -523,7 +555,7 @@ in
       type = types.bool;
       default = false;
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to configure luks support in the initrd, when no luks
         devices are configured.
       '';
@@ -532,15 +564,15 @@ in
     boot.initrd.luks.reusePassphrases = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         When opening a new LUKS device try reusing last successful
         passphrase.
 
         Useful for mounting a number of devices that use the same
         passphrase without retyping it several times.
 
-        Such setup can be useful if you use <command>cryptsetup
-        luksSuspend</command>. Different LUKS devices will still have
+        Such setup can be useful if you use {command}`cryptsetup luksSuspend`.
+        Different LUKS devices will still have
         different master keys even when using the same passphrase.
       '';
     };
@@ -563,7 +595,7 @@ in
             default = name;
             example = "luksroot";
             type = types.str;
-            description = "Name of the unencrypted device in <filename>/dev/mapper</filename>.";
+            description = lib.mdDoc "Name of the unencrypted device in {file}`/dev/mapper`.";
           };
 
           device = mkOption {
@@ -593,6 +625,25 @@ in
             '';
           };
 
+          tryEmptyPassphrase = mkOption {
+            default = false;
+            type = types.bool;
+            description = lib.mdDoc ''
+              If keyFile fails then try an empty passphrase first before
+              prompting for password.
+            '';
+          };
+
+          keyFileTimeout = mkOption {
+            default = null;
+            example = 5;
+            type = types.nullOr types.int;
+            description = lib.mdDoc ''
+              The amount of time in seconds for a keyFile to appear before
+              timing out and trying passwords.
+            '';
+          };
+
           keyFileSize = mkOption {
             default = null;
             example = 4096;
@@ -695,6 +746,17 @@ in
               description = lib.mdDoc "The FIDO2 credential ID.";
             };
 
+            credentials = mkOption {
+              default = [];
+              example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ];
+              type = types.listOf types.str;
+              description = lib.mdDoc ''
+                List of FIDO2 credential IDs.
+
+                Use this if you have multiple FIDO2 keys you want to use for the same luks device.
+              '';
+            };
+
             gracePeriod = mkOption {
               default = 10;
               type = types.int;
@@ -799,7 +861,7 @@ in
             '';
             description = lib.mdDoc ''
               Commands that should be run right before we try to mount our LUKS device.
-              This can be useful, if the keys needed to open the drive is on another partion.
+              This can be useful, if the keys needed to open the drive is on another partition.
             '';
           };
 
@@ -819,7 +881,7 @@ in
             default = [];
             example = [ "_netdev" ];
             visible = false;
-            description = ''
+            description = lib.mdDoc ''
               Only used with systemd stage 1.
 
               Extra options to append to the last column of the generated crypttab file.
@@ -877,6 +939,10 @@ in
           message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
         }
 
+        { assertion = !config.boot.initrd.systemd.enable -> all (x: x.keyFileTimeout == null) (attrValues luks.devices);
+          message = "boot.initrd.luks.devices.<name>.keyFileTimeout is only supported for systemd initrd";
+        }
+
         { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices);
           message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1.";
         }
@@ -893,9 +959,11 @@ in
         { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport;
           message = "systemd stage 1 does not support GPG smartcards yet.";
         }
-        # TODO
         { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support;
-          message = "systemd stage 1 does not support FIDO2 yet.";
+          message = ''
+            systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.devices.<name>.fido2`.
+            Use systemd-cryptenroll(1) to configure FIDO2 support.
+          '';
         }
         # TODO
         { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport;
@@ -915,7 +983,14 @@ in
       ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
 
     # copy the cryptsetup binary and it's dependencies
-    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+    boot.initrd.extraUtilsCommands = let
+      pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } ''
+        mkdir -p "$out/bin"
+        cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512"
+        strip -s "$out/bin/pbkdf2-sha512"
+      '';
+    in
+    mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
       copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
       sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
@@ -925,9 +1000,7 @@ in
         copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
         copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
 
-        cc -O3 -I${pkgs.openssl.dev}/include -L${lib.getLib pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
-        strip -s pbkdf2-sha512
-        copy_bin_and_libs pbkdf2-sha512
+        copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512
 
         mkdir -p $out/etc/ssl
         cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
@@ -951,13 +1024,12 @@ in
         copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
 
         ${concatMapStringsSep "\n" (x:
-          if x.gpgCard != null then
+          optionalString (x.gpgCard != null)
             ''
               mkdir -p $out/secrets/gpg-keys/${x.device}
               cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
               cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
             ''
-          else ""
           ) (attrValues luks.devices)
         }
       ''}
diff --git a/nixpkgs/nixos/modules/system/boot/modprobe.nix b/nixpkgs/nixos/modules/system/boot/modprobe.nix
index c8ab3b0d8e4a..d751c4462d3f 100644
--- a/nixpkgs/nixos/modules/system/boot/modprobe.nix
+++ b/nixpkgs/nixos/modules/system/boot/modprobe.nix
@@ -7,6 +7,9 @@ with lib;
   ###### interface
 
   options = {
+    boot.modprobeConfig.enable = mkEnableOption (lib.mdDoc "modprobe config. This is useful for systems like containers which do not require a kernel") // {
+      default = true;
+    };
 
     boot.blacklistedKernelModules = mkOption {
       type = types.listOf types.str;
@@ -38,7 +41,7 @@ with lib;
 
   ###### implementation
 
-  config = mkIf (!config.boot.isContainer) {
+  config = mkIf config.boot.modprobeConfig.enable {
 
     environment.etc."modprobe.d/ubuntu.conf".source = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
 
diff --git a/nixpkgs/nixos/modules/system/boot/networkd.nix b/nixpkgs/nixos/modules/system/boot/networkd.nix
index 337d238f910d..d88f88f9fdaf 100644
--- a/nixpkgs/nixos/modules/system/boot/networkd.nix
+++ b/nixpkgs/nixos/modules/system/boot/networkd.nix
@@ -6,8 +6,6 @@ with lib;
 
 let
 
-  cfg = config.systemd.network;
-
   check = {
 
     global = {
@@ -27,9 +25,11 @@ let
 
       sectionDHCPv4 = checkUnitConfig "DHCPv4" [
         (assertOnlyFields [
+          "ClientIdentifier"
           "DUIDType"
           "DUIDRawData"
         ])
+        (assertValueOneOf "ClientIdentifier" ["mac" "duid" "duid-only"])
       ];
 
       sectionDHCPv6 = checkUnitConfig "DHCPv6" [
@@ -72,6 +72,9 @@ let
           "CombinedChannels"
           "RxBufferSize"
           "TxBufferSize"
+          "ReceiveQueues"
+          "TransmitQueues"
+          "TransmitQueueLength"
         ])
         (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"])
         (assertMacAddress "MACAddress")
@@ -98,6 +101,9 @@ let
         (assertRange "CombinedChannels" 1 4294967295)
         (assertInt "RxBufferSize")
         (assertInt "TxBufferSize")
+        (assertRange "ReceiveQueues" 1 4096)
+        (assertRange "TransmitQueues" 1 4096)
+        (assertRange "TransmitQueueLength" 1 4294967294)
       ];
     };
 
@@ -303,6 +309,48 @@ let
 
       sectionTap = checkUnitConfig "Tap" tunChecks;
 
+      sectionL2TP = checkUnitConfig "L2TP" [
+        (assertOnlyFields [
+          "TunnelId"
+          "PeerTunnelId"
+          "Remote"
+          "Local"
+          "EncapsulationType"
+          "UDPSourcePort"
+          "UDPDestinationPort"
+          "UDPChecksum"
+          "UDP6ZeroChecksumTx"
+          "UDP6ZeroChecksumRx"
+        ])
+        (assertInt "TunnelId")
+        (assertRange "TunnelId" 1 4294967295)
+        (assertInt "PeerTunnelId")
+        (assertRange "PeerTunnelId" 1 4294967295)
+        (assertValueOneOf "EncapsulationType" [ "ip" "udp" ])
+        (assertPort "UDPSourcePort")
+        (assertPort "UDPDestinationPort")
+        (assertValueOneOf "UDPChecksum" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumTx" boolValues)
+        (assertValueOneOf "UDP6ZeroChecksumRx" boolValues)
+      ];
+
+      sectionL2TPSession = checkUnitConfig "L2TPSession" [
+        (assertOnlyFields [
+          "Name"
+          "SessionId"
+          "PeerSessionId"
+          "Layer2SpecificHeader"
+        ])
+        (assertHasField "Name")
+        (assertHasField "SessionId")
+        (assertInt "SessionId")
+        (assertRange "SessionId" 1 4294967295)
+        (assertHasField "PeerSessionId")
+        (assertInt "PeerSessionId")
+        (assertRange "PeerSessionId" 1 4294967295)
+        (assertValueOneOf "Layer2SpecificHeader" [ "none" "default" ])
+      ];
+
       # NOTE The PrivateKey directive is missing on purpose here, please
       # do not add it to this list. The nix store is world-readable let's
       # refrain ourselves from providing a footgun.
@@ -451,6 +499,7 @@ let
           "Multicast"
           "AllMulticast"
           "Unmanaged"
+          "Group"
           "RequiredForOnline"
           "RequiredFamilyForOnline"
           "ActivationPolicy"
@@ -463,6 +512,8 @@ let
         (assertValueOneOf "AllMulticast" boolValues)
         (assertValueOneOf "Promiscuous" boolValues)
         (assertValueOneOf "Unmanaged" boolValues)
+        (assertInt "Group")
+        (assertRange "Group" 0 2147483647)
         (assertValueOneOf "RequiredForOnline" (boolValues ++ [
           "missing"
           "off"
@@ -498,7 +549,6 @@ let
           "LinkLocalAddressing"
           "IPv4LLRoute"
           "DefaultRouteOnDevice"
-          "IPv6Token"
           "LLMNR"
           "MulticastDNS"
           "DNSOverTLS"
@@ -523,7 +573,7 @@ let
           "IPv6ProxyNDP"
           "IPv6ProxyNDPAddress"
           "IPv6SendRA"
-          "DHCPv6PrefixDelegation"
+          "DHCPPrefixDelegation"
           "IPv6MTUBytes"
           "Bridge"
           "Bond"
@@ -566,12 +616,11 @@ let
         (assertValueOneOf "IPv4ProxyARP" boolValues)
         (assertValueOneOf "IPv6ProxyNDP" boolValues)
         (assertValueOneOf "IPv6SendRA" boolValues)
-        (assertValueOneOf "DHCPv6PrefixDelegation" boolValues)
+        (assertValueOneOf "DHCPPrefixDelegation" boolValues)
         (assertByteFormat "IPv6MTUBytes")
         (assertValueOneOf "ActiveSlave" boolValues)
         (assertValueOneOf "PrimarySlave" boolValues)
         (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
-        (assertValueOneOf "IgnoreCarrierLoss" boolValues)
         (assertValueOneOf "KeepConfiguration" (boolValues ++ ["static" "dhcp-on-stop" "dhcp"]))
       ];
 
@@ -583,6 +632,7 @@ let
           "Label"
           "PreferredLifetime"
           "Scope"
+          "RouteMetric"
           "HomeAddress"
           "DuplicateAddressDetection"
           "ManageTemporaryAddress"
@@ -591,6 +641,7 @@ let
         ])
         (assertHasField "Address")
         (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0])
+        (assertInt "RouteMetric")
         (assertValueOneOf "HomeAddress" boolValues)
         (assertValueOneOf "DuplicateAddressDetection" ["ipv4" "ipv6" "both" "none"])
         (assertValueOneOf "ManageTemporaryAddress" boolValues)
@@ -616,6 +667,7 @@ let
           "User"
           "SuppressPrefixLength"
           "Type"
+          "SuppressInterfaceGroup"
         ])
         (assertInt "TypeOfService")
         (assertRange "TypeOfService" 0 255)
@@ -629,6 +681,7 @@ let
         (assertInt "SuppressPrefixLength")
         (assertRange "SuppressPrefixLength" 0 128)
         (assertValueOneOf "Type" ["blackhole" "unreachable" "prohibit"])
+        (assertRange "SuppressInterfaceGroup" 0 2147483647)
       ];
 
       sectionRoute = checkUnitConfig "Route" [
@@ -708,6 +761,9 @@ let
           "BlackList"
           "RequestOptions"
           "SendOption"
+          "FallbackLeaseLifetimeSec"
+          "Label"
+          "Use6RD"
         ])
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "RoutesToDNS" boolValues)
@@ -730,6 +786,8 @@ let
         (assertPort "ListenPort")
         (assertValueOneOf "SendRelease" boolValues)
         (assertValueOneOf "SendDecline" boolValues)
+        (assertValueOneOf "FallbackLeaseLifetimeSec" ["forever" "infinity"])
+        (assertValueOneOf "Use6RD" boolValues)
       ];
 
       sectionDHCPv6 = checkUnitConfig "DHCPv6" [
@@ -742,7 +800,6 @@ let
           "MUDURL"
           "RequestOptions"
           "SendVendorOption"
-          "ForceDHCPv6PDOtherInformation"
           "PrefixDelegationHint"
           "WithoutRA"
           "SendOption"
@@ -751,27 +808,33 @@ let
           "DUIDType"
           "DUIDRawData"
           "IAID"
+          "UseDelegatedPrefix"
         ])
         (assertValueOneOf "UseAddress" boolValues)
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "UseNTP" boolValues)
         (assertInt "RouteMetric")
         (assertValueOneOf "RapidCommit" boolValues)
-        (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
-        (assertValueOneOf "WithoutRA" ["solicit" "information-request"])
+        (assertValueOneOf "WithoutRA" ["no" "solicit" "information-request"])
         (assertRange "SendOption" 1 65536)
         (assertInt "IAID")
+        (assertValueOneOf "UseDelegatedPrefix" boolValues)
       ];
 
-      sectionDHCPv6PrefixDelegation = checkUnitConfig "DHCPv6PrefixDelegation" [
+      sectionDHCPPrefixDelegation = checkUnitConfig "DHCPPrefixDelegation" [
         (assertOnlyFields [
+          "UplinkInterface"
           "SubnetId"
           "Announce"
           "Assign"
           "Token"
+          "ManageTemporaryAddress"
+          "RouteMetric"
         ])
         (assertValueOneOf "Announce" boolValues)
         (assertValueOneOf "Assign" boolValues)
+        (assertValueOneOf "ManageTemporaryAddress" boolValues)
+        (assertRange "RouteMetric" 0 4294967295)
       ];
 
       sectionIPv6AcceptRA = checkUnitConfig "IPv6AcceptRA" [
@@ -789,6 +852,10 @@ let
           "RouteAllowList"
           "DHCPv6Client"
           "RouteMetric"
+          "UseMTU"
+          "UseGateway"
+          "UseRoutePrefix"
+          "Token"
         ])
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
@@ -796,14 +863,19 @@ let
         (assertValueOneOf "UseAutonomousPrefix" boolValues)
         (assertValueOneOf "UseOnLinkPrefix" boolValues)
         (assertValueOneOf "DHCPv6Client" (boolValues ++ ["always"]))
+        (assertValueOneOf "UseMTU" boolValues)
+        (assertValueOneOf "UseGateway" boolValues)
+        (assertValueOneOf "UseRoutePrefix" boolValues)
       ];
 
       sectionDHCPServer = checkUnitConfig "DHCPServer" [
         (assertOnlyFields [
+          "ServerAddress"
           "PoolOffset"
           "PoolSize"
           "DefaultLeaseTimeSec"
           "MaxLeaseTimeSec"
+          "UplinkInterface"
           "EmitDNS"
           "DNS"
           "EmitNTP"
@@ -817,10 +889,15 @@ let
           "EmitLPR"
           "LPR"
           "EmitRouter"
+          "Router"
           "EmitTimezone"
           "Timezone"
           "SendOption"
           "SendVendorOption"
+          "BindToInterface"
+          "RelayTarget"
+          "RelayAgentCircuitId"
+          "RelayAgentRemoteId"
         ])
         (assertInt "PoolOffset")
         (assertMinimum "PoolOffset" 0)
@@ -834,6 +911,7 @@ let
         (assertValueOneOf "EmitLPR" boolValues)
         (assertValueOneOf "EmitRouter" boolValues)
         (assertValueOneOf "EmitTimezone" boolValues)
+        (assertValueOneOf "BindToInterface" boolValues)
       ];
 
       sectionIPv6SendRA = checkUnitConfig "IPv6SendRA" [
@@ -842,6 +920,7 @@ let
           "OtherInformation"
           "RouterLifetimeSec"
           "RouterPreference"
+          "UplinkInterface"
           "EmitDNS"
           "DNS"
           "EmitDomains"
@@ -862,11 +941,21 @@ let
           "Prefix"
           "PreferredLifetimeSec"
           "ValidLifetimeSec"
+          "Token"
         ])
         (assertValueOneOf "AddressAutoconfiguration" boolValues)
         (assertValueOneOf "OnLink" boolValues)
       ];
 
+      sectionIPv6RoutePrefix = checkUnitConfig "IPv6RoutePrefix" [
+        (assertOnlyFields [
+          "Route"
+          "LifetimeSec"
+        ])
+        (assertHasField "Route")
+        (assertInt "LifetimeSec")
+      ];
+
       sectionDHCPServerStaticLease = checkUnitConfig "DHCPServerStaticLease" [
         (assertOnlyFields [
           "MACAddress"
@@ -877,6 +966,470 @@ let
         (assertMacAddress "MACAddress")
       ];
 
+      sectionBridge = checkUnitConfig "Bridge" [
+        (assertOnlyFields [
+          "UnicastFlood"
+          "MulticastFlood"
+          "MulticastToUnicast"
+          "NeighborSuppression"
+          "Learning"
+          "Hairpin"
+          "Isolated"
+          "UseBPDU"
+          "FastLeave"
+          "AllowPortToBeRoot"
+          "ProxyARP"
+          "ProxyARPWiFi"
+          "MulticastRouter"
+          "Cost"
+          "Priority"
+        ])
+        (assertValueOneOf "UnicastFlood" boolValues)
+        (assertValueOneOf "MulticastFlood" boolValues)
+        (assertValueOneOf "MulticastToUnicast" boolValues)
+        (assertValueOneOf "NeighborSuppression" boolValues)
+        (assertValueOneOf "Learning" boolValues)
+        (assertValueOneOf "Hairpin" boolValues)
+        (assertValueOneOf "Isolated" boolValues)
+        (assertValueOneOf "UseBPDU" boolValues)
+        (assertValueOneOf "FastLeave" boolValues)
+        (assertValueOneOf "AllowPortToBeRoot" boolValues)
+        (assertValueOneOf "ProxyARP" boolValues)
+        (assertValueOneOf "ProxyARPWiFi" boolValues)
+        (assertValueOneOf "MulticastRouter" [ "no" "query" "permanent" "temporary" ])
+        (assertInt "Cost")
+        (assertRange "Cost" 1 65535)
+        (assertInt "Priority")
+        (assertRange "Priority" 0 63)
+      ];
+
+      sectionBridgeFDB = checkUnitConfig "BridgeFDB" [
+        (assertOnlyFields [
+          "MACAddress"
+          "Destination"
+          "VLANId"
+          "VNI"
+          "AssociatedWith"
+          "OutgoingInterface"
+        ])
+        (assertHasField "MACAddress")
+        (assertInt "VLANId")
+        (assertRange "VLANId" 0 4094)
+        (assertInt "VNI")
+        (assertRange "VNI" 1 16777215)
+        (assertValueOneOf "AssociatedWith" [ "use" "self" "master" "router" ])
+      ];
+
+      sectionBridgeMDB = checkUnitConfig "BridgeMDB" [
+        (assertOnlyFields [
+          "MulticastGroupAddress"
+          "VLANId"
+        ])
+        (assertHasField "MulticastGroupAddress")
+        (assertInt "VLANId")
+        (assertRange "VLANId" 0 4094)
+      ];
+
+      sectionLLDP = checkUnitConfig "LLDP" [
+        (assertOnlyFields [
+          "MUDURL"
+        ])
+      ];
+
+      sectionCAN = checkUnitConfig "CAN" [
+        (assertOnlyFields [
+          "BitRate"
+          "SamplePoint"
+          "TimeQuantaNSec"
+          "PropagationSegment"
+          "PhaseBufferSegment1"
+          "PhaseBufferSegment2"
+          "SyncJumpWidth"
+          "DataBitRate"
+          "DataSamplePoint"
+          "DataTimeQuantaNSec"
+          "DataPropagationSegment"
+          "DataPhaseBufferSegment1"
+          "DataPhaseBufferSegment2"
+          "DataSyncJumpWidth"
+          "FDMode"
+          "FDNonISO"
+          "RestartSec"
+          "Termination"
+          "TripleSampling"
+          "BusErrorReporting"
+          "ListenOnly"
+          "Loopback"
+          "OneShot"
+          "PresumeAck"
+          "ClassicDataLengthCode"
+        ])
+        (assertInt "TimeQuantaNSec" )
+        (assertRange "TimeQuantaNSec" 0 4294967295 )
+        (assertInt "PropagationSegment" )
+        (assertRange "PropagationSegment" 0 4294967295 )
+        (assertInt "PhaseBufferSegment1" )
+        (assertRange "PhaseBufferSegment1" 0 4294967295 )
+        (assertInt "PhaseBufferSegment2" )
+        (assertRange "PhaseBufferSegment2" 0 4294967295 )
+        (assertInt "SyncJumpWidth" )
+        (assertRange "SyncJumpWidth" 0 4294967295 )
+        (assertInt "DataTimeQuantaNSec" )
+        (assertRange "DataTimeQuantaNSec" 0 4294967295 )
+        (assertInt "DataPropagationSegment" )
+        (assertRange "DataPropagationSegment" 0 4294967295 )
+        (assertInt "DataPhaseBufferSegment1" )
+        (assertRange "DataPhaseBufferSegment1" 0 4294967295 )
+        (assertInt "DataPhaseBufferSegment2" )
+        (assertRange "DataPhaseBufferSegment2" 0 4294967295 )
+        (assertInt "DataSyncJumpWidth" )
+        (assertRange "DataSyncJumpWidth" 0 4294967295 )
+        (assertValueOneOf "FDMode" boolValues)
+        (assertValueOneOf "FDNonISO" boolValues)
+        (assertValueOneOf "TripleSampling" boolValues)
+        (assertValueOneOf "BusErrorReporting" boolValues)
+        (assertValueOneOf "ListenOnly" boolValues)
+        (assertValueOneOf "Loopback" boolValues)
+        (assertValueOneOf "OneShot" boolValues)
+        (assertValueOneOf "PresumeAck" boolValues)
+        (assertValueOneOf "ClassicDataLengthCode" boolValues)
+      ];
+
+      sectionIPoIB = checkUnitConfig "IPoIB" [
+        (assertOnlyFields [
+          "Mode"
+          "IgnoreUserspaceMulticastGroup"
+        ])
+        (assertValueOneOf "Mode" [ "datagram" "connected" ])
+        (assertValueOneOf "IgnoreUserspaceMulticastGroup" boolValues)
+      ];
+
+      sectionQDisc = checkUnitConfig "QDisc" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+        (assertValueOneOf "Parent" [ "clsact" "ingress" ])
+      ];
+
+      sectionNetworkEmulator = checkUnitConfig "NetworkEmulator" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "DelaySec"
+          "DelayJitterSec"
+          "PacketLimit"
+          "LossRate"
+          "DuplicateRate"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionTokenBucketFilter = checkUnitConfig "TokenBucketFilter" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "LatencySec"
+          "LimitBytes"
+          "BurstBytes"
+          "Rate"
+          "MPUBytes"
+          "PeakRate"
+          "MTUBytes"
+        ])
+      ];
+
+      sectionPIE = checkUnitConfig "PIE" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 1 4294967294)
+      ];
+
+      sectionFlowQueuePIE = checkUnitConfig "FlowQueuePIE" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 1 4294967294)
+      ];
+
+      sectionStochasticFairBlue = checkUnitConfig "StochasticFairBlue" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 1 4294967294)
+      ];
+
+      sectionStochasticFairnessQueueing = checkUnitConfig "StochasticFairnessQueueing" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PerturbPeriodSec"
+        ])
+        (assertInt "PerturbPeriodSec")
+      ];
+
+      sectionBFIFO = checkUnitConfig "BFIFO" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "LimitBytes"
+        ])
+      ];
+
+      sectionPFIFO = checkUnitConfig "PFIFO" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionPFIFOHeadDrop = checkUnitConfig "PFIFOHeadDrop" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionPFIFOFast = checkUnitConfig "PFIFOFast" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+      ];
+
+      sectionCAKE = checkUnitConfig "CAKE" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "Bandwidth"
+          "AutoRateIngress"
+          "OverheadBytes"
+          "MPUBytes"
+          "CompensationMode"
+          "UseRawPacketSize"
+          "FlowIsolationMode"
+          "NAT"
+          "PriorityQueueingPreset"
+          "FirewallMark"
+          "Wash"
+          "SplitGSO"
+        ])
+        (assertValueOneOf "AutoRateIngress" boolValues)
+        (assertInt "OverheadBytes")
+        (assertRange "OverheadBytes" (-64) 256)
+        (assertInt "MPUBytes")
+        (assertRange "MPUBytes" 1 256)
+        (assertValueOneOf "CompensationMode" [ "none" "atm" "ptm" ])
+        (assertValueOneOf "UseRawPacketSize" boolValues)
+        (assertValueOneOf "FlowIsolationMode"
+          [
+            "none"
+            "src-host"
+            "dst-host"
+            "hosts"
+            "flows"
+            "dual-src-host"
+            "dual-dst-host"
+            "triple"
+          ])
+        (assertValueOneOf "NAT" boolValues)
+        (assertValueOneOf "PriorityQueueingPreset"
+          [
+            "besteffort"
+            "precedence"
+            "diffserv8"
+            "diffserv4"
+            "diffserv3"
+          ])
+        (assertInt "FirewallMark")
+        (assertRange "FirewallMark" 1 4294967295)
+        (assertValueOneOf "Wash" boolValues)
+        (assertValueOneOf "SplitGSO" boolValues)
+      ];
+
+      sectionControlledDelay = checkUnitConfig "ControlledDelay" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+          "TargetSec"
+          "IntervalSec"
+          "ECN"
+          "CEThresholdSec"
+        ])
+        (assertValueOneOf "ECN" boolValues)
+      ];
+
+      sectionDeficitRoundRobinScheduler = checkUnitConfig "DeficitRoundRobinScheduler" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+      ];
+
+      sectionDeficitRoundRobinSchedulerClass = checkUnitConfig "DeficitRoundRobinSchedulerClass" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "QuantumBytes"
+        ])
+      ];
+
+      sectionEnhancedTransmissionSelection = checkUnitConfig "EnhancedTransmissionSelection" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "Bands"
+          "StrictBands"
+          "QuantumBytes"
+          "PriorityMap"
+        ])
+        (assertInt "Bands")
+        (assertRange "Bands" 1 16)
+        (assertInt "StrictBands")
+        (assertRange "StrictBands" 1 16)
+      ];
+
+      sectionGenericRandomEarlyDetection = checkUnitConfig "GenericRandomEarlyDetection" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "VirtualQueues"
+          "DefaultVirtualQueue"
+          "GenericRIO"
+        ])
+        (assertInt "VirtualQueues")
+        (assertRange "VirtualQueues" 1 16)
+        (assertInt "DefaultVirtualQueue")
+        (assertRange "DefaultVirtualQueue" 1 16)
+        (assertValueOneOf "GenericRIO" boolValues)
+      ];
+
+      sectionFairQueueingControlledDelay = checkUnitConfig "FairQueueingControlledDelay" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+          "MemoryLimitBytes"
+          "Flows"
+          "TargetSec"
+          "IntervalSec"
+          "QuantumBytes"
+          "ECN"
+          "CEThresholdSec"
+        ])
+        (assertInt "PacketLimit")
+        (assertInt "Flows")
+        (assertValueOneOf "ECN" boolValues)
+      ];
+
+      sectionFairQueueing = checkUnitConfig "FairQueueing" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+          "FlowLimit"
+          "QuantumBytes"
+          "InitualQuantumBytes"
+          "MaximumRate"
+          "Buckets"
+          "OrphanMask"
+          "Pacing"
+          "CEThresholdSec"
+        ])
+        (assertInt "PacketLimit")
+        (assertInt "FlowLimit")
+        (assertInt "OrphanMask")
+        (assertValueOneOf "Pacing" boolValues)
+      ];
+
+      sectionTrivialLinkEqualizer = checkUnitConfig "TrivialLinkEqualizer" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "Id"
+        ])
+      ];
+
+      sectionHierarchyTokenBucket = checkUnitConfig "HierarchyTokenBucket" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "DefaultClass"
+          "RateToQuantum"
+        ])
+        (assertInt "RateToQuantum")
+      ];
+
+      sectionHierarchyTokenBucketClass = checkUnitConfig "HierarchyTokenBucketClass" [
+        (assertOnlyFields [
+          "Parent"
+          "ClassId"
+          "Priority"
+          "QuantumBytes"
+          "MTUBytes"
+          "OverheadBytes"
+          "Rate"
+          "CeilRate"
+          "BufferBytes"
+          "CeilBufferBytes"
+        ])
+      ];
+
+      sectionHeavyHitterFilter = checkUnitConfig "HeavyHitterFilter" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+          "PacketLimit"
+        ])
+        (assertInt "PacketLimit")
+        (assertRange "PacketLimit" 0 4294967294)
+      ];
+
+      sectionQuickFairQueueing = checkUnitConfig "QuickFairQueueing" [
+        (assertOnlyFields [
+          "Parent"
+          "Handle"
+        ])
+      ];
+
+      sectionQuickFairQueueingClass = checkUnitConfig "QuickFairQueueingClass" [
+        (assertOnlyFields [
+          "Parent"
+          "ClassId"
+          "Weight"
+          "MaxPacketBytes"
+        ])
+        (assertInt "Weight")
+        (assertRange "Weight" 1 1023)
+      ];
+
+      sectionBridgeVLAN = checkUnitConfig "BridgeVLAN" [
+        (assertOnlyFields [
+          "VLAN"
+          "EgressUntagged"
+          "PVID"
+        ])
+        (assertInt "PVID")
+        (assertRange "PVID" 0 4094)
+      ];
     };
   };
 
@@ -887,6 +1440,8 @@ let
       type = types.bool;
       description = lib.mdDoc ''
         Whether to manage network configuration using {command}`systemd-network`.
+
+        This also enables {option}`systemd.networkd.enable`.
       '';
     };
 
@@ -969,6 +1524,21 @@ let
 
   };
 
+
+  l2tpSessionOptions = {
+    options = {
+      l2tpSessionConfig = mkOption {
+        default = {};
+        type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionL2TPSession;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[L2TPSession]` section of the unit.  See
+          {manpage}`systemd.netdev(5)` for details.
+        '';
+      };
+    };
+  };
+
   wireguardPeerOptions = {
     options = {
       wireguardPeerConfig = mkOption {
@@ -1082,6 +1652,38 @@ let
       '';
     };
 
+    l2tpConfig = mkOption {
+      default = {};
+      example = {
+        TunnelId = 10;
+        PeerTunnelId = 12;
+        Local = "static";
+        Remote = "192.168.30.101";
+        EncapsulationType = "ip";
+      };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionL2TP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[L2TP]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
+    l2tpSessions = mkOption {
+      default = [];
+      example = [ { l2tpSessionConfig={
+        SessionId = 25;
+        PeerSessionId = 26;
+        Name = "l2tp-sess";
+      };}];
+      type = with types; listOf (submodule l2tpSessionOptions);
+      description = lib.mdDoc ''
+        Each item in this array specifies an option in the
+        `[L2TPSession]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     wireguardConfig = mkOption {
       default = {};
       example = {
@@ -1230,6 +1832,21 @@ let
     };
   };
 
+  ipv6RoutePrefixOptions = {
+    options = {
+      ipv6RoutePrefixConfig = mkOption {
+        default = {};
+        example = { Route = "fd00::/64"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6RoutePrefix;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[IPv6RoutePrefix]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
   dhcpServerStaticLeaseOptions = {
     options = {
       dhcpServerStaticLeaseConfig = mkOption {
@@ -1248,6 +1865,51 @@ let
     };
   };
 
+  bridgeFDBOptions = {
+    options = {
+      bridgeFDBConfig = mkOption {
+        default = {};
+        example = { MACAddress = "65:43:4a:5b:d8:5f"; Destination = "192.168.1.42"; VNI = 20; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeFDB;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[BridgeFDB]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
+  bridgeMDBOptions = {
+    options = {
+      bridgeMDBConfig = mkOption {
+        default = {};
+        example = { MulticastGroupAddress = "ff02::1:2:3:4"; VLANId = 10; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeMDB;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[BridgeMDB]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
+  bridgeVLANOptions = {
+    options = {
+      bridgeMDBConfig = mkOption {
+        default = {};
+        example = { VLAN = 20; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeVLAN;
+        description = lib.mdDoc ''
+          Each attribute in this set specifies an option in the
+          `[BridgeVLAN]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
   networkOptions = commonNetworkOptions // {
 
     linkConfig = mkOption {
@@ -1302,12 +1964,17 @@ let
     };
 
     dhcpV6PrefixDelegationConfig = mkOption {
+      visible = false;
+      apply = _: throw "The option `systemd.network.networks.<name>.dhcpV6PrefixDelegationConfig` has been renamed to `systemd.network.networks.<name>.dhcpPrefixDelegationConfig`.";
+    };
+
+    dhcpPrefixDelegationConfig = mkOption {
       default = {};
       example = { SubnetId = "auto"; Announce = true; };
-      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6PrefixDelegation;
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPPrefixDelegation;
       description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        `[DHCPv6PrefixDelegation]` section of the unit. See
+        `[DHCPPrefixDelegation]` section of the unit. See
         {manpage}`systemd.network(5)` for details.
       '';
     };
@@ -1372,6 +2039,376 @@ let
       '';
     };
 
+    ipv6RoutePrefixes = mkOption {
+      default = [];
+      example = [ { ipv6RoutePrefixConfig = { Route = "fd00::/64"; LifetimeSec = 3600; }; } ];
+      type = with types; listOf (submodule ipv6RoutePrefixOptions);
+      description = lib.mdDoc ''
+        A list of ipv6RoutePrefix sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeConfig = mkOption {
+      default = {};
+      example = { MulticastFlood = false; Cost = 20; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridge;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[Bridge]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeFDBs = mkOption {
+      default = [];
+      example = [ { bridgeFDBConfig = { MACAddress = "90:e2:ba:43:fc:71"; Destination = "192.168.100.4"; VNI = 3600; }; } ];
+      type = with types; listOf (submodule bridgeFDBOptions);
+      description = lib.mdDoc ''
+        A list of BridgeFDB sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeMDBs = mkOption {
+      default = [];
+      example = [ { bridgeMDBConfig = { MulticastGroupAddress = "ff02::1:2:3:4"; VLANId = 10; } ; } ];
+      type = with types; listOf (submodule bridgeMDBOptions);
+      description = lib.mdDoc ''
+        A list of BridgeMDB sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    lldpConfig = mkOption {
+      default = {};
+      example = { MUDURL = "https://things.example.org/product_abc123/v5"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionLLDP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[LLDP]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    canConfig = mkOption {
+      default = {};
+      example = { };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionCAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[CAN]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    ipoIBConfig = mkOption {
+      default = {};
+      example = { };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPoIB;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[IPoIB]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    qdiscConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionQDisc;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[QDisc]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    networkEmulatorConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; DelaySec = "20msec"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetworkEmulator;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[NetworkEmulator]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    tokenBucketFilterConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; Rate = "100k"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionTokenBucketFilter;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[TokenBucketFilter]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pieConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "3847"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPIE;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PIE]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    flowQueuePIEConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "3847"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionFlowQueuePIE;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[FlowQueuePIE]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    stochasticFairBlueConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "3847"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionStochasticFairBlue;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[StochasticFairBlue]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    stochasticFairnessQueueingConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PerturbPeriodSec = "30"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionStochasticFairnessQueueing;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[StochasticFairnessQueueing]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bfifoConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; LimitBytes = "20K"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionBFIFO;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[BFIFO]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pfifoConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "300"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFO;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PFIFO]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pfifoHeadDropConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; PacketLimit = "300"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFOHeadDrop;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PFIFOHeadDrop]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    pfifoFastConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionPFIFOFast;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[PFIFOFast]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    cakeConfig = mkOption {
+      default = {};
+      example = { Bandwidth = "40M"; OverheadBytes = 8; CompensationMode = "ptm"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionCAKE;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[CAKE]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    controlledDelayConfig = mkOption {
+      default = {};
+      example = { Parent = "ingress"; TargetSec = "20msec"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionControlledDelay;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[ControlledDelay]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    deficitRoundRobinSchedulerConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDeficitRoundRobinScheduler;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[DeficitRoundRobinScheduler]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    deficitRoundRobinSchedulerClassConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; QuantumBytes = "300k"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDeficitRoundRobinSchedulerClass;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[DeficitRoundRobinSchedulerClass]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    enhancedTransmissionSelectionConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; QuantumBytes = "300k"; Bands = 3; PriorityMap = "100 200 300"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionEnhancedTransmissionSelection;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[EnhancedTransmissionSelection]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    genericRandomEarlyDetectionConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; VirtualQueues = 5; DefaultVirtualQueue = 3; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionGenericRandomEarlyDetection;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[GenericRandomEarlyDetection]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    fairQueueingControlledDelayConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; Flows = 5; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionFairQueueingControlledDelay;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[FairQueueingControlledDelay]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    fairQueueingConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; FlowLimit = 5; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionFairQueueing;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[FairQueueing]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    trivialLinkEqualizerConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; Id = 0; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionTrivialLinkEqualizer;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[TrivialLinkEqualizer]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    hierarchyTokenBucketConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionHierarchyTokenBucket;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[HierarchyTokenBucket]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    hierarchyTokenBucketClassConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; Rate = "10M"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionHierarchyTokenBucketClass;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[HierarchyTokenBucketClass]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    heavyHitterFilterConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; PacketLimit = 10000; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionHeavyHitterFilter;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[HeavyHitterFilter]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    quickFairQueueingConfig = mkOption {
+      default = {};
+      example = { Parent = "root"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionQuickFairQueueing;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[QuickFairQueueing]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    quickFairQueueingConfigClass = mkOption {
+      default = {};
+      example = { Parent = "root"; Weight = 133; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionQuickFairQueueingClass;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[QuickFairQueueingClass]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeVLANConfig = mkOption {
+      default = {};
+      example = { VLAN = "10-20"; };
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeVLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the
+        `[BridgeVLAN]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    bridgeVLANs = mkOption {
+      default = [];
+      example = [ { bridgeVLANConfig = { VLAN = "10-20"; }; } ];
+      type = with types; listOf (submodule bridgeVLANOptions);
+      description = lib.mdDoc ''
+        A list of BridgeVLAN sections to be added to the unit.  See
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
     name = mkOption {
       type = types.nullOr types.str;
       default = null;
@@ -1632,6 +2669,14 @@ let
           [Tap]
           ${attrsToSection def.tapConfig}
         ''
+        + optionalString (def.l2tpConfig != { }) ''
+          [L2TP]
+          ${attrsToSection def.l2tpConfig}
+        ''
+        + flip concatMapStrings def.l2tpSessions (x: ''
+          [L2TPSession]
+          ${attrsToSection x.l2tpSessionConfig}
+        '')
         + optionalString (def.wireguardConfig != { }) ''
           [WireGuard]
           ${attrsToSection def.wireguardConfig}
@@ -1743,9 +2788,9 @@ let
           [DHCPv6]
           ${attrsToSection def.dhcpV6Config}
         ''
-        + optionalString (def.dhcpV6PrefixDelegationConfig != { }) ''
-          [DHCPv6PrefixDelegation]
-          ${attrsToSection def.dhcpV6PrefixDelegationConfig}
+        + optionalString (def.dhcpPrefixDelegationConfig != { }) ''
+          [DHCPPrefixDelegation]
+          ${attrsToSection def.dhcpPrefixDelegationConfig}
         ''
         + optionalString (def.ipv6AcceptRAConfig != { }) ''
           [IPv6AcceptRA]
@@ -1763,23 +2808,153 @@ let
           [IPv6Prefix]
           ${attrsToSection x.ipv6PrefixConfig}
         '')
+        + flip concatMapStrings def.ipv6RoutePrefixes (x: ''
+          [IPv6RoutePrefix]
+          ${attrsToSection x.ipv6RoutePrefixConfig}
+        '')
         + flip concatMapStrings def.dhcpServerStaticLeases (x: ''
           [DHCPServerStaticLease]
           ${attrsToSection x.dhcpServerStaticLeaseConfig}
         '')
+        + optionalString (def.bridgeConfig != { }) ''
+          [Bridge]
+          ${attrsToSection def.bridgeConfig}
+        ''
+        + flip concatMapStrings def.bridgeFDBs (x: ''
+          [BridgeFDB]
+          ${attrsToSection x.bridgeFDBConfig}
+        '')
+        + flip concatMapStrings def.bridgeMDBs (x: ''
+          [BridgeMDB]
+          ${attrsToSection x.bridgeMDBConfig}
+        '')
+        + optionalString (def.lldpConfig != { }) ''
+          [LLDP]
+          ${attrsToSection def.lldpConfig}
+        ''
+        + optionalString (def.canConfig != { }) ''
+          [CAN]
+          ${attrsToSection def.canConfig}
+        ''
+        + optionalString (def.ipoIBConfig != { }) ''
+          [IPoIB]
+          ${attrsToSection def.ipoIBConfig}
+        ''
+        + optionalString (def.qdiscConfig != { }) ''
+          [QDisc]
+          ${attrsToSection def.qdiscConfig}
+        ''
+        + optionalString (def.networkEmulatorConfig != { }) ''
+          [NetworkEmulator]
+          ${attrsToSection def.networkEmulatorConfig}
+        ''
+        + optionalString (def.tokenBucketFilterConfig != { }) ''
+          [TokenBucketFilter]
+          ${attrsToSection def.tokenBucketFilterConfig}
+        ''
+        + optionalString (def.pieConfig != { }) ''
+          [PIE]
+          ${attrsToSection def.pieConfig}
+        ''
+        + optionalString (def.flowQueuePIEConfig != { }) ''
+          [FlowQueuePIE]
+          ${attrsToSection def.flowQueuePIEConfig}
+        ''
+        + optionalString (def.stochasticFairBlueConfig != { }) ''
+          [StochasticFairBlue]
+          ${attrsToSection def.stochasticFairBlueConfig}
+        ''
+        + optionalString (def.stochasticFairnessQueueingConfig != { }) ''
+          [StochasticFairnessQueueing]
+          ${attrsToSection def.stochasticFairnessQueueingConfig}
+        ''
+        + optionalString (def.bfifoConfig != { }) ''
+          [BFIFO]
+          ${attrsToSection def.bfifoConfig}
+        ''
+        + optionalString (def.pfifoConfig != { }) ''
+          [PFIFO]
+          ${attrsToSection def.pfifoConfig}
+        ''
+        + optionalString (def.pfifoHeadDropConfig != { }) ''
+          [PFIFOHeadDrop]
+          ${attrsToSection def.pfifoHeadDropConfig}
+        ''
+        + optionalString (def.pfifoFastConfig != { }) ''
+          [PFIFOFast]
+          ${attrsToSection def.pfifoFastConfig}
+        ''
+        + optionalString (def.cakeConfig != { }) ''
+          [CAKE]
+          ${attrsToSection def.cakeConfig}
+        ''
+        + optionalString (def.controlledDelayConfig != { }) ''
+          [ControlledDelay]
+          ${attrsToSection def.controlledDelayConfig}
+        ''
+        + optionalString (def.deficitRoundRobinSchedulerConfig != { }) ''
+          [DeficitRoundRobinScheduler]
+          ${attrsToSection def.deficitRoundRobinSchedulerConfig}
+        ''
+        + optionalString (def.deficitRoundRobinSchedulerClassConfig != { }) ''
+          [DeficitRoundRobinSchedulerClass]
+          ${attrsToSection def.deficitRoundRobinSchedulerClassConfig}
+        ''
+        + optionalString (def.enhancedTransmissionSelectionConfig != { }) ''
+          [EnhancedTransmissionSelection]
+          ${attrsToSection def.enhancedTransmissionSelectionConfig}
+        ''
+        + optionalString (def.genericRandomEarlyDetectionConfig != { }) ''
+          [GenericRandomEarlyDetection]
+          ${attrsToSection def.genericRandomEarlyDetectionConfig}
+        ''
+        + optionalString (def.fairQueueingControlledDelayConfig != { }) ''
+          [FairQueueingControlledDelay]
+          ${attrsToSection def.fairQueueingControlledDelayConfig}
+        ''
+        + optionalString (def.fairQueueingConfig != { }) ''
+          [FairQueueing]
+          ${attrsToSection def.fairQueueingConfig}
+        ''
+        + optionalString (def.trivialLinkEqualizerConfig != { }) ''
+          [TrivialLinkEqualizer]
+          ${attrsToSection def.trivialLinkEqualizerConfig}
+        ''
+        + optionalString (def.hierarchyTokenBucketConfig != { }) ''
+          [HierarchyTokenBucket]
+          ${attrsToSection def.hierarchyTokenBucketConfig}
+        ''
+        + optionalString (def.hierarchyTokenBucketClassConfig != { }) ''
+          [HierarchyTokenBucketClass]
+          ${attrsToSection def.hierarchyTokenBucketClassConfig}
+        ''
+        + optionalString (def.heavyHitterFilterConfig != { }) ''
+          [HeavyHitterFilter]
+          ${attrsToSection def.heavyHitterFilterConfig}
+        ''
+        + optionalString (def.quickFairQueueingConfig != { }) ''
+          [QuickFairQueueing]
+          ${attrsToSection def.quickFairQueueingConfig}
+        ''
+        + optionalString (def.quickFairQueueingConfigClass != { }) ''
+          [QuickFairQueueingClass]
+          ${attrsToSection def.quickFairQueueingConfigClass}
+        ''
+        + flip concatMapStrings def.bridgeVLANs (x: ''
+          [BridgeVLAN]
+          ${attrsToSection x.bridgeVLANConfig}
+        '')
         + def.extraConfig;
     };
 
-  unitFiles = listToAttrs (map (name: {
-    name = "systemd/network/${name}";
+  mkUnitFiles = prefix: cfg: listToAttrs (map (name: {
+    name = "${prefix}systemd/network/${name}";
     value.source = "${cfg.units.${name}.unit}/${name}";
   }) (attrNames cfg.units));
-in
 
-{
-  options = {
+  commonOptions = visible: {
 
-    systemd.network.enable = mkOption {
+    enable = mkOption {
       default = false;
       type = types.bool;
       description = lib.mdDoc ''
@@ -1787,32 +2962,36 @@ in
       '';
     };
 
-    systemd.network.links = mkOption {
+    links = mkOption {
       default = {};
+      inherit visible;
       type = with types; attrsOf (submodule [ { options = linkOptions; } ]);
       description = lib.mdDoc "Definition of systemd network links.";
     };
 
-    systemd.network.netdevs = mkOption {
+    netdevs = mkOption {
       default = {};
+      inherit visible;
       type = with types; attrsOf (submodule [ { options = netdevOptions; } ]);
       description = lib.mdDoc "Definition of systemd network devices.";
     };
 
-    systemd.network.networks = mkOption {
+    networks = mkOption {
       default = {};
+      inherit visible;
       type = with types; attrsOf (submodule [ { options = networkOptions; } networkConfig ]);
       description = lib.mdDoc "Definition of systemd networks.";
     };
 
-    systemd.network.config = mkOption {
+    config = mkOption {
       default = {};
+      inherit visible;
       type = with types; submodule [ { options = networkdOptions; } networkdConfig ];
       description = lib.mdDoc "Definition of global systemd network config.";
     };
 
-    systemd.network.units = mkOption {
-      description = "Definition of networkd units.";
+    units = mkOption {
+      description = lib.mdDoc "Definition of networkd units.";
       default = {};
       internal = true;
       type = with types; attrsOf (submodule (
@@ -1824,7 +3003,21 @@ in
         }));
     };
 
-    systemd.network.wait-online = {
+    wait-online = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        example = false;
+        description = lib.mdDoc ''
+          Whether to enable the systemd-networkd-wait-online service.
+
+          systemd-networkd-wait-online can timeout and fail if there are no network interfaces
+          available for it to manage. When systemd-networkd is enabled but a different service is
+          responsible for managing the system's internet connection (for example, NetworkManager or
+          connman are used to manage WiFi connections), this service is unnecessary and can be
+          disabled.
+        '';
+      };
       anyInterface = mkOption {
         description = lib.mdDoc ''
           Whether to consider the network online when any interface is online, as opposed to all of them.
@@ -1857,7 +3050,7 @@ in
           Extra command-line arguments to pass to systemd-networkd-wait-online.
           These also affect per-interface `systemd-network-wait-online@` services.
 
-          See [{manpage}`systemd-networkd-wait-online.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html) for all available options.
+          See {manpage}`systemd-networkd-wait-online.service(8)` for all available options.
         '';
         type = with types; listOf str;
         default = [];
@@ -1866,12 +3059,11 @@ in
 
   };
 
-  config = mkMerge [
+  commonConfig = config: let cfg = config.systemd.network; in mkMerge [
 
     # .link units are honored by udev, no matter if systemd-networkd is enabled or not.
     {
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links;
-      environment.etc = unitFiles;
 
       systemd.network.wait-online.extraArgs =
         [ "--timeout=${toString cfg.wait-online.timeout}" ]
@@ -1881,14 +3073,6 @@ in
 
     (mkIf config.systemd.network.enable {
 
-      users.users.systemd-network.group = "systemd-network";
-
-      systemd.additionalUpstreamSystemUnits = [
-        "systemd-networkd-wait-online.service"
-        "systemd-networkd.service"
-        "systemd-networkd.socket"
-      ];
-
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs
         // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks;
 
@@ -1897,15 +3081,8 @@ in
       # networkd.
       systemd.sockets.systemd-networkd.wantedBy = [ "sockets.target" ];
 
-      systemd.services.systemd-networkd = {
-        wantedBy = [ "multi-user.target" ];
-        aliases = [ "dbus-org.freedesktop.network1.service" ];
-        restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [
-          config.environment.etc."systemd/networkd.conf".source
-        ];
-      };
-
       systemd.services.systemd-networkd-wait-online = {
+        inherit (cfg.wait-online) enable;
         wantedBy = [ "network-online.target" ];
         serviceConfig.ExecStart = [
           ""
@@ -1925,8 +3102,37 @@ in
         };
       };
 
+    })
+  ];
+
+  stage2Config = let
+    cfg = config.systemd.network;
+    unitFiles = mkUnitFiles "" cfg;
+  in mkMerge [
+    (commonConfig config)
+
+    { environment.etc = unitFiles; }
+
+    (mkIf config.systemd.network.enable {
+
+      users.users.systemd-network.group = "systemd-network";
+
+      systemd.additionalUpstreamSystemUnits = [
+        "systemd-networkd-wait-online.service"
+        "systemd-networkd.service"
+        "systemd-networkd.socket"
+      ];
+
       environment.etc."systemd/networkd.conf" = renderConfig cfg.config;
 
+      systemd.services.systemd-networkd = {
+        wantedBy = [ "multi-user.target" ];
+        restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [
+          config.environment.etc."systemd/networkd.conf".source
+        ];
+        aliases = [ "dbus-org.freedesktop.network1.service" ];
+      };
+
       networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) {
         enable = mkDefault true;
         rttablesExtraConfig = ''
@@ -1937,6 +3143,129 @@ in
       };
 
       services.resolved.enable = mkDefault true;
+
+    })
+  ];
+
+  stage1Config = let
+    cfg = config.boot.initrd.systemd.network;
+  in mkMerge [
+    (commonConfig config.boot.initrd)
+
+    {
+      systemd.network.enable = mkDefault config.boot.initrd.network.enable;
+      systemd.contents = mkUnitFiles "/etc/" cfg;
+
+      # Networkd link files are used early by udev to set up interfaces early.
+      # This must be done in stage 1 to avoid race conditions between udev and
+      # network daemons.
+      systemd.network.units = lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units;
+      systemd.storePaths = ["${config.boot.initrd.systemd.package}/lib/systemd/network/99-default.link"];
+    }
+
+    (mkIf cfg.enable {
+
+      systemd.package = pkgs.systemdStage1Network;
+
+      # For networkctl
+      systemd.dbus.enable = mkDefault true;
+
+      systemd.additionalUpstreamUnits = [
+        "systemd-networkd-wait-online.service"
+        "systemd-networkd.service"
+        "systemd-networkd.socket"
+        "systemd-network-generator.service"
+        "network-online.target"
+        "network-pre.target"
+        "network.target"
+        "nss-lookup.target"
+        "nss-user-lookup.target"
+        "remote-fs-pre.target"
+        "remote-fs.target"
+      ];
+      systemd.users.systemd-network = {};
+      systemd.groups.systemd-network = {};
+
+      systemd.contents."/etc/systemd/networkd.conf" = renderConfig cfg.config;
+
+      systemd.services.systemd-networkd = {
+        wantedBy = [ "initrd.target" ];
+        # These before and conflicts lines can be removed when this PR makes it into a release:
+        # https://github.com/systemd/systemd/pull/27791
+        before = ["initrd-switch-root.target"];
+        conflicts = ["initrd-switch-root.target"];
+      };
+      systemd.sockets.systemd-networkd = {
+        wantedBy = [ "initrd.target" ];
+        before = ["initrd-switch-root.target"];
+        conflicts = ["initrd-switch-root.target"];
+      };
+
+      systemd.services.systemd-network-generator.wantedBy = [ "sysinit.target" ];
+
+      systemd.storePaths = [
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd"
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd-wait-online"
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-network-generator"
+      ];
+      kernelModules = [ "af_packet" ];
+
+      systemd.services.nixos-flush-networkd = mkIf config.boot.initrd.network.flushBeforeStage2 {
+        description = "Flush Network Configuration";
+        wantedBy = ["initrd.target"];
+        after = ["systemd-networkd.service" "dbus.socket" "dbus.service"];
+        before = ["shutdown.target" "initrd-switch-root.target"];
+        conflicts = ["shutdown.target" "initrd-switch-root.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          # This service does nothing when starting, but brings down
+          # interfaces when switching root. This is the easiest way to
+          # ensure proper ordering while stopping. See systemd.unit(5)
+          # section on Before= and After=. The important part is that
+          # we are stopped before units we need, like dbus.service,
+          # and that we are stopped before starting units like
+          # initrd-switch-root.target
+          Type = "oneshot";
+          RemainAfterExit = true;
+          ExecStart = "/bin/true";
+        };
+        # systemd-networkd doesn't bring down interfaces on its own
+        # when it exits (see: systemd-networkd(8)), so we have to do
+        # it ourselves. The networkctl command doesn't have a way to
+        # bring all interfaces down, so we have to iterate over the
+        # list and filter out unmanaged interfaces to bring them down
+        # individually.
+        preStop = ''
+          networkctl list --full --no-legend | while read _idx link _type _operational setup _; do
+            [ "$setup" = unmanaged ] && continue
+            networkctl down "$link"
+          done
+        '';
+      };
+
+    })
+  ];
+
+in
+
+{
+  options = {
+    systemd.network = commonOptions true;
+    boot.initrd.systemd.network = commonOptions "shallow";
+  };
+
+  config = mkMerge [
+    stage2Config
+    (mkIf config.boot.initrd.systemd.enable {
+      assertions = [{
+        assertion = config.boot.initrd.network.udhcpc.extraArgs == [];
+        message = ''
+          boot.initrd.network.udhcpc.extraArgs is not supported when
+          boot.initrd.systemd.enable is enabled
+        '';
+      }];
+
+      boot.initrd = stage1Config;
     })
   ];
 }
diff --git a/nixpkgs/nixos/modules/system/boot/plymouth.nix b/nixpkgs/nixos/modules/system/boot/plymouth.nix
index 02d8fcf4799c..a1ab70938575 100644
--- a/nixpkgs/nixos/modules/system/boot/plymouth.nix
+++ b/nixpkgs/nixos/modules/system/boot/plymouth.nix
@@ -62,7 +62,7 @@ in
 
     boot.plymouth = {
 
-      enable = mkEnableOption "Plymouth boot splash screen";
+      enable = mkEnableOption (lib.mdDoc "Plymouth boot splash screen");
 
       font = mkOption {
         default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf";
@@ -75,10 +75,10 @@ in
 
       themePackages = mkOption {
         default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth;
-        defaultText = literalDocBook ''
+        defaultText = literalMD ''
           A NixOS branded variant of the breeze theme when
-          <literal>config.${opt.theme} == "breeze"</literal>, otherwise
-          <literal>[ ]</literal>.
+          `config.${opt.theme} == "breeze"`, otherwise
+          `[ ]`.
         '';
         type = types.listOf types.package;
         description = lib.mdDoc ''
@@ -146,6 +146,9 @@ in
     systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
     systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
 
+    # Prevent Plymouth taking over the screen during system updates.
+    systemd.services.plymouth-start.restartIfChanged = false;
+
     boot.initrd.systemd = {
       extraBin.plymouth = "${plymouth}/bin/plymouth"; # for the recovery shell
       storePaths = [
diff --git a/nixpkgs/nixos/modules/system/boot/resolved.nix b/nixpkgs/nixos/modules/system/boot/resolved.nix
index 0ab2a875975d..4e7201833db6 100644
--- a/nixpkgs/nixos/modules/system/boot/resolved.nix
+++ b/nixpkgs/nixos/modules/system/boot/resolved.nix
@@ -16,7 +16,9 @@ in
       default = false;
       type = types.bool;
       description = lib.mdDoc ''
-        Whether to enable the systemd DNS resolver daemon.
+        Whether to enable the systemd DNS resolver daemon, `systemd-resolved`.
+
+        Search for `services.resolved` to see all options.
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
index 337064034efb..bad045ec101f 100644
--- a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
@@ -73,7 +73,7 @@ trap 'fail' 0
 
 # Print a greeting.
 info
-info "<<< NixOS Stage 1 >>>"
+info "<<< @distroName@ Stage 1 >>>"
 info
 
 # Make several required directories.
@@ -234,8 +234,7 @@ done
 mkdir -p /lib
 ln -s @modulesClosure@/lib/modules /lib/modules
 ln -s @modulesClosure@/lib/firmware /lib/firmware
-# see comment in stage-1.nix for explanation
-echo @extraUtils@/bin/modprobe-kernel > /proc/sys/kernel/modprobe
+echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
 for i in @kernelModules@; do
     info "loading module $(basename $i)..."
     modprobe $i
@@ -294,6 +293,9 @@ checkFS() {
     # Skip fsck for inherently readonly filesystems.
     if [ "$fsType" = squashfs ]; then return 0; fi
 
+    # Skip fsck.erofs because it is still experimental.
+    if [ "$fsType" = erofs ]; then return 0; fi
+
     # If we couldn't figure out the FS type, then skip fsck.
     if [ "$fsType" = auto ]; then
         echo 'cannot check filesystem with type "auto"!'
@@ -342,6 +344,14 @@ checkFS() {
     return 0
 }
 
+escapeFstab() {
+    local original="$1"
+
+    # Replace space
+    local escaped="${original// /\\040}"
+    # Replace tab
+    echo "${escaped//$'\t'/\\011}"
+}
 
 # Function for mounting a file system.
 mountFS() {
@@ -364,22 +374,6 @@ mountFS() {
 
     checkFS "$device" "$fsType"
 
-    # Optionally resize the filesystem.
-    case $options in
-        *x-nixos.autoresize*)
-            if [ "$fsType" = ext2 -o "$fsType" = ext3 -o "$fsType" = ext4 ]; then
-                modprobe "$fsType"
-                echo "resizing $device..."
-                e2fsck -fp "$device"
-                resize2fs "$device"
-            elif [ "$fsType" = f2fs ]; then
-                echo "resizing $device..."
-                fsck.f2fs -fp "$device"
-                resize.f2fs "$device"
-            fi
-            ;;
-    esac
-
     # Create backing directories for overlayfs
     if [ "$fsType" = overlay ]; then
         for i in upper work; do
@@ -403,6 +397,11 @@ mountFS() {
         n=$((n + 1))
     done
 
+    # For bind mounts, busybox has a tendency to ignore options, which can be a
+    # security issue (e.g. "nosuid"). Remounting the partition seems to fix the
+    # issue.
+    mount "/mnt-root$mountPoint" -o "remount,$optionsPrefixed"
+
     [ "$mountPoint" == "/" ] &&
         [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] &&
         lustrateRoot "/mnt-root"
@@ -414,7 +413,7 @@ lustrateRoot () {
     local root="$1"
 
     echo
-    echo -e "\e[1;33m<<< NixOS is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m"
+    echo -e "\e[1;33m<<< @distroName@ is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m"
     echo
 
     mkdir -m 0755 -p "$root/old-root.tmp"
@@ -430,7 +429,7 @@ lustrateRoot () {
         mv -v "$d" "$root/old-root.tmp"
     done
 
-    # Use .tmp to make sure subsequent invokations don't clash
+    # Use .tmp to make sure subsequent invocations don't clash
     mv -v "$root/old-root.tmp" "$root/old-root"
 
     mkdir -m 0755 -p "$root/etc"
@@ -555,6 +554,9 @@ while read -u 3 mountPoint; do
 
       umount /tmp-iso
       rmdir /tmp-iso
+      if [ -n "$isoPath" ] && [ $fsType = "iso9660" ] && mountpoint -q /findiso; then
+       umount /findiso
+      fi
       continue
     fi
 
@@ -566,7 +568,7 @@ while read -u 3 mountPoint; do
         continue
     fi
 
-    mountFS "$device" "$mountPoint" "$options" "$fsType"
+    mountFS "$device" "$(escapeFstab "$mountPoint")" "$(escapeFstab "$options")" "$fsType"
 done
 
 exec 3>&-
diff --git a/nixpkgs/nixos/modules/system/boot/stage-1.nix b/nixpkgs/nixos/modules/system/boot/stage-1.nix
index 37adcc531d3d..dcb15cf7d42b 100644
--- a/nixpkgs/nixos/modules/system/boot/stage-1.nix
+++ b/nixpkgs/nixos/modules/system/boot/stage-1.nix
@@ -90,7 +90,7 @@ let
   # copy what we need.  Instead of using statically linked binaries,
   # we just copy what we need from Glibc and use patchelf to make it
   # work.
-  extraUtils = pkgs.runCommandCC "extra-utils"
+  extraUtils = pkgs.runCommand "extra-utils"
     { nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
       allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
     }
@@ -150,32 +150,6 @@ let
       copy_bin_and_libs ${pkgs.kmod}/bin/kmod
       ln -sf kmod $out/bin/modprobe
 
-      # Dirty hack to make sure the kernel properly loads modules
-      # such as ext4 on demand (e.g. on a `mount(2)` syscall). This is necessary
-      # because `kmod` isn't linked against `libpthread.so.0` anymore (since
-      # it was merged into `libc.so.6` since version `2.34`), but still needs
-      # to access it for some reason. This is not an issue in stage-1 itself
-      # because of the `LD_LIBRARY_PATH`-variable and anytime later because the rpath of
-      # kmod/modprobe points to glibc's `$out/lib` where `libpthread.so.6` exists.
-      # However, this is a problem when the kernel calls `modprobe` inside
-      # the initial ramdisk because it doesn't know about the
-      # `LD_LIBRARY_PATH` and the rpath was nuked.
-      #
-      # Also, we can't use `makeWrapper` here because `kmod` only does
-      # `modprobe` functionality if `argv[0] == "modprobe"`.
-      cat >$out/bin/modprobe-kernel <<EOF
-      #!$out/bin/ash
-      export LD_LIBRARY_PATH=$out/lib
-      exec $out/bin/modprobe "\$@"
-      EOF
-      chmod +x $out/bin/modprobe-kernel
-
-      # Copy resize2fs if any ext* filesystems are to be resized
-      ${optionalString (any (fs: fs.autoResize && (lib.hasPrefix "ext" fs.fsType)) fileSystems) ''
-        # We need mke2fs in the initrd.
-        copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
-      ''}
-
       # Copy multipath.
       ${optionalString config.services.multipath.enable ''
         copy_bin_and_libs ${config.services.multipath.package}/bin/multipath
@@ -205,8 +179,9 @@ let
       # Copy ld manually since it isn't detected correctly
       cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
 
-      # Copy all of the needed libraries
-      find $out/bin $out/lib -type f | while read BIN; do
+      # Copy all of the needed libraries in a consistent order so
+      # duplicates are resolved the same way.
+      find $out/bin $out/lib -type f | sort | while read BIN; do
         echo "Copying libs for executable $BIN"
         for LIB in $(${findLibs}/bin/find-libs $BIN); do
           TGT="$out/lib/$(basename $LIB)"
@@ -341,6 +316,8 @@ let
 
     inherit (config.boot) resumeDevice;
 
+    inherit (config.system.nixos) distroName;
+
     inherit (config.system.build) earlyMountScript;
 
     inherit (config.boot.initrd) checkJournalingFS verbose
@@ -462,7 +439,8 @@ let
           ) config.boot.initrd.secrets)
          }
 
-        (cd "$tmp" && find . -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
+        # mindepth 1 so that we don't change the mode of /
+        (cd "$tmp" && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
           ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
       '';
 
@@ -475,12 +453,12 @@ in
       type = types.str;
       default = "";
       example = "/dev/sda3";
-      description = ''
+      description = lib.mdDoc ''
         Device for manual resume attempt during boot. This should be used primarily
         if you want to resume from file. If left empty, the swap partitions are used.
         Specify here the device where the file resides.
-        You should also use <varname>boot.kernelParams</varname> to specify
-        <literal>«resume_offset»</literal>.
+        You should also use {var}`boot.kernelParams` to specify
+        `«resume_offset»`.
       '';
     };
 
@@ -575,7 +553,7 @@ in
       internal = true;
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed in the builder of the
         extra-utils derivation.  This can be used to provide
         additional utilities in the initial ramdisk.
@@ -586,7 +564,7 @@ in
       internal = true;
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed in the builder of the
         extra-utils derivation after patchelf has done its
         job.  This can be used to test additional utilities
@@ -598,7 +576,7 @@ in
       internal = true;
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed in the builder of the
         udev-rules derivation.  This can be used to add
         additional udev rules in the initial ramdisk.
@@ -611,16 +589,14 @@ in
         then "zstd"
         else "gzip"
       );
-      defaultText = literalDocBook "<literal>zstd</literal> if the kernel supports it (5.9+), <literal>gzip</literal> if not";
+      defaultText = literalMD "`zstd` if the kernel supports it (5.9+), `gzip` if not";
       type = types.either types.str (types.functionTo types.str);
-      description = ''
+      description = lib.mdDoc ''
         The compressor to use on the initrd image. May be any of:
 
-        <itemizedlist>
-         <listitem><para>The name of one of the predefined compressors, see <filename>pkgs/build-support/kernel/initrd-compressor-meta.nix</filename> for the definitions.</para></listitem>
-         <listitem><para>A function which, given the nixpkgs package set, returns the path to a compressor tool, e.g. <literal>pkgs: "''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
-         <listitem><para>(not recommended, because it does not work when cross-compiling) the full path to a compressor tool, e.g. <literal>"''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
-        </itemizedlist>
+        - The name of one of the predefined compressors, see {file}`pkgs/build-support/kernel/initrd-compressor-meta.nix` for the definitions.
+        - A function which, given the nixpkgs package set, returns the path to a compressor tool, e.g. `pkgs: "''${pkgs.pigz}/bin/pigz"`
+        - (not recommended, because it does not work when cross-compiling) the full path to a compressor tool, e.g. `"''${pkgs.pigz}/bin/pigz"`
 
         The given program should read data from stdin and write it to stdout compressed.
       '';
@@ -662,16 +638,14 @@ in
       default = true;
       type = types.bool;
       description =
-        ''
+        lib.mdDoc ''
           Verbosity of the initrd. Please note that disabling verbosity removes
           only the mandatory messages generated by the NixOS scripts. For a
           completely silent boot, you might also want to set the two following
           configuration options:
 
-          <itemizedlist>
-            <listitem><para><literal>boot.consoleLogLevel = 0;</literal></para></listitem>
-            <listitem><para><literal>boot.kernelParams = [ "quiet" "udev.log_level=3" ];</literal></para></listitem>
-          </itemizedlist>
+          - `boot.consoleLogLevel = 0;`
+          - `boot.kernelParams = [ "quiet" "udev.log_level=3" ];`
         '';
     };
 
@@ -680,7 +654,7 @@ in
         default = false;
         type = types.bool;
         description =
-          ''
+          lib.mdDoc ''
             Whether the bootloader setup runs append-initrd-secrets.
             If not, any needed secrets must be copied into the initrd
             and thus added to the store.
diff --git a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
index f2a839d07868..f9a2084ea9e8 100755
--- a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
@@ -19,7 +19,7 @@ if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
 
     # Print a greeting.
     echo
-    echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
+    echo -e "\e[1;32m<<< @distroName@ Stage 2 >>>\e[0m"
     echo
 
 
@@ -68,7 +68,7 @@ fi
 # like squashfs.
 chown -f 0:30000 /nix/store
 chmod -f 1775 /nix/store
-if [ -n "@readOnlyStore@" ]; then
+if [ -n "@readOnlyNixStore@" ]; then
     if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then
         if [ -z "$container" ]; then
             mount --bind /nix/store /nix/store
diff --git a/nixpkgs/nixos/modules/system/boot/stage-2.nix b/nixpkgs/nixos/modules/system/boot/stage-2.nix
index 6b4193ea2967..001380158d5f 100644
--- a/nixpkgs/nixos/modules/system/boot/stage-2.nix
+++ b/nixpkgs/nixos/modules/system/boot/stage-2.nix
@@ -10,9 +10,9 @@ let
     src = ./stage-2-init.sh;
     shellDebug = "${pkgs.bashInteractive}/bin/bash";
     shell = "${pkgs.bash}/bin/bash";
-    inherit (config.boot) systemdExecutable extraSystemdUnitPaths;
+    inherit (config.boot) readOnlyNixStore systemdExecutable extraSystemdUnitPaths;
+    inherit (config.system.nixos) distroName;
     isExecutable = true;
-    inherit (config.nix) readOnlyStore;
     inherit useHostResolvConf;
     inherit (config.system.build) earlyMountScript;
     path = lib.makeBinPath ([
@@ -42,6 +42,17 @@ in
         '';
       };
 
+      readOnlyNixStore = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          If set, NixOS will enforce the immutability of the Nix store
+          by making {file}`/nix/store` a read-only bind
+          mount.  Nix will automatically make the store writable when
+          needed.
+        '';
+      };
+
       systemdExecutable = mkOption {
         default = "/run/current-system/systemd/lib/systemd/systemd";
         type = types.str;
diff --git a/nixpkgs/nixos/modules/system/boot/stratisroot.nix b/nixpkgs/nixos/modules/system/boot/stratisroot.nix
new file mode 100644
index 000000000000..241d044db2fe
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/stratisroot.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, utils, ... }:
+let
+  requiredStratisFilesystems = lib.attrsets.filterAttrs (_: x: utils.fsNeededForBoot x && x.stratis.poolUuid != null) config.fileSystems;
+in
+{
+  options = {};
+  config = lib.mkIf (requiredStratisFilesystems != {}) {
+    assertions = [
+      {
+        assertion = config.boot.initrd.systemd.enable;
+        message = "stratis root fs requires systemd stage 1";
+      }
+    ];
+    boot.initrd = {
+      systemd = {
+        storePaths = [
+          "${pkgs.stratisd}/lib/udev/stratis-base32-decode"
+          "${pkgs.stratisd}/lib/udev/stratis-str-cmp"
+          "${pkgs.lvm2.bin}/bin/dmsetup"
+          "${pkgs.stratisd}/libexec/stratisd-min"
+          "${pkgs.stratisd.initrd}/bin/stratis-rootfs-setup"
+        ];
+        packages = [pkgs.stratisd.initrd];
+        extraBin = {
+          thin_check = "${pkgs."thin-provisioning-tools"}/bin/thin_check";
+          thin_repair = "${pkgs."thin-provisioning-tools"}/bin/thin_repair";
+          thin_metadata_size = "${pkgs."thin-provisioning-tools"}/bin/thin_metadata_size";
+          stratis-min = "${pkgs.stratisd}/bin/stratis-min";
+        };
+        services =
+          lib.attrsets.mapAttrs' (
+            mountPoint: fileSystem: {
+              name = "stratis-setup-${fileSystem.stratis.poolUuid}";
+              value = {
+                description = "setup for Stratis root filesystem";
+                unitConfig.DefaultDependencies = "no";
+                conflicts = [ "shutdown.target" "initrd-switch-root.target" ];
+                onFailure = [ "emergency.target" ];
+                unitConfig.OnFailureJobMode = "isolate";
+                wants = [ "stratisd-min.service" "plymouth-start.service" ];
+                wantedBy = [ "initrd.target" ];
+                after = [ "paths.target" "plymouth-start.service" "stratisd-min.service" ];
+                before = [ "initrd.target" "shutdown.target" "initrd-switch-root.target" ];
+                environment.STRATIS_ROOTFS_UUID = fileSystem.stratis.poolUuid;
+                serviceConfig = {
+                  Type = "oneshot";
+                  ExecStart = "${pkgs.stratisd.initrd}/bin/stratis-rootfs-setup";
+                  RemainAfterExit = "yes";
+                };
+              };
+            }
+          ) requiredStratisFilesystems;
+      };
+      availableKernelModules = [ "dm-thin-pool" "dm-crypt" ] ++ [ "aes" "aes_generic" "blowfish" "twofish"
+        "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
+        "af_alg" "algif_skcipher"
+      ];
+      services.udev.packages = [
+        pkgs.stratisd.initrd
+        pkgs.lvm2
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd.nix b/nixpkgs/nixos/modules/system/boot/systemd.nix
index 35280ee07366..d2af86fef57c 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd.nix
@@ -79,6 +79,8 @@ let
       # Filesystems.
       "systemd-fsck@.service"
       "systemd-fsck-root.service"
+      "systemd-growfs@.service"
+      "systemd-growfs-root.service"
       "systemd-remount-fs.service"
       "systemd-pstore.service"
       "local-fs.target"
@@ -121,7 +123,7 @@ let
       "final.target"
       "kexec.target"
       "systemd-kexec.service"
-      "systemd-update-utmp.service"
+    ] ++ lib.optional cfg.package.withUtmp "systemd-update-utmp.service" ++ [
 
       # Password entry.
       "systemd-ask-password-console.path"
@@ -151,6 +153,9 @@ let
     ] ++ optionals cfg.package.withHostnamed [
       "dbus-org.freedesktop.hostname1.service"
       "systemd-hostnamed.service"
+    ] ++ optionals cfg.package.withPortabled [
+      "dbus-org.freedesktop.portable1.service"
+      "systemd-portabled.service"
     ] ++ [
       "systemd-exit.service"
       "systemd-update-done.service"
@@ -325,8 +330,8 @@ in
       type = types.lines;
       example = "DefaultLimitCORE=infinity";
       description = lib.mdDoc ''
-        Extra config options for systemd. See man systemd-system.conf for
-        available options.
+        Extra config options for systemd. See systemd-system.conf(5) man page
+        for available options.
       '';
     };
 
@@ -447,7 +452,7 @@ in
         (mkAfter [ "systemd" ])
       ]);
       group = (mkMerge [
-        (mkAfter [ "systemd" ])
+        (mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups
       ]);
     };
 
@@ -555,7 +560,8 @@ in
       # Environment of PID 1
       systemd.managerEnvironment = {
         # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools
-        PATH = lib.makeBinPath config.system.fsPackages;
+        # util-linux is needed for the main fsck utility wrapping the fs-specific ones
+        PATH = lib.makeBinPath (config.system.fsPackages ++ [cfg.package.util-linux]);
         LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
         TZDIR = "/etc/zoneinfo";
         # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
@@ -582,6 +588,15 @@ in
     systemd.services."systemd-backlight@".restartIfChanged = false;
     systemd.services."systemd-fsck@".restartIfChanged = false;
     systemd.services."systemd-fsck@".path = [ config.system.path ];
+    systemd.services."systemd-makefs@" = {
+      restartIfChanged = false;
+      path = [ pkgs.util-linux ] ++ config.system.fsPackages;
+      # Since there is no /etc/systemd/system/systemd-makefs@.service
+      # file, the units generated in /run/systemd/generator would
+      # override anything we put here. But by forcing the use of a
+      # drop-in in /etc, it does apply.
+      overrideStrategy = "asDropin";
+    };
     systemd.services.systemd-random-seed.restartIfChanged = false;
     systemd.services.systemd-remount-fs.restartIfChanged = false;
     systemd.services.systemd-update-utmp.restartIfChanged = false;
@@ -608,6 +623,10 @@ in
 
     boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
 
+    # Avoid potentially degraded system state due to
+    # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)."
+    systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
+
     services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix b/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix
index c2ca973d3807..03ef00e5683c 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/coredump.nix
@@ -44,7 +44,21 @@ in {
         '';
 
         # install provided sysctl snippets
-        "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+        "sysctl.d/50-coredump.conf".source =
+          # Fix systemd-coredump error caused by truncation of `kernel.core_pattern`
+          # when the `systemd` derivation name is too long. This works by substituting
+          # the path to `systemd` with a symlink that has a constant-length path.
+          #
+          # See: https://github.com/NixOS/nixpkgs/issues/213408
+          pkgs.substitute {
+            src = "${systemd}/example/sysctl.d/50-coredump.conf";
+            replacements = [
+              "--replace"
+              "${systemd}"
+              "${pkgs.symlinkJoin { name = "systemd"; paths = [ systemd ]; }}"
+            ];
+          };
+
         "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
       };
 
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/homed.nix b/nixpkgs/nixos/modules/system/boot/systemd/homed.nix
new file mode 100644
index 000000000000..403d1690124d
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd/homed.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.homed;
+in
+{
+  options.services.homed.enable = lib.mkEnableOption (lib.mdDoc ''
+    Enable systemd home area/user account manager
+  '');
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = config.services.nscd.enable;
+        message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,";
+      }
+    ];
+
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-homed.service"
+      "systemd-homed-activate.service"
+    ];
+
+    # This is mentioned in homed's [Install] section.
+    #
+    # While homed appears to work without it, it's probably better
+    # to follow upstream recommendations.
+    services.userdbd.enable = lib.mkDefault true;
+
+    systemd.services = {
+      systemd-homed = {
+        # These packages are required to manage encrypted volumes
+        path = config.system.fsPackages;
+        aliases = [ "dbus-org.freedesktop.home1.service" ];
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      systemd-homed-activate = {
+        wantedBy = [ "systemd-homed.service" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix b/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix
index bc65880719d7..7b59c0cbe7b8 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/initrd-secrets.nix
@@ -19,13 +19,13 @@
       # drop this service, we'd mount the /run tmpfs over the secret, making it
       # invisible in stage 2.
       script = ''
-        for secret in $(cd /.initrd-secrets; find . -type f); do
+        for secret in $(cd /.initrd-secrets; find . -type f -o -type l); do
           mkdir -p "$(dirname "/$secret")"
           cp "/.initrd-secrets/$secret" "/$secret"
         done
       '';
 
-      unitConfig = {
+      serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
       };
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix b/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix
index 888653469ed7..3f40a5b2dfa0 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/initrd.nix
@@ -1,4 +1,4 @@
-{ lib, config, utils, pkgs, ... }:
+{ lib, options, config, utils, pkgs, ... }:
 
 with lib;
 
@@ -71,15 +71,6 @@ let
     "systemd-tmpfiles-setup.service"
     "timers.target"
     "umount.target"
-
-    # TODO: Networking
-    # "network-online.target"
-    # "network-pre.target"
-    # "network.target"
-    # "nss-lookup.target"
-    # "nss-user-lookup.target"
-    # "remote-fs-pre.target"
-    # "remote-fs.target"
   ] ++ cfg.additionalUpstreamUnits;
 
   upstreamWants = [
@@ -100,14 +91,7 @@ let
 
   fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
 
-  fstab = pkgs.writeText "initrd-fstab" (lib.concatMapStringsSep "\n"
-    ({ fsType, mountPoint, device, options, autoFormat, autoResize, ... }@fs: let
-        opts = options ++ optional autoFormat "x-systemd.makefs" ++ optional autoResize "x-systemd.growfs";
-        finalDevice = if (lib.elem "bind" options) then "/sysroot${device}" else device;
-      in "${finalDevice} /sysroot${mountPoint} ${fsType} ${lib.concatStringsSep "," opts}") fileSystems);
-
   needMakefs = lib.any (fs: fs.autoFormat) fileSystems;
-  needGrowfs = lib.any (fs: fs.autoResize) fileSystems;
 
   kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
   modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
@@ -124,7 +108,7 @@ let
     name = "initrd-bin-env";
     paths = map getBin cfg.initrdBin;
     pathsToLink = ["/bin" "/sbin"];
-    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin);
+    postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -sf '${v}' $out/bin/'${n}'") cfg.extraBin);
   };
 
   initialRamdisk = pkgs.makeInitrdNG {
@@ -138,17 +122,42 @@ let
 
 in {
   options.boot.initrd.systemd = {
-    enable = mkEnableOption ''systemd in initrd.
-
-      Note: This is in very early development and is highly
-      experimental. Most of the features NixOS supports in initrd are
-      not yet supported by the intrd generated with this option.
-    '';
+    enable = mkEnableOption (lib.mdDoc "systemd in initrd") // {
+      description = lib.mdDoc ''
+        Whether to enable systemd in initrd. The unit options such as
+        {option}`boot.initrd.systemd.services` are the same as their
+        stage 2 counterparts such as {option}`systemd.services`,
+        except that `restartTriggers` and `reloadTriggers` are not
+        supported.
+
+        Note: This is experimental. Some of the `boot.initrd` options
+        are not supported when this is enabled, and the options under
+        `boot.initrd.systemd` are subject to change.
+      '';
+    };
 
-    package = (mkPackageOption pkgs "systemd" {
+    package = mkPackageOptionMD pkgs "systemd" {
       default = "systemdStage1";
-    }) // {
-      visible = false;
+    };
+
+    extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "DefaultLimitCORE=infinity";
+      description = lib.mdDoc ''
+        Extra config options for systemd. See systemd-system.conf(5) man page
+        for available options.
+      '';
+    };
+
+    managerEnvironment = mkOption {
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+      default = {};
+      example = { SYSTEMD_LOG_LEVEL = "debug"; };
+      description = lib.mdDoc ''
+        Environment variables of PID 1. These variables are
+        *not* passed to started units.
+      '';
     };
 
     contents = mkOption {
@@ -158,7 +167,6 @@ in {
           "/etc/hostname".text = "mymachine";
         }
       '';
-      visible = false;
       default = {};
       type = utils.systemdUtils.types.initrdContents;
     };
@@ -208,7 +216,6 @@ in {
 
     emergencyAccess = mkOption {
       type = with types; oneOf [ bool (nullOr (passwdEntry str)) ];
-      visible = false;
       description = lib.mdDoc ''
         Set to true for unauthenticated emergency access, and false for
         no emergency access.
@@ -222,7 +229,6 @@ in {
     initrdBin = mkOption {
       type = types.listOf types.package;
       default = [];
-      visible = false;
       description = lib.mdDoc ''
         Packages to include in /bin for the stage 1 emergency shell.
       '';
@@ -231,7 +237,6 @@ in {
     additionalUpstreamUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      visible = false;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
       description = lib.mdDoc ''
         Additional units shipped with systemd that shall be enabled.
@@ -242,7 +247,6 @@ in {
       default = [ ];
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
-      visible = false;
       description = lib.mdDoc ''
         A list of units to skip when generating system systemd configuration directory. This has
         priority over upstream units, {option}`boot.initrd.systemd.units`, and
@@ -255,13 +259,12 @@ in {
     units = mkOption {
       description = lib.mdDoc "Definition of systemd units.";
       default = {};
-      visible = false;
+      visible = "shallow";
       type = systemdUtils.types.units;
     };
 
     packages = mkOption {
       default = [];
-      visible = false;
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
       description = lib.mdDoc "Packages providing systemd units and hooks.";
@@ -269,7 +272,7 @@ in {
 
     targets = mkOption {
       default = {};
-      visible = false;
+      visible = "shallow";
       type = systemdUtils.types.initrdTargets;
       description = lib.mdDoc "Definition of systemd target units.";
     };
@@ -277,35 +280,35 @@ in {
     services = mkOption {
       default = {};
       type = systemdUtils.types.initrdServices;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd service units.";
     };
 
     sockets = mkOption {
       default = {};
       type = systemdUtils.types.initrdSockets;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd socket units.";
     };
 
     timers = mkOption {
       default = {};
       type = systemdUtils.types.initrdTimers;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd timer units.";
     };
 
     paths = mkOption {
       default = {};
       type = systemdUtils.types.initrdPaths;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd path units.";
     };
 
     mounts = mkOption {
       default = [];
       type = systemdUtils.types.initrdMounts;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc ''
         Definition of systemd mount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -316,7 +319,7 @@ in {
     automounts = mkOption {
       default = [];
       type = systemdUtils.types.automounts;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc ''
         Definition of systemd automount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -327,7 +330,7 @@ in {
     slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of slice configurations.";
     };
   };
@@ -335,7 +338,12 @@ in {
   config = mkIf (config.boot.initrd.enable && cfg.enable) {
     system.build = { inherit initialRamdisk; };
 
-    boot.initrd.availableKernelModules = [ "autofs4" ]; # systemd needs this for some features
+    boot.initrd.availableKernelModules = [
+      # systemd needs this for some features
+      "autofs4"
+      # systemd-cryptenroll
+      "tpm-tis"
+    ] ++ lib.optional (pkgs.stdenv.hostPlatform.system != "riscv64-linux") "tpm-crb";
 
     boot.initrd.systemd = {
       initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages;
@@ -343,26 +351,32 @@ in {
         less = "${pkgs.less}/bin/less";
         mount = "${cfg.package.util-linux}/bin/mount";
         umount = "${cfg.package.util-linux}/bin/umount";
+        fsck = "${cfg.package.util-linux}/bin/fsck";
       };
 
+      managerEnvironment.PATH = "/bin:/sbin";
+
       contents = {
+        "/tmp/.keep".text = "systemd requires the /tmp mount point in the initrd cpio archive";
         "/init".source = "${cfg.package}/lib/systemd/systemd";
         "/etc/systemd/system".source = stage1Units;
 
         "/etc/systemd/system.conf".text = ''
           [Manager]
-          DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"}
+          DefaultEnvironment=PATH=/bin:/sbin
+          ${cfg.extraConfig}
+          ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         '';
 
-        "/etc/fstab".source = fstab;
-
         "/lib/modules".source = "${modulesClosure}/lib/modules";
         "/lib/firmware".source = "${modulesClosure}/lib/firmware";
 
         "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
 
-        "/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd";
-        "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::";
+        # We can use either ! or * to lock the root account in the
+        # console, but some software like OpenSSH won't even allow you
+        # to log in with an SSH key if you use ! so we use * instead
+        "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then optionalString (!cfg.emergencyAccess) "*" else cfg.emergencyAccess}:::::::";
 
         "/bin".source = "${initrdBinEnv}/bin";
         "/sbin".source = "${initrdBinEnv}/sbin";
@@ -377,12 +391,13 @@ in {
         "/etc/os-release".source = config.boot.initrd.osRelease;
         "/etc/initrd-release".source = config.boot.initrd.osRelease;
 
+      } // optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") {
+        "/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source;
       };
 
       storePaths = [
         # systemd tooling
         "${cfg.package}/lib/systemd/systemd-fsck"
-        (lib.mkIf needGrowfs "${cfg.package}/lib/systemd/systemd-growfs")
         "${cfg.package}/lib/systemd/systemd-hibernate-resume"
         "${cfg.package}/lib/systemd/systemd-journald"
         (lib.mkIf needMakefs "${cfg.package}/lib/systemd/systemd-makefs")
@@ -406,6 +421,14 @@ in {
 
         # so NSS can look up usernames
         "${pkgs.glibc}/lib/libnss_files.so.2"
+      ] ++ optionals cfg.package.withCryptsetup [
+        # tpm2 support
+        "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so"
+        pkgs.tpm2-tss
+
+        # fido2 support
+        "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so"
+        "${pkgs.libfido2}/lib/libfido2.so.1"
       ] ++ jobScripts;
 
       targets.initrd.aliases = ["default.target"];
@@ -423,21 +446,6 @@ in {
                      (v: let n = escapeSystemdPath v.where;
                          in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
 
-      # The unit in /run/systemd/generator shadows the unit in
-      # /etc/systemd/system, but will still apply drop-ins from
-      # /etc/systemd/system/foo.service.d/
-      #
-      # We need IgnoreOnIsolate, otherwise the Requires dependency of
-      # a mount unit on its makefs unit causes it to be unmounted when
-      # we isolate for switch-root. Use a dummy package so that
-      # generateUnits will generate drop-ins instead of unit files.
-      packages = [(pkgs.runCommand "dummy" {} ''
-        mkdir -p $out/etc/systemd/system
-        touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
-      '')];
-      services."systemd-makefs@" = lib.mkIf needMakefs { unitConfig.IgnoreOnIsolate = true; };
-      services."systemd-growfs@" = lib.mkIf needGrowfs { unitConfig.IgnoreOnIsolate = true; };
-
       # make sure all the /dev nodes are set up
       services.systemd-tmpfiles-setup-dev.wantedBy = ["sysinit.target"];
 
@@ -471,7 +479,7 @@ in {
 
           # If we are not booting a NixOS closure (e.g. init=/bin/sh),
           # we don't know what root to prepare so we don't do anything
-          if ! [ -x "/sysroot$closure/prepare-root" ]; then
+          if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then
             echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf
             echo "$closure does not look like a NixOS installation - not activating"
             exit 0
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/logind.nix b/nixpkgs/nixos/modules/system/boot/systemd/logind.nix
index 598016032136..cf01c1882857 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/logind.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/logind.nix
@@ -11,64 +11,145 @@ let
   ];
 in
 {
-  options = {
-    services.logind.extraConfig = mkOption {
+  options.services.logind = {
+    extraConfig = mkOption {
       default = "";
       type = types.lines;
       example = "IdleAction=lock";
       description = lib.mdDoc ''
-        Extra config options for systemd-logind. See
-        [
-        logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html) for available options.
+        Extra config options for systemd-logind.
+        See [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html)
+        for available options.
       '';
     };
 
-    services.logind.killUserProcesses = mkOption {
+    killUserProcesses = mkOption {
       default = false;
       type = types.bool;
       description = lib.mdDoc ''
         Specifies whether the processes of a user should be killed
         when the user logs out.  If true, the scope unit corresponding
         to the session and all processes inside that scope will be
-        terminated.  If false, the scope is "abandoned" (see
-        [systemd.scope(5)](https://www.freedesktop.org/software/systemd/man/systemd.scope.html#)), and processes are not killed.
+        terminated.  If false, the scope is "abandoned"
+        (see [systemd.scope(5)](https://www.freedesktop.org/software/systemd/man/systemd.scope.html#)),
+        and processes are not killed.
 
         See [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=)
         for more details.
       '';
     };
 
-    services.logind.lidSwitch = mkOption {
+    powerKey = mkOption {
+      default = "poweroff";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the power key is pressed.
+      '';
+    };
+
+    powerKeyLongPress = mkOption {
+      default = "ignore";
+      example = "reboot";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the power key is long-pressed.
+      '';
+    };
+
+    rebootKey = mkOption {
+      default = "reboot";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the reboot key is pressed.
+      '';
+    };
+
+    rebootKeyLongPress = mkOption {
+      default = "poweroff";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the reboot key is long-pressed.
+      '';
+    };
+
+    suspendKey = mkOption {
       default = "suspend";
       example = "ignore";
       type = logindHandlerType;
 
       description = lib.mdDoc ''
-        Specifies what to be done when the laptop lid is closed.
+        Specifies what to do when the suspend key is pressed.
+      '';
+    };
+
+    suspendKeyLongPress = mkOption {
+      default = "hibernate";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the suspend key is long-pressed.
+      '';
+    };
+
+    hibernateKey = mkOption {
+      default = "hibernate";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the hibernate key is pressed.
       '';
     };
 
-    services.logind.lidSwitchDocked = mkOption {
+    hibernateKeyLongPress = mkOption {
       default = "ignore";
       example = "suspend";
       type = logindHandlerType;
 
       description = lib.mdDoc ''
-        Specifies what to be done when the laptop lid is closed
-        and another screen is added.
+        Specifies what to do when the hibernate key is long-pressed.
       '';
     };
 
-    services.logind.lidSwitchExternalPower = mkOption {
+    lidSwitch = mkOption {
+      default = "suspend";
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the laptop lid is closed.
+      '';
+    };
+
+    lidSwitchExternalPower = mkOption {
       default = cfg.lidSwitch;
       defaultText = literalExpression "services.logind.lidSwitch";
       example = "ignore";
       type = logindHandlerType;
 
       description = lib.mdDoc ''
-        Specifies what to do when the laptop lid is closed and the system is
-        on external power. By default use the same action as specified in
-        services.logind.lidSwitch.
+        Specifies what to do when the laptop lid is closed
+        and the system is on external power. By default use
+        the same action as specified in services.logind.lidSwitch.
+      '';
+    };
+
+    lidSwitchDocked = mkOption {
+      default = "ignore";
+      example = "suspend";
+      type = logindHandlerType;
+
+      description = lib.mdDoc ''
+        Specifies what to do when the laptop lid is closed
+        and another screen is added.
       '';
     };
   };
@@ -82,6 +163,8 @@ in
       "dbus-org.freedesktop.import1.service"
     ] ++ optionals config.systemd.package.withMachined [
       "dbus-org.freedesktop.machine1.service"
+    ] ++ optionals config.systemd.package.withPortabled [
+      "dbus-org.freedesktop.portable1.service"
     ] ++ [
       "dbus-org.freedesktop.login1.service"
       "user@.service"
@@ -92,9 +175,17 @@ in
       "systemd/logind.conf".text = ''
         [Login]
         KillUserProcesses=${if cfg.killUserProcesses then "yes" else "no"}
+        HandlePowerKey=${cfg.powerKey}
+        HandlePowerKeyLongPress=${cfg.powerKeyLongPress}
+        HandleRebootKey=${cfg.rebootKey}
+        HandleRebootKeyLongPress=${cfg.rebootKeyLongPress}
+        HandleSuspendKey=${cfg.suspendKey}
+        HandleSuspendKeyLongPress=${cfg.suspendKeyLongPress}
+        HandleHibernateKey=${cfg.hibernateKey}
+        HandleHibernateKeyLongPress=${cfg.hibernateKeyLongPress}
         HandleLidSwitch=${cfg.lidSwitch}
-        HandleLidSwitchDocked=${cfg.lidSwitchDocked}
         HandleLidSwitchExternalPower=${cfg.lidSwitchExternalPower}
+        HandleLidSwitchDocked=${cfg.lidSwitchDocked}
         ${cfg.extraConfig}
       '';
     };
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix b/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix
index 0d06fb3c0322..cbc89554c9fd 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/nspawn.nix
@@ -27,12 +27,12 @@ let
     (assertOnlyFields [
       "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem"
       "Overlay" "OverlayReadOnly" "PrivateUsersChown" "BindUser"
-      "Inaccessible" "PrivateUserOwnership"
+      "Inaccessible" "PrivateUsersOwnership"
     ])
     (assertValueOneOf "ReadOnly" boolValues)
     (assertValueOneOf "Volatile" (boolValues ++ [ "state" ]))
     (assertValueOneOf "PrivateUsersChown" boolValues)
-    (assertValueOneOf "PrivateUserOwnership" [ "off" "chown" "map" "auto" ])
+    (assertValueOneOf "PrivateUsersOwnership" [ "off" "chown" "map" "auto" ])
   ];
 
   checkNetwork = checkUnitConfig "Network" [
@@ -45,7 +45,9 @@ let
   ];
 
   instanceOptions = {
-    options = sharedOptions // {
+    options =
+    (getAttrs [ "enable" ] sharedOptions)
+    // {
       execConfig = mkOption {
         default = {};
         example = { Parameters = "/bin/sh"; };
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix b/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix
new file mode 100644
index 000000000000..fad755e278c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd/oomd.nix
@@ -0,0 +1,57 @@
+{ config, lib, ... }: let
+
+  cfg = config.systemd.oomd;
+
+in {
+  options.systemd.oomd = {
+    enable = lib.mkEnableOption (lib.mdDoc "the `systemd-oomd` OOM killer") // { default = true; };
+
+    # Fedora enables the first and third option by default. See the 10-oomd-* files here:
+    # https://src.fedoraproject.org/rpms/systemd/tree/acb90c49c42276b06375a66c73673ac351025597
+    enableRootSlice = lib.mkEnableOption (lib.mdDoc "oomd on the root slice (`-.slice`)");
+    enableSystemSlice = lib.mkEnableOption (lib.mdDoc "oomd on the system slice (`system.slice`)");
+    enableUserServices = lib.mkEnableOption (lib.mdDoc "oomd on all user services (`user@.service`)");
+
+    extraConfig = lib.mkOption {
+      type = with lib.types; attrsOf (oneOf [ str int bool ]);
+      default = {};
+      example = lib.literalExpression ''{ DefaultMemoryPressureDurationSec = "20s"; }'';
+      description = lib.mdDoc ''
+        Extra config options for `systemd-oomd`. See {command}`man oomd.conf`
+        for available options.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-oomd.service"
+      "systemd-oomd.socket"
+    ];
+    systemd.services.systemd-oomd.wantedBy = [ "multi-user.target" ];
+
+    environment.etc."systemd/oomd.conf".text = lib.generators.toINI {} {
+      OOM = cfg.extraConfig;
+    };
+
+    systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = lib.mkDefault "20s"; # Fedora default
+
+    users.users.systemd-oom = {
+      description = "systemd-oomd service user";
+      group = "systemd-oom";
+      isSystemUser = true;
+    };
+    users.groups.systemd-oom = { };
+
+    systemd.slices."-".sliceConfig = lib.mkIf cfg.enableRootSlice {
+      ManagedOOMSwap = "kill";
+    };
+    systemd.slices."system".sliceConfig = lib.mkIf cfg.enableSystemSlice {
+      ManagedOOMSwap = "kill";
+    };
+    systemd.services."user@".serviceConfig = lib.mkIf cfg.enableUserServices {
+      ManagedOOMMemoryPressure = "kill";
+      ManagedOOMMemoryPressureLimit = "50%";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/repart.nix b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix
new file mode 100644
index 000000000000..e81b3e4ff2a1
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix
@@ -0,0 +1,152 @@
+{ config, pkgs, lib, utils, ... }:
+
+let
+  cfg = config.systemd.repart;
+  initrdCfg = config.boot.initrd.systemd.repart;
+
+  writeDefinition = name: partitionConfig: pkgs.writeText
+    "${name}.conf"
+    (lib.generators.toINI { } { Partition = partitionConfig; });
+
+  listOfDefinitions = lib.mapAttrsToList
+    writeDefinition
+    (lib.filterAttrs (k: _: !(lib.hasPrefix "_" k)) cfg.partitions);
+
+  # Create a directory in the store that contains a copy of all definition
+  # files. This is then passed to systemd-repart in the initrd so it can access
+  # the definition files after the sysroot has been mounted but before
+  # activation. This needs a hard copy of the files and not just symlinks
+  # because otherwise the files do not show up in the sysroot.
+  definitionsDirectory = pkgs.runCommand "systemd-repart-definitions" { } ''
+    mkdir -p $out
+    ${(lib.concatStringsSep "\n"
+      (map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions)
+    )}
+  '';
+in
+{
+  options = {
+    boot.initrd.systemd.repart = {
+      enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+        description = lib.mdDoc ''
+          Grow and add partitions to a partition table at boot time in the initrd.
+          systemd-repart only works with GPT partition tables.
+
+          To run systemd-repart after the initrd, see
+          `options.systemd.repart.enable`.
+        '';
+      };
+
+      device = lib.mkOption {
+        type = with lib.types; nullOr str;
+        description = lib.mdDoc ''
+          The device to operate on.
+
+          If `device == null`, systemd-repart will operate on the device
+          backing the root partition. So in order to dynamically *create* the
+          root partition in the initrd you need to set a device.
+        '';
+        default = null;
+        example = "/dev/vda";
+      };
+    };
+
+    systemd.repart = {
+      enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
+        description = lib.mdDoc ''
+          Grow and add partitions to a partition table.
+          systemd-repart only works with GPT partition tables.
+
+          To run systemd-repart while in the initrd, see
+          `options.boot.initrd.systemd.repart.enable`.
+        '';
+      };
+
+      partitions = lib.mkOption {
+        type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ]));
+        default = { };
+        example = {
+          "10-root" = {
+            Type = "root";
+          };
+          "20-home" = {
+            Type = "home";
+            SizeMinBytes = "512M";
+            SizeMaxBytes = "2G";
+          };
+        };
+        description = lib.mdDoc ''
+          Specify partitions as a set of the names of the definition files as the
+          key and the partition configuration as its value. The partition
+          configuration can use all upstream options. See <link
+          xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/>
+          for all available options.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf (cfg.enable || initrdCfg.enable) {
+    boot.initrd.systemd = lib.mkIf initrdCfg.enable {
+      additionalUpstreamUnits = [
+        "systemd-repart.service"
+      ];
+
+      storePaths = [
+        "${config.boot.initrd.systemd.package}/bin/systemd-repart"
+      ];
+
+      contents."/etc/repart.d".source = definitionsDirectory;
+
+      # Override defaults in upstream unit.
+      services.systemd-repart =
+        let
+          deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device";
+        in
+        {
+          # systemd-repart tries to create directories in /var/tmp by default to
+          # store large temporary files that benefit from persistence on disk. In
+          # the initrd, however, /var/tmp does not provide more persistence than
+          # /tmp, so we re-use it here.
+          environment."TMPDIR" = "/tmp";
+          serviceConfig = {
+            ExecStart = [
+              " " # required to unset the previous value.
+              # When running in the initrd, systemd-repart by default searches
+              # for definition files in /sysroot or /sysusr. We tell it to look
+              # in the initrd itself.
+              ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
+                  --definitions=/etc/repart.d \
+                  --dry-run=no ${lib.optionalString (initrdCfg.device != null) initrdCfg.device}
+              ''
+            ];
+          };
+          # systemd-repart needs to run after /sysroot (or /sysuser, but we
+          # don't have it) has been mounted because otherwise it cannot
+          # determine the device (i.e disk) to operate on. If you want to run
+          # systemd-repart without /sysroot (i.e. to create the root
+          # partition), you have to explicitly tell it which device to operate
+          # on. The service then needs to be ordered to run after this device
+          # is available.
+          requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ];
+          after =
+            if initrdCfg.device == null then
+              [ "sysroot.mount" ]
+            else
+              [ deviceUnit ];
+        };
+    };
+
+    environment.etc = lib.mkIf cfg.enable {
+      "repart.d".source = definitionsDirectory;
+    };
+
+    systemd = lib.mkIf cfg.enable {
+      additionalUpstreamSystemUnits = [
+        "systemd-repart.service"
+      ];
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+}
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix b/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix
index cb257dce6f04..b4b750fa9aaf 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/shutdown.nix
@@ -9,7 +9,7 @@
 
 in {
   options.systemd.shutdownRamfs = {
-    enable = lib.mkEnableOption "pivoting back to an initramfs for shutdown" // { default = true; };
+    enable = lib.mkEnableOption (lib.mdDoc "pivoting back to an initramfs for shutdown") // { default = true; };
     contents = lib.mkOption {
       description = lib.mdDoc "Set of files that have to be linked into the shutdown ramfs";
       example = lib.literalExpression ''
@@ -33,26 +33,30 @@ in {
     systemd.shutdownRamfs.contents."/shutdown".source = "${config.systemd.package}/lib/systemd/systemd-shutdown";
     systemd.shutdownRamfs.storePaths = [pkgs.runtimeShell "${pkgs.coreutils}/bin"];
 
+    systemd.mounts = [{
+      what = "tmpfs";
+      where = "/run/initramfs";
+      type = "tmpfs";
+    }];
+
     systemd.services.generate-shutdown-ramfs = {
       description = "Generate shutdown ramfs";
       wantedBy = [ "shutdown.target" ];
       before = [ "shutdown.target" ];
       unitConfig = {
         DefaultDependencies = false;
+        RequiresMountsFor = "/run/initramfs";
         ConditionFileIsExecutable = [
           "!/run/initramfs/shutdown"
         ];
       };
 
-      path = [pkgs.util-linux pkgs.makeInitrdNGTool];
-      serviceConfig.Type = "oneshot";
-      script = ''
-        mkdir -p /run/initramfs
-        if ! mountpoint -q /run/initramfs; then
-          mount -t tmpfs tmpfs /run/initramfs
-        fi
-        make-initrd-ng ${ramfsContents} /run/initramfs
-      '';
+      serviceConfig = {
+        Type = "oneshot";
+        ProtectSystem = "strict";
+        ReadWritePaths = "/run/initramfs";
+        ExecStart = "${pkgs.makeInitrdNGTool}/bin/make-initrd-ng ${ramfsContents} /run/initramfs";
+      };
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix
index 5d94f89fe945..3065392b1d20 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -81,6 +81,7 @@ in
         # is handled by NixOS anyway.
         # ln -s "${systemd}/example/tmpfiles.d/home.conf"
         ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
+        ln -s "${systemd}/example/tmpfiles.d/portables.conf"
         ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
         ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
         ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/user.nix b/nixpkgs/nixos/modules/system/boot/systemd/user.nix
index 3200a58d73c9..92e1b087392d 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd/user.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd/user.nix
@@ -39,6 +39,20 @@ let
     "timers.target"
     "xdg-desktop-autostart.target"
   ] ++ config.systemd.additionalUpstreamUserUnits;
+
+  writeTmpfiles = { rules, user ? null }:
+    let
+      suffix = if user == null then "" else "-${user}";
+    in
+    pkgs.writeTextFile {
+      name = "nixos-user-tmpfiles.d${suffix}";
+      destination = "/etc/xdg/user-tmpfiles.d/00-nixos${suffix}.conf";
+      text = ''
+        # This file is created automatically and should not be modified.
+        # Please change the options ‘systemd.user.tmpfiles’ instead.
+        ${concatStringsSep "\n" rules}
+      '';
+    };
 in {
   options = {
     systemd.user.extraConfig = mkOption {
@@ -46,7 +60,7 @@ in {
       type = types.lines;
       example = "DefaultCPUAccounting=yes";
       description = lib.mdDoc ''
-        Extra config options for systemd user instances. See man systemd-user.conf for
+        Extra config options for systemd user instances. See {manpage}`systemd-user.conf(5)` for
         available options.
       '';
     };
@@ -93,11 +107,48 @@ in {
       description = lib.mdDoc "Definition of systemd per-user timer units.";
     };
 
+    systemd.user.tmpfiles = {
+      rules = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "D %C - - - 7d" ];
+        description = lib.mdDoc ''
+          Global user rules for creation, deletion and cleaning of volatile and
+          temporary files automatically. See
+          {manpage}`tmpfiles.d(5)`
+          for the exact format.
+        '';
+      };
+
+      users = mkOption {
+        description = mdDoc ''
+          Per-user rules for creation, deletion and cleaning of volatile and
+          temporary files automatically.
+        '';
+        default = {};
+        type = types.attrsOf (types.submodule {
+          options = {
+            rules = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "D %C - - - 7d" ];
+              description = mdDoc ''
+                Per-user rules for creation, deletion and cleaning of volatile and
+                temporary files automatically. See
+                {manpage}`tmpfiles.d(5)`
+                for the exact format.
+              '';
+            };
+          };
+        });
+      };
+    };
+
     systemd.additionalUpstreamUserUnits = mkOption {
       default = [];
       type = types.listOf types.str;
       example = [];
-      description = ''
+      description = lib.mdDoc ''
         Additional units shipped with systemd that should be enabled for per-user systemd instances.
       '';
       internal = true;
@@ -154,5 +205,30 @@ in {
     # Some overrides to upstream units.
     systemd.services."user@".restartIfChanged = false;
     systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
+
+    # enable systemd user tmpfiles
+    systemd.user.services.systemd-tmpfiles-setup.wantedBy =
+      optional
+        (cfg.tmpfiles.rules != [] || any (cfg': cfg'.rules != []) (attrValues cfg.tmpfiles.users))
+        "basic.target";
+
+    # /run/current-system/sw/etc/xdg is in systemd's $XDG_CONFIG_DIRS so we can
+    # write the tmpfiles.d rules for everyone there
+    environment.systemPackages =
+      optional
+        (cfg.tmpfiles.rules != [])
+        (writeTmpfiles { inherit (cfg.tmpfiles) rules; });
+
+    # /etc/profiles/per-user/$USER/etc/xdg is in systemd's $XDG_CONFIG_DIRS so
+    # we can write a single user's tmpfiles.d rules there
+    users.users =
+      mapAttrs
+        (user: cfg': {
+          packages = optional (cfg'.rules != []) (writeTmpfiles {
+            inherit (cfg') rules;
+            inherit user;
+          });
+        })
+        cfg.tmpfiles.users;
   };
 }
diff --git a/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix b/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix
new file mode 100644
index 000000000000..994aa3ca3b8c
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/systemd/userdbd.nix
@@ -0,0 +1,18 @@
+{ config, lib, ... }:
+
+let
+  cfg = config.services.userdbd;
+in
+{
+  options.services.userdbd.enable = lib.mkEnableOption (lib.mdDoc ''
+    Enables the systemd JSON user/group record lookup service
+  '');
+  config = lib.mkIf cfg.enable {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-userdbd.socket"
+      "systemd-userdbd.service"
+    ];
+
+    systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/boot/tmp.nix b/nixpkgs/nixos/modules/system/boot/tmp.nix
index 1f9431710aec..fd16cd3fba42 100644
--- a/nixpkgs/nixos/modules/system/boot/tmp.nix
+++ b/nixpkgs/nixos/modules/system/boot/tmp.nix
@@ -3,62 +3,67 @@
 with lib;
 
 let
-  cfg = config.boot;
+  cfg = config.boot.tmp;
 in
 {
-
-  ###### interface
+  imports = [
+    (mkRenamedOptionModule [ "boot" "cleanTmpDir" ] [ "boot" "tmp" "cleanOnBoot" ])
+    (mkRenamedOptionModule [ "boot" "tmpOnTmpfs" ] [ "boot" "tmp" "useTmpfs" ])
+    (mkRenamedOptionModule [ "boot" "tmpOnTmpfsSize" ] [ "boot" "tmp" "tmpfsSize" ])
+  ];
 
   options = {
+    boot.tmp = {
+      cleanOnBoot = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to delete all files in {file}`/tmp` during boot.
+        '';
+      };
 
-    boot.cleanTmpDir = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Whether to delete all files in {file}`/tmp` during boot.
-      '';
-    };
+      tmpfsSize = mkOption {
+        type = types.oneOf [ types.str types.types.ints.positive ];
+        default = "50%";
+        description = lib.mdDoc ''
+          Size of tmpfs in percentage.
+          Percentage is defined by systemd.
+        '';
+      };
 
-    boot.tmpOnTmpfs = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-         Whether to mount a tmpfs on {file}`/tmp` during boot.
-      '';
-    };
+      useTmpfs = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+           Whether to mount a tmpfs on {file}`/tmp` during boot.
 
-    boot.tmpOnTmpfsSize = mkOption {
-      type = types.oneOf [ types.str types.types.ints.positive ];
-      default = "50%";
-      description = lib.mdDoc ''
-        Size of tmpfs in percentage.
-        Percentage is defined by systemd.
-      '';
+           ::: {.note}
+           Large Nix builds can fail if the mounted tmpfs is not large enough.
+           In such a case either increase the tmpfsSize or disable this option.
+           :::
+        '';
+      };
     };
-
   };
 
-  ###### implementation
-
   config = {
-
     # When changing remember to update /tmp mount in virtualisation/qemu-vm.nix
-    systemd.mounts = mkIf cfg.tmpOnTmpfs [
+    systemd.mounts = mkIf cfg.useTmpfs [
       {
         what = "tmpfs";
         where = "/tmp";
         type = "tmpfs";
-        mountConfig.Options = concatStringsSep "," [ "mode=1777"
-                                                     "strictatime"
-                                                     "rw"
-                                                     "nosuid"
-                                                     "nodev"
-                                                     "size=${toString cfg.tmpOnTmpfsSize}" ];
+        mountConfig.Options = concatStringsSep "," [
+          "mode=1777"
+          "strictatime"
+          "rw"
+          "nosuid"
+          "nodev"
+          "size=${toString cfg.tmpfsSize}"
+        ];
       }
     ];
 
-    systemd.tmpfiles.rules = optional config.boot.cleanTmpDir "D! /tmp 1777 root root";
-
+    systemd.tmpfiles.rules = optional cfg.cleanOnBoot "D! /tmp 1777 root root";
   };
-
 }
diff --git a/nixpkgs/nixos/modules/system/boot/uvesafb.nix b/nixpkgs/nixos/modules/system/boot/uvesafb.nix
new file mode 100644
index 000000000000..b10dc42887a1
--- /dev/null
+++ b/nixpkgs/nixos/modules/system/boot/uvesafb.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.boot.uvesafb;
+  inherit (lib) mkIf mkEnableOption mkOption mdDoc types;
+in {
+  options = {
+    boot.uvesafb = {
+      enable = mkEnableOption (mdDoc "uvesafb");
+
+      gfx-mode = mkOption {
+        type = types.str;
+        default = "1024x768-32";
+        description = mdDoc "Screen resolution in modedb format. See [uvesafb](https://docs.kernel.org/fb/uvesafb.html) and [modedb](https://docs.kernel.org/fb/modedb.html) documentation for more details. The default value is a sensible default but may be not ideal for all setups.";
+      };
+
+      v86d.package = mkOption {
+        type = types.package;
+        description = mdDoc "Which v86d package to use with uvesafb";
+        defaultText = ''config.boot.kernelPackages.v86d.overrideAttrs (old: {
+          hardeningDisable = [ "all" ];
+        })'';
+        default = config.boot.kernelPackages.v86d.overrideAttrs (old: {
+          hardeningDisable = [ "all" ];
+        });
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    boot.initrd = {
+      kernelModules = [ "uvesafb" ];
+      extraFiles."/usr/v86d".source = cfg.v86d.package;
+    };
+
+    boot.kernelParams = [
+      "video=uvesafb:mode:${cfg.gfx-mode},mtrr:3,ywrap"
+      ''uvesafb.v86d="${cfg.v86d.package}/bin/v86d"''
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/system/etc/setup-etc.pl b/nixpkgs/nixos/modules/system/etc/setup-etc.pl
index be6b2d9ae71e..ea0a38308172 100644
--- a/nixpkgs/nixos/modules/system/etc/setup-etc.pl
+++ b/nixpkgs/nixos/modules/system/etc/setup-etc.pl
@@ -13,8 +13,12 @@ sub atomicSymlink {
     my $tmp = "$target.tmp";
     unlink $tmp;
     symlink $source, $tmp or return 0;
-    rename $tmp, $target or return 0;
-    return 1;
+    if (rename $tmp, $target) {
+        return 1;
+    } else {
+        unlink $tmp;
+        return 0;
+    }
 }
 
 
@@ -87,6 +91,12 @@ my @copied;
 
 sub link {
     my $fn = substr $File::Find::name, length($etc) + 1 or next;
+
+    # nixos-enter sets up /etc/resolv.conf as a bind mount, so skip it.
+    if ($fn eq "resolv.conf" and $ENV{'IN_NIXOS_ENTER'}) {
+        return;
+    }
+
     my $target = "/etc/$fn";
     File::Path::make_path(dirname $target);
     $created{$fn} = 1;
@@ -103,7 +113,7 @@ sub link {
     if (-e "$_.mode") {
         my $mode = read_file("$_.mode"); chomp $mode;
         if ($mode eq "direct-symlink") {
-            atomicSymlink readlink("$static/$fn"), $target or warn;
+            atomicSymlink readlink("$static/$fn"), $target or warn "could not create symlink $target";
         } else {
             my $uid = read_file("$_.uid"); chomp $uid;
             my $gid = read_file("$_.gid"); chomp $gid;
@@ -112,12 +122,15 @@ sub link {
             $gid = getgrnam $gid unless $gid =~ /^\+/;
             chown int($uid), int($gid), "$target.tmp" or warn;
             chmod oct($mode), "$target.tmp" or warn;
-            rename "$target.tmp", $target or warn;
+            unless (rename "$target.tmp", $target) {
+                warn "could not create target $target";
+                unlink "$target.tmp";
+            }
         }
         push @copied, $fn;
         print CLEAN "$fn\n";
     } elsif (-l "$_") {
-        atomicSymlink "$static/$fn", $target or warn;
+        atomicSymlink "$static/$fn", $target or warn "could not create symlink $target";
     }
 }
 
@@ -137,7 +150,7 @@ foreach my $fn (@oldCopied) {
 
 # Rewrite /etc/.clean.
 close CLEAN;
-write_file("/etc/.clean", map { "$_\n" } @copied);
+write_file("/etc/.clean", map { "$_\n" } sort @copied);
 
 # Create /etc/NIXOS tag if not exists.
 # When /etc is not on a persistent filesystem, it will be wiped after reboot,
diff --git a/nixpkgs/nixos/modules/tasks/auto-upgrade.nix b/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
index bfc5265518d2..29e3e313336f 100644
--- a/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
@@ -46,11 +46,11 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "https://nixos.org/channels/nixos-14.12-small";
-        description = ''
+        description = lib.mdDoc ''
           The URI of the NixOS channel to use for automatic
           upgrades. By default, this is the channel set using
-          <command>nix-channel</command> (run <literal>nix-channel
-          --list</literal> to see the current value).
+          {command}`nix-channel` (run `nix-channel --list`
+          to see the current value).
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/tasks/bcache.nix b/nixpkgs/nixos/modules/tasks/bcache.nix
index 0a13522de11f..408ddc02373f 100644
--- a/nixpkgs/nixos/modules/tasks/bcache.nix
+++ b/nixpkgs/nixos/modules/tasks/bcache.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 {
-  options.boot.initrd.services.bcache.enable = (lib.mkEnableOption "bcache support in the initrd") // {
+  options.boot.initrd.services.bcache.enable = (lib.mkEnableOption (lib.mdDoc "bcache support in the initrd")) // {
     visible = false; # only works with systemd stage 1
   };
 
diff --git a/nixpkgs/nixos/modules/tasks/filesystems.nix b/nixpkgs/nixos/modules/tasks/filesystems.nix
index 318740a44f4a..7cb2ca23fa41 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems.nix
@@ -33,7 +33,16 @@ let
       mountPoint = mkOption {
         example = "/mnt/usb";
         type = nonEmptyWithoutTrailingSlash;
-        description = lib.mdDoc "Location of the mounted the file system.";
+        description = lib.mdDoc "Location of the mounted file system.";
+      };
+
+      stratis.poolUuid = lib.mkOption {
+        type = types.uniq (types.nullOr types.str);
+        description = lib.mdDoc ''
+          UUID of the stratis pool that the fs is located in
+        '';
+        example = "04c68063-90a5-4235-b9dd-6180098a20d9";
+        default = null;
       };
 
       device = mkOption {
@@ -54,7 +63,7 @@ let
         default = [ "defaults" ];
         example = [ "data=journal" ];
         description = lib.mdDoc "Options used to mount the file system.";
-        type = types.listOf nonEmptyStr;
+        type = types.nonEmptyListOf nonEmptyStr;
       };
 
       depends = mkOption {
@@ -103,12 +112,9 @@ let
       };
 
       formatOptions = mkOption {
-        default = "";
-        type = types.str;
-        description = lib.mdDoc ''
-          If {option}`autoFormat` option is set specifies
-          extra options passed to mkfs.
-        '';
+        visible = false;
+        type = types.unspecified;
+        default = null;
       };
 
       autoResize = mkOption {
@@ -130,19 +136,11 @@ let
 
     };
 
-    config = let
-      defaultFormatOptions =
-        # -F needed to allow bare block device without partitions
-        if (builtins.substring 0 3 config.fsType) == "ext" then "-F"
-        # -q needed for non-interactive operations
-        else if config.fsType == "jfs" then "-q"
-        # (same here)
-        else if config.fsType == "reiserfs" then "-q"
-        else null;
-    in {
-      options = mkIf config.autoResize [ "x-nixos.autoresize" ];
-      formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions);
-    };
+    config.options = mkMerge [
+      (mkIf config.autoResize [ "x-systemd.growfs" ])
+      (mkIf config.autoFormat [ "x-systemd.makefs" ])
+      (mkIf (utils.fsNeededForBoot config) [ "x-initrd.mount" ])
+    ];
 
   };
 
@@ -153,6 +151,58 @@ let
       specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}"
     '') mounts);
 
+  makeFstabEntries =
+    let
+      fsToSkipCheck = [
+        "none"
+        "auto"
+        "overlay"
+        "iso9660"
+        "bindfs"
+        "udf"
+        "btrfs"
+        "zfs"
+        "tmpfs"
+        "bcachefs"
+        "nfs"
+        "nfs4"
+        "nilfs2"
+        "vboxsf"
+        "squashfs"
+        "glusterfs"
+        "apfs"
+        "9p"
+        "cifs"
+        "prl_fs"
+        "vmhgfs"
+      ] ++ lib.optionals (!config.boot.initrd.checkJournalingFS) [
+        "ext3"
+        "ext4"
+        "reiserfs"
+        "xfs"
+        "jfs"
+        "f2fs"
+      ];
+      isBindMount = fs: builtins.elem "bind" fs.options;
+      skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
+      # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
+      escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
+    in fstabFileSystems: { rootPrefix ? "" }: concatMapStrings (fs:
+      (optionalString (isBindMount fs) (escape rootPrefix))
+      + (if fs.device != null then escape fs.device
+         else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
+         else throw "No device specified for mount point ‘${fs.mountPoint}’.")
+      + " " + escape fs.mountPoint
+      + " " + fs.fsType
+      + " " + escape (builtins.concatStringsSep "," fs.options)
+      + " 0 " + (if skipCheck fs then "0" else if fs.mountPoint == "/" then "1" else "2")
+      + "\n"
+    ) fstabFileSystems;
+
+    initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) {
+      rootPrefix = "/sysroot";
+    });
+
 in
 
 {
@@ -175,28 +225,27 @@ in
         }
       '';
       type = types.attrsOf (types.submodule [coreFileSystemOpts fileSystemOpts]);
-      description = ''
+      description = lib.mdDoc ''
         The file systems to be mounted.  It must include an entry for
-        the root directory (<literal>mountPoint = "/"</literal>).  Each
+        the root directory (`mountPoint = "/"`).  Each
         entry in the list is an attribute set with the following fields:
-        <literal>mountPoint</literal>, <literal>device</literal>,
-        <literal>fsType</literal> (a file system type recognised by
-        <command>mount</command>; defaults to
-        <literal>"auto"</literal>), and <literal>options</literal>
-        (the mount options passed to <command>mount</command> using the
-        <option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>).
-
-        Instead of specifying <literal>device</literal>, you can also
-        specify a volume label (<literal>label</literal>) for file
-        systems that support it, such as ext2/ext3 (see <command>mke2fs
-        -L</command>).
+        `mountPoint`, `device`,
+        `fsType` (a file system type recognised by
+        {command}`mount`; defaults to
+        `"auto"`), and `options`
+        (the mount options passed to {command}`mount` using the
+        {option}`-o` flag; defaults to `[ "defaults" ]`).
+
+        Instead of specifying `device`, you can also
+        specify a volume label (`label`) for file
+        systems that support it, such as ext2/ext3 (see {command}`mke2fs -L`).
       '';
     };
 
     system.fsPackages = mkOption {
       internal = true;
       default = [ ];
-      description = "Packages supplying file system mounters and checkers.";
+      description = lib.mdDoc "Packages supplying file system mounters and checkers.";
     };
 
     boot.supportedFilesystems = mkOption {
@@ -210,7 +259,7 @@ in
       default = {};
       type = types.attrsOf (types.submodule coreFileSystemOpts);
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         Special filesystems that are mounted very early during boot.
       '';
     };
@@ -253,7 +302,13 @@ in
 
     assertions = let
       ls = sep: concatMapStringsSep sep (x: x.mountPoint);
-      notAutoResizable = fs: fs.autoResize && !(hasPrefix "ext" fs.fsType || fs.fsType == "f2fs");
+      resizableFSes = [
+        "ext3"
+        "ext4"
+        "btrfs"
+        "xfs"
+      ];
+      notAutoResizable = fs: fs.autoResize && !(builtins.elem fs.fsType resizableFSes);
     in [
       { assertion = ! (fileSystems' ? cycle);
         message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
@@ -261,8 +316,21 @@ in
       { assertion = ! (any notAutoResizable fileSystems);
         message = let
           fs = head (filter notAutoResizable fileSystems);
-        in
-          "Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = \"${fs.fsType}\"':${if fs.fsType == "auto" then " fsType has to be explicitly set and" else ""} only the ext filesystems and f2fs support it.";
+        in ''
+          Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = "${fs.fsType}"'
+          ${optionalString (fs.fsType == "auto") "fsType has to be explicitly set and"}
+          only the following support it: ${lib.concatStringsSep ", " resizableFSes}.
+        '';
+      }
+      {
+        assertion = ! (any (fs: fs.formatOptions != null) fileSystems);
+        message = let
+          fs = head (filter (fs: fs.formatOptions != null) fileSystems);
+        in ''
+          'fileSystems.<name>.formatOptions' has been removed, since
+          systemd-makefs does not support any way to provide formatting
+          options.
+        '';
       }
     ];
 
@@ -279,11 +347,6 @@ in
 
     environment.etc.fstab.text =
       let
-        fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" "apfs" "9p" "cifs" "prl_fs" "vmhgfs" ];
-        isBindMount = fs: builtins.elem "bind" fs.options;
-        skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
-        # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
-        escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
         swapOptions = sw: concatStringsSep "," (
           sw.options
           ++ optional (sw.priority != null) "pri=${toString sw.priority}"
@@ -298,18 +361,7 @@ in
         # <file system> <mount point>   <type>  <options>       <dump>  <pass>
 
         # Filesystems.
-        ${concatMapStrings (fs:
-            (if fs.device != null then escape fs.device
-             else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
-             else throw "No device specified for mount point ‘${fs.mountPoint}’.")
-            + " " + escape fs.mountPoint
-            + " " + fs.fsType
-            + " " + builtins.concatStringsSep "," fs.options
-            + " 0"
-            + " " + (if skipCheck fs then "0" else
-                     if fs.mountPoint == "/" then "1" else "2")
-            + "\n"
-        ) fileSystems}
+        ${makeFstabEntries fileSystems {}}
 
         # Swap devices.
         ${flip concatMapStrings config.swapDevices (sw:
@@ -317,43 +369,17 @@ in
         )}
       '';
 
+    boot.initrd.systemd.storePaths = [initrdFstab];
+    boot.initrd.systemd.managerEnvironment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
+    boot.initrd.systemd.services.initrd-parse-etc.environment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
+
     # Provide a target that pulls in all filesystems.
     systemd.targets.fs =
       { description = "All File Systems";
         wants = [ "local-fs.target" "remote-fs.target" ];
       };
 
-    systemd.services =
-
-    # Emit systemd services to format requested filesystems.
-      let
-        formatDevice = fs:
-          let
-            mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount";
-            device'  = escapeSystemdPath fs.device;
-            device'' = "${device'}.device";
-          in nameValuePair "mkfs-${device'}"
-          { description = "Initialisation of Filesystem ${fs.device}";
-            wantedBy = [ mountPoint' ];
-            before = [ mountPoint' "systemd-fsck@${device'}.service" ];
-            requires = [ device'' ];
-            after = [ device'' ];
-            path = [ pkgs.util-linux ] ++ config.system.fsPackages;
-            script =
-              ''
-                if ! [ -e "${fs.device}" ]; then exit 1; fi
-                # FIXME: this is scary.  The test could be more robust.
-                type=$(blkid -p -s TYPE -o value "${fs.device}" || true)
-                if [ -z "$type" ]; then
-                  echo "creating ${fs.fsType} filesystem on ${fs.device}..."
-                  mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}"
-                fi
-              '';
-            unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ];
-            unitConfig.DefaultDependencies = false; # needed to prevent a cycle
-            serviceConfig.Type = "oneshot";
-          };
-      in listToAttrs (map formatDevice (filter (fs: fs.autoFormat && !(utils.fsNeededForBoot fs)) fileSystems)) // {
+    systemd.services = {
     # Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
     # This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
         "mount-pstore" = {
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix b/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix
index ac41ba5f93a4..851c09781339 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -16,7 +16,7 @@ let
         local path="$2"
         if bcachefs unlock -c $path > /dev/null 2> /dev/null; then    # test for encryption
             prompt $name
-            until bcachefs unlock $path 2> /dev/null; do              # repeat until sucessfully unlocked
+            until bcachefs unlock $path 2> /dev/null; do              # repeat until successfully unlocked
                 printf "unlocking failed!\n"
                 prompt $name
             done
@@ -42,7 +42,11 @@ in
 {
   config = mkIf (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
     {
-      system.fsPackages = [ pkgs.bcachefs-tools ];
+      # We do not want to include bachefs in the fsPackages for systemd-initrd
+      # because we provide the unwrapped version of mount.bcachefs
+      # through the extraBin option, which will make it available for use.
+      system.fsPackages = lib.optional (!config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
+      environment.systemPackages = lib.optional (config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
 
       # use kernel package with bcachefs support until it's in mainline
       boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
@@ -52,7 +56,14 @@ in
       # chacha20 and poly1305 are required only for decryption attempts
       boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
 
-      boot.initrd.extraUtilsCommands = ''
+      boot.initrd.systemd.extraBin = {
+        "bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs";
+        "mount.bcachefs" = pkgs.runCommand "mount.bcachefs" {} ''
+          cp -pdv ${pkgs.bcachefs-tools}/bin/.mount.bcachefs.sh-wrapped $out
+        '';
+      };
+
+      boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
       '';
       boot.initrd.extraUtilsCommandsTest = ''
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix
index 32bfaba95c2e..82fdd6058710 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/btrfs.nix
@@ -19,13 +19,13 @@ in
     # One could also do regular btrfs balances, but that shouldn't be necessary
     # during normal usage and as long as the filesystems aren't filled near capacity
     services.btrfs.autoScrub = {
-      enable = mkEnableOption "regular btrfs scrub";
+      enable = mkEnableOption (lib.mdDoc "regular btrfs scrub");
 
       fileSystems = mkOption {
         type = types.listOf types.path;
         example = [ "/" ];
         description = lib.mdDoc ''
-          List of paths to btrfs filesystems to regularily call {command}`btrfs scrub` on.
+          List of paths to btrfs filesystems to regularly call {command}`btrfs scrub` on.
           Defaults to all mount points with btrfs filesystems.
           If you mount a filesystem multiple times or additionally mount subvolumes,
           you need to manually specify this list to avoid scrubbing multiple times.
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/envfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/envfs.nix
new file mode 100644
index 000000000000..365cb46ff2fe
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/envfs.nix
@@ -0,0 +1,60 @@
+{ pkgs, config, lib, ... }:
+
+let
+  cfg = config.services.envfs;
+  mounts = {
+    "/usr/bin" = {
+      device = "none";
+      fsType = "envfs";
+      options = [
+        "fallback-path=${pkgs.runCommand "fallback-path" {} (''
+          mkdir -p $out
+          ln -s ${config.environment.usrbinenv} $out/env
+          ln -s ${config.environment.binsh} $out/sh
+        '' + cfg.extraFallbackPathCommands)}"
+        "nofail"
+      ];
+    };
+    "/bin" = {
+      device = "/usr/bin";
+      fsType = "none";
+      options = [ "bind" "nofail" ];
+    };
+  };
+in {
+  options = {
+    services.envfs = {
+      enable = lib.mkEnableOption (lib.mdDoc "Envfs filesystem") // {
+        description = lib.mdDoc ''
+          Fuse filesystem that returns symlinks to executables based on the PATH
+          of the requesting process. This is useful to execute shebangs on NixOS
+          that assume hard coded locations in locations like /bin or /usr/bin
+          etc.
+        '';
+      };
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.envfs;
+        defaultText = lib.literalExpression "pkgs.envfs";
+        description = lib.mdDoc "Which package to use for the envfs.";
+      };
+
+      extraFallbackPathCommands = lib.mkOption {
+        type = lib.types.lines;
+        default = "";
+        example = "ln -s $''{pkgs.bash}/bin/bash $out/bash";
+        description = lib.mdDoc "Extra commands to run in the package that contains fallback executables in case not other executable is found";
+      };
+    };
+  };
+  config = lib.mkIf (cfg.enable) {
+    environment.systemPackages = [ cfg.package ];
+    # we also want these mounts in virtual machines.
+    fileSystems = if config.virtualisation ? qemu then lib.mkVMOverride mounts else mounts;
+
+    # We no longer need those when using envfs
+    system.activationScripts.usrbinenv = lib.mkForce "";
+    system.activationScripts.binsh = lib.mkForce "";
+  };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/erofs.nix b/nixpkgs/nixos/modules/tasks/filesystems/erofs.nix
new file mode 100644
index 000000000000..a3d657669350
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/filesystems/erofs.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inInitrd = lib.any (fs: fs == "erofs") config.boot.initrd.supportedFilesystems;
+  inSystem = lib.any (fs: fs == "erofs") config.boot.supportedFilesystems;
+
+in
+
+{
+  config = lib.mkIf (inInitrd || inSystem) {
+
+    system.fsPackages = [ pkgs.erofs-utils ];
+
+    boot.initrd.availableKernelModules = lib.mkIf inInitrd [ "erofs" ];
+
+    # fsck.erofs is currently experimental and should not be run as a
+    # privileged user. Thus, it is not included in the initrd.
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/ext.nix b/nixpkgs/nixos/modules/tasks/filesystems/ext.nix
index 9b61f21643ab..edc0efc55213 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/ext.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/ext.nix
@@ -3,13 +3,14 @@
 let
 
   inInitrd = lib.any (fs: fs == "ext2" || fs == "ext3" || fs == "ext4") config.boot.initrd.supportedFilesystems;
+  inSystem = lib.any (fs: fs == "ext2" || fs == "ext3" || fs == "ext4") config.boot.supportedFilesystems;
 
 in
 
 {
   config = {
 
-    system.fsPackages = lib.mkIf (config.boot.initrd.systemd.enable -> inInitrd) [ pkgs.e2fsprogs ];
+    system.fsPackages = lib.mkIf (config.boot.initrd.systemd.enable -> (inInitrd || inSystem)) [ pkgs.e2fsprogs ];
 
     # As of kernel 4.3, there is no separate ext3 driver (they're also handled by ext4.ko)
     boot.initrd.availableKernelModules = lib.mkIf (config.boot.initrd.systemd.enable -> inInitrd) [ "ext2" "ext4" ];
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix b/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix
index 1d52861aa39d..035784f43df8 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/f2fs.nix
@@ -15,11 +15,6 @@ in
 
     boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/fsck.f2fs
-      ${optionalString (any (fs: fs.autoResize) fileSystems) ''
-        # We need f2fs-tools' tools to resize filesystems
-        copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/resize.f2fs
-      ''}
-
     '';
   };
 }
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix
index 700f05af2bec..6d80c4c657da 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/jfs.nix
@@ -12,7 +12,7 @@ in
 
     boot.initrd.kernelModules = mkIf inInitrd [ "jfs" ];
 
-    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !boot.initrd.systemd.enable) ''
+    boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.jfsutils}/sbin/fsck.jfs
     '';
   };
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix b/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix
index 5baab1c802cf..5421b617b43b 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/vfat.nix
@@ -11,7 +11,7 @@ in
 {
   config = mkIf (any (fs: fs == "vfat") config.boot.supportedFilesystems) {
 
-    system.fsPackages = [ pkgs.dosfstools ];
+    system.fsPackages = [ pkgs.dosfstools pkgs.mtools ];
 
     boot.initrd.kernelModules = mkIf inInitrd [ "vfat" "nls_cp437" "nls_iso8859-1" ];
 
diff --git a/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
index 2ff6c1067c76..16dc0c44c18d 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
@@ -97,10 +97,15 @@ let
     in
       map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);
 
-  getKeyLocations = pool:
-    if isBool cfgZfs.requestEncryptionCredentials
-    then "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}"
-    else "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}";
+  getKeyLocations = pool: if isBool cfgZfs.requestEncryptionCredentials then {
+    hasKeys = cfgZfs.requestEncryptionCredentials;
+    command = "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}";
+  } else let
+    keys = filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials;
+  in {
+    hasKeys = keys != [];
+    command = "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString keys}";
+  };
 
   createImportService = { pool, systemd, force, prefix ? "" }:
     nameValuePair "zfs-import-${pool}" {
@@ -124,25 +129,26 @@ let
         RemainAfterExit = true;
       };
       environment.ZFS_FORCE = optionalString force "-f";
-      script = (importLib {
+      script = let
+        keyLocations = getKeyLocations pool;
+      in (importLib {
         # See comments at importLib definition.
         zpoolCmd = "${cfgZfs.package}/sbin/zpool";
         awkCmd = "${pkgs.gawk}/bin/awk";
         inherit cfgZfs;
       }) + ''
-        poolImported "${pool}" && exit
-        echo -n "importing ZFS pool \"${pool}\"..."
-        # Loop across the import until it succeeds, because the devices needed may not be discovered yet.
-        for trial in `seq 1 60`; do
-          poolReady "${pool}" && poolImport "${pool}" && break
-          sleep 1
-        done
-        poolImported "${pool}" || poolImport "${pool}"  # Try one last time, e.g. to import a degraded pool.
+        if ! poolImported "${pool}"; then
+          echo -n "importing ZFS pool \"${pool}\"..."
+          # Loop across the import until it succeeds, because the devices needed may not be discovered yet.
+          for trial in `seq 1 60`; do
+            poolReady "${pool}" && poolImport "${pool}" && break
+            sleep 1
+          done
+          poolImported "${pool}" || poolImport "${pool}"  # Try one last time, e.g. to import a degraded pool.
+        fi
         if poolImported "${pool}"; then
-          ${optionalString (if isBool cfgZfs.requestEncryptionCredentials
-                            then cfgZfs.requestEncryptionCredentials
-                            else cfgZfs.requestEncryptionCredentials != []) ''
-            ${getKeyLocations pool} | while IFS=$'\t' read ds kl ks; do
+          ${optionalString keyLocations.hasKeys ''
+            ${keyLocations.command} | while IFS=$'\t' read ds kl ks; do
               {
               if [[ "$ks" != unavailable ]]; then
                 continue
@@ -154,7 +160,7 @@ let
                   tries=3
                   success=false
                   while [[ $success != true ]] && [[ $tries -gt 0 ]]; do
-                    ${systemd}/bin/systemd-ask-password "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds" \
+                    ${systemd}/bin/systemd-ask-password --timeout=${toString cfgZfs.passwordTimeout} "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds" \
                       && success=true \
                       || tries=$((tries - 1))
                   done
@@ -209,7 +215,7 @@ in
         readOnly = true;
         type = types.bool;
         default = inInitrd || inSystem;
-        defaultText = literalDocBook "<literal>true</literal> if ZFS filesystem support is enabled";
+        defaultText = literalMD "`true` if ZFS filesystem support is enabled";
         description = lib.mdDoc "True if ZFS filesystem support is enabled";
       };
 
@@ -226,6 +232,15 @@ in
           '';
       };
 
+      allowHibernation = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Allow hibernation support, this may be a unsafe option depending on your
+          setup. Make sure to NOT use Swap on ZFS.
+        '';
+      };
+
       extraPools = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -277,12 +292,12 @@ in
       forceImportAll = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Forcibly import all ZFS pool(s).
 
-          If you set this option to <literal>false</literal> and NixOS subsequently fails to
+          If you set this option to `false` and NixOS subsequently fails to
           import your non-root ZFS pool(s), you should manually import each pool with
-          "zpool import -f &lt;pool-name&gt;", and then reboot. You should only need to do
+          "zpool import -f \<pool-name\>", and then reboot. You should only need to do
           this once.
         '';
       };
@@ -298,6 +313,16 @@ in
           an interactive prompt (keylocation=prompt) and from a file (keylocation=file://).
         '';
       };
+
+      passwordTimeout = mkOption {
+        type = types.int;
+        default = 0;
+        description = lib.mdDoc ''
+          Timeout in seconds to wait for password entry for decrypt at boot.
+
+          Defaults to 0, which waits forever.
+        '';
+      };
     };
 
     services.zfs.autoSnapshot = {
@@ -399,7 +424,7 @@ in
     };
 
     services.zfs.autoScrub = {
-      enable = mkEnableOption "periodic scrubbing of ZFS pools";
+      enable = mkEnableOption (lib.mdDoc "periodic scrubbing of ZFS pools");
 
       interval = mkOption {
         default = "Sun, 02:00";
@@ -426,7 +451,7 @@ in
       type = types.either (types.enum [ "disabled" "all" ]) (types.listOf types.str);
       default = "disabled";
       example = [ "tank" "dozer" ];
-      description = ''
+      description = lib.mdDoc ''
         After importing, expand each device in the specified pools.
 
         Set the value to the plain string "all" to expand all pools on boot:
@@ -440,7 +465,7 @@ in
     };
 
     services.zfs.zed = {
-      enableMail = mkEnableOption "ZED's ability to send emails" // {
+      enableMail = mkEnableOption (lib.mdDoc "ZED's ability to send emails") // {
         default = cfgZfs.package.enableMail;
         defaultText = literalExpression "config.${optZfs.package}.enableMail";
       };
@@ -494,10 +519,18 @@ in
           assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
           message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
         }
+        {
+          assertion = cfgZfs.allowHibernation -> !cfgZfs.forceImportRoot && !cfgZfs.forceImportAll;
+          message = "boot.zfs.allowHibernation while force importing is enabled will cause data corruption";
+        }
       ];
 
       boot = {
         kernelModules = [ "zfs" ];
+        # https://github.com/openzfs/zfs/issues/260
+        # https://github.com/openzfs/zfs/issues/12842
+        # https://github.com/NixOS/nixpkgs/issues/106093
+        kernelParams = lib.optionals (!config.boot.zfs.allowHibernation) [ "nohibernate" ];
 
         extraModulePackages = [
           (if config.boot.zfs.enableUnstable then
@@ -548,7 +581,7 @@ in
               ''
               else concatMapStrings (fs: ''
                 zfs load-key -- ${escapeShellArg fs}
-              '') cfgZfs.requestEncryptionCredentials}
+              '') (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}
         '') rootPools));
 
         # Systemd in stage 1
diff --git a/nixpkgs/nixos/modules/tasks/lvm.nix b/nixpkgs/nixos/modules/tasks/lvm.nix
index 2f98e3e7da5b..a14f26c02e48 100644
--- a/nixpkgs/nixos/modules/tasks/lvm.nix
+++ b/nixpkgs/nixos/modules/tasks/lvm.nix
@@ -5,23 +5,27 @@ let
   cfg = config.services.lvm;
 in {
   options.services.lvm = {
+    enable = mkEnableOption (lib.mdDoc "lvm2") // {
+      default = true;
+    };
+
     package = mkOption {
       type = types.package;
       default = pkgs.lvm2;
       internal = true;
       defaultText = literalExpression "pkgs.lvm2";
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to override the LVM package that's used on the system
         (udev rules, tmpfiles, systemd services).
         Defaults to pkgs.lvm2, pkgs.lvm2_dmeventd if dmeventd or pkgs.lvm2_vdo if vdo is enabled.
       '';
     };
-    dmeventd.enable = mkEnableOption "the LVM dmevent daemon";
-    boot.thin.enable = mkEnableOption "support for booting from ThinLVs";
-    boot.vdo.enable = mkEnableOption "support for booting from VDOLVs";
+    dmeventd.enable = mkEnableOption (lib.mdDoc "the LVM dmevent daemon");
+    boot.thin.enable = mkEnableOption (lib.mdDoc "support for booting from ThinLVs");
+    boot.vdo.enable = mkEnableOption (lib.mdDoc "support for booting from VDOLVs");
   };
 
-  options.boot.initrd.services.lvm.enable = (mkEnableOption "enable booting from LVM2 in the initrd") // {
+  options.boot.initrd.services.lvm.enable = (mkEnableOption (lib.mdDoc "enable booting from LVM2 in the initrd")) // {
     visible = false;
   };
 
@@ -30,7 +34,7 @@ in {
       # minimal configuration file to make lvmconfig/lvm2-activation-generator happy
       environment.etc."lvm/lvm.conf".text = "config {}";
     })
-    (mkIf (!config.boot.isContainer) {
+    (mkIf cfg.enable {
       systemd.tmpfiles.packages = [ cfg.package.out ];
       environment.systemPackages = [ cfg.package ];
       systemd.packages = [ cfg.package ];
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix b/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
index f44dafc9706a..24f0c37acf90 100644
--- a/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -293,7 +293,7 @@ let
             script = ''
               # Remove Dead Interfaces
               echo "Removing old bridge ${n}..."
-              ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link del "${n}"
 
               echo "Adding bridge ${n}..."
               ip link add name "${n}" type bridge
@@ -396,7 +396,7 @@ let
             '';
             postStop = ''
               echo "Cleaning Open vSwitch ${n}"
-              echo "Shuting down internal ${n} interface"
+              echo "Shutting down internal ${n} interface"
               ip link set ${n} down || true
               echo "Deleting flows for ${n}"
               ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
@@ -459,7 +459,7 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
               ip link add link "${v.interface}" name "${n}" type macvlan \
                 ${optionalString (v.mode != null) "mode ${v.mode}"}
               ip link set "${n}" up
@@ -517,7 +517,7 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
               ip link add name "${n}" type sit \
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
@@ -551,7 +551,7 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${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}\""} \
@@ -579,7 +579,7 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
               ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
 
               # We try to bring up the logical VLAN interface. If the master
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix b/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
index 1657fabcd9b1..dfa883a2c336 100644
--- a/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -28,11 +28,164 @@ let
     # TODO: warn the user that any address configured on those interfaces will be useless
     ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
 
+  domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
+  genericNetwork = override:
+    let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
+      ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
+        makeGateway = gateway: {
+          routeConfig = {
+            Gateway = gateway;
+            GatewayOnLink = false;
+          };
+        };
+    in optionalAttrs (gateway != [ ]) {
+      routes = override (map makeGateway gateway);
+    } // optionalAttrs (domains != [ ]) {
+      domains = override domains;
+    };
+
+  genericDhcpNetworks = initrd: mkIf cfg.useDHCP {
+    networks."99-ethernet-default-dhcp" = {
+      # We want to match physical ethernet interfaces as commonly
+      # found on laptops, desktops and servers, to provide an
+      # "out-of-the-box" setup that works for common cases.  This
+      # heuristic isn't perfect (it could match interfaces with
+      # custom names that _happen_ to start with en or eth), but
+      # should be good enough to make the common case easy and can
+      # be overridden on a case-by-case basis using
+      # higher-priority networks or by disabling useDHCP.
+
+      # Type=ether matches veth interfaces as well, and this is
+      # more likely to result in interfaces being configured to
+      # use DHCP when they shouldn't.
+
+      # When wait-online.anyInterface is enabled, RequiredForOnline really
+      # means "sufficient for online", so we can enable it.
+      # Otherwise, don't block the network coming online because of default networks.
+      matchConfig.Name = ["en*" "eth*"];
+      DHCP = "yes";
+      linkConfig.RequiredForOnline =
+        lib.mkDefault (if initrd
+        then config.boot.initrd.systemd.network.wait-online.anyInterface
+        else config.systemd.network.wait-online.anyInterface);
+      networkConfig.IPv6PrivacyExtensions = "kernel";
+    };
+    networks."99-wireless-client-dhcp" = {
+      # Like above, but this is much more likely to be correct.
+      matchConfig.WLANInterfaceType = "station";
+      DHCP = "yes";
+      linkConfig.RequiredForOnline =
+        lib.mkDefault config.systemd.network.wait-online.anyInterface;
+      networkConfig.IPv6PrivacyExtensions = "kernel";
+      # We also set the route metric to one more than the default
+      # of 1024, so that Ethernet is preferred if both are
+      # available.
+      dhcpV4Config.RouteMetric = 1025;
+      ipv6AcceptRAConfig.RouteMetric = 1025;
+    };
+  };
+
+
+  interfaceNetworks = mkMerge (forEach interfaces (i: {
+    netdevs = mkIf i.virtual ({
+      "40-${i.name}" = {
+        netdevConfig = {
+          Name = i.name;
+          Kind = i.virtualType;
+        };
+        "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
+          User = i.virtualOwner;
+        };
+      };
+    });
+    networks."40-${i.name}" = mkMerge [ (genericNetwork id) {
+      name = mkDefault i.name;
+      DHCP = mkForce (dhcpStr
+        (if i.useDHCP != null then i.useDHCP else false));
+      address = forEach (interfaceIps i)
+        (ip: "${ip.address}/${toString ip.prefixLength}");
+      routes = forEach (interfaceRoutes i)
+        (route: {
+          # Most of these route options have not been tested.
+          # Please fix or report any mistakes you may find.
+          routeConfig =
+            optionalAttrs (route.address != null && route.prefixLength != null) {
+              Destination = "${route.address}/${toString route.prefixLength}";
+            } //
+            optionalAttrs (route.options ? fastopen_no_cookie) {
+              FastOpenNoCookie = route.options.fastopen_no_cookie;
+            } //
+            optionalAttrs (route.via != null) {
+              Gateway = route.via;
+            } //
+            optionalAttrs (route.type != null) {
+              Type = route.type;
+            } //
+            optionalAttrs (route.options ? onlink) {
+              GatewayOnLink = true;
+            } //
+            optionalAttrs (route.options ? initrwnd) {
+              InitialAdvertisedReceiveWindow = route.options.initrwnd;
+            } //
+            optionalAttrs (route.options ? initcwnd) {
+              InitialCongestionWindow = route.options.initcwnd;
+            } //
+            optionalAttrs (route.options ? pref) {
+              IPv6Preference = route.options.pref;
+            } //
+            optionalAttrs (route.options ? mtu) {
+              MTUBytes = route.options.mtu;
+            } //
+            optionalAttrs (route.options ? metric) {
+              Metric = route.options.metric;
+            } //
+            optionalAttrs (route.options ? src) {
+              PreferredSource = route.options.src;
+            } //
+            optionalAttrs (route.options ? protocol) {
+              Protocol = route.options.protocol;
+            } //
+            optionalAttrs (route.options ? quickack) {
+              QuickAck = route.options.quickack;
+            } //
+            optionalAttrs (route.options ? scope) {
+              Scope = route.options.scope;
+            } //
+            optionalAttrs (route.options ? from) {
+              Source = route.options.from;
+            } //
+            optionalAttrs (route.options ? table) {
+              Table = route.options.table;
+            } //
+            optionalAttrs (route.options ? advmss) {
+              TCPAdvertisedMaximumSegmentSize = route.options.advmss;
+            } //
+            optionalAttrs (route.options ? ttl-propagate) {
+              TTLPropagate = route.options.ttl-propagate == "enabled";
+            };
+        });
+      networkConfig.IPv6PrivacyExtensions = "kernel";
+      linkConfig = optionalAttrs (i.macAddress != null) {
+        MACAddress = i.macAddress;
+      } // optionalAttrs (i.mtu != null) {
+        MTUBytes = toString i.mtu;
+      };
+    }];
+  }));
+
 in
 
 {
+  config = mkMerge [
 
-  config = mkIf cfg.useNetworkd {
+  (mkIf config.boot.initrd.network.enable {
+    # Note this is if initrd.network.enable, not if
+    # initrd.systemd.network.enable. By setting the latter and not the
+    # former, the user retains full control over the configuration.
+    boot.initrd.systemd.network = mkMerge [(genericDhcpNetworks true) interfaceNetworks];
+  })
+
+  (mkIf cfg.useNetworkd {
 
     assertions = [ {
       assertion = cfg.defaultGatewayWindowSize == null;
@@ -54,149 +207,11 @@ in
     networking.dhcpcd.enable = mkDefault false;
 
     systemd.network =
-      let
-        domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
-        genericNetwork = override:
-          let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
-            ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
-              makeGateway = gateway: {
-                routeConfig = {
-                  Gateway = gateway;
-                  GatewayOnLink = false;
-                };
-              };
-          in optionalAttrs (gateway != [ ]) {
-            routes = override (map makeGateway gateway);
-          } // optionalAttrs (domains != [ ]) {
-            domains = override domains;
-          };
-      in mkMerge [ {
+      mkMerge [ {
         enable = true;
       }
-      (mkIf cfg.useDHCP {
-        networks."99-ethernet-default-dhcp" = lib.mkIf cfg.useDHCP {
-          # We want to match physical ethernet interfaces as commonly
-          # found on laptops, desktops and servers, to provide an
-          # "out-of-the-box" setup that works for common cases.  This
-          # heuristic isn't perfect (it could match interfaces with
-          # custom names that _happen_ to start with en or eth), but
-          # should be good enough to make the common case easy and can
-          # be overridden on a case-by-case basis using
-          # higher-priority networks or by disabling useDHCP.
-
-          # Type=ether matches veth interfaces as well, and this is
-          # more likely to result in interfaces being configured to
-          # use DHCP when they shouldn't.
-
-          # When wait-online.anyInterface is enabled, RequiredForOnline really
-          # means "sufficient for online", so we can enable it.
-          # Otherwise, don't block the network coming online because of default networks.
-          matchConfig.Name = ["en*" "eth*"];
-          DHCP = "yes";
-          linkConfig.RequiredForOnline =
-            lib.mkDefault config.systemd.network.wait-online.anyInterface;
-          networkConfig.IPv6PrivacyExtensions = "kernel";
-        };
-        networks."99-wireless-client-dhcp" = lib.mkIf cfg.useDHCP {
-          # Like above, but this is much more likely to be correct.
-          matchConfig.WLANInterfaceType = "station";
-          DHCP = "yes";
-          linkConfig.RequiredForOnline =
-            lib.mkDefault config.systemd.network.wait-online.anyInterface;
-          networkConfig.IPv6PrivacyExtensions = "kernel";
-          # We also set the route metric to one more than the default
-          # of 1024, so that Ethernet is preferred if both are
-          # available.
-          dhcpV4Config.RouteMetric = 1025;
-          ipv6AcceptRAConfig.RouteMetric = 1025;
-        };
-      })
-      (mkMerge (forEach interfaces (i: {
-        netdevs = mkIf i.virtual ({
-          "40-${i.name}" = {
-            netdevConfig = {
-              Name = i.name;
-              Kind = i.virtualType;
-            };
-            "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
-              User = i.virtualOwner;
-            };
-          };
-        });
-        networks."40-${i.name}" = mkMerge [ (genericNetwork id) {
-          name = mkDefault i.name;
-          DHCP = mkForce (dhcpStr
-            (if i.useDHCP != null then i.useDHCP else false));
-          address = forEach (interfaceIps i)
-            (ip: "${ip.address}/${toString ip.prefixLength}");
-          routes = forEach (interfaceRoutes i)
-            (route: {
-              # Most of these route options have not been tested.
-              # Please fix or report any mistakes you may find.
-              routeConfig =
-                optionalAttrs (route.prefixLength > 0) {
-                  Destination = "${route.address}/${toString route.prefixLength}";
-                } //
-                optionalAttrs (route.options ? fastopen_no_cookie) {
-                  FastOpenNoCookie = route.options.fastopen_no_cookie;
-                } //
-                optionalAttrs (route.via != null) {
-                  Gateway = route.via;
-                } //
-                optionalAttrs (route.type != null) {
-                  Type = route.type;
-                } //
-                optionalAttrs (route.options ? onlink) {
-                  GatewayOnLink = true;
-                } //
-                optionalAttrs (route.options ? initrwnd) {
-                  InitialAdvertisedReceiveWindow = route.options.initrwnd;
-                } //
-                optionalAttrs (route.options ? initcwnd) {
-                  InitialCongestionWindow = route.options.initcwnd;
-                } //
-                optionalAttrs (route.options ? pref) {
-                  IPv6Preference = route.options.pref;
-                } //
-                optionalAttrs (route.options ? mtu) {
-                  MTUBytes = route.options.mtu;
-                } //
-                optionalAttrs (route.options ? metric) {
-                  Metric = route.options.metric;
-                } //
-                optionalAttrs (route.options ? src) {
-                  PreferredSource = route.options.src;
-                } //
-                optionalAttrs (route.options ? protocol) {
-                  Protocol = route.options.protocol;
-                } //
-                optionalAttrs (route.options ? quickack) {
-                  QuickAck = route.options.quickack;
-                } //
-                optionalAttrs (route.options ? scope) {
-                  Scope = route.options.scope;
-                } //
-                optionalAttrs (route.options ? from) {
-                  Source = route.options.from;
-                } //
-                optionalAttrs (route.options ? table) {
-                  Table = route.options.table;
-                } //
-                optionalAttrs (route.options ? advmss) {
-                  TCPAdvertisedMaximumSegmentSize = route.options.advmss;
-                } //
-                optionalAttrs (route.options ? ttl-propagate) {
-                  TTLPropagate = route.options.ttl-propagate == "enabled";
-                };
-            });
-          networkConfig.IPv6PrivacyExtensions = "kernel";
-          linkConfig = optionalAttrs (i.macAddress != null) {
-            MACAddress = i.macAddress;
-          } // optionalAttrs (i.mtu != null) {
-            MTUBytes = toString i.mtu;
-          };
-        }];
-      })))
+      (genericDhcpNetworks false)
+      interfaceNetworks
       (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
         netdevs."40-${name}" = {
           netdevConfig = {
@@ -422,7 +437,7 @@ in
             '';
             postStop = ''
               echo "Cleaning Open vSwitch ${n}"
-              echo "Shuting down internal ${n} interface"
+              echo "Shutting down internal ${n} interface"
               ip link set ${n} down || true
               echo "Deleting flows for ${n}"
               ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
@@ -437,6 +452,7 @@ in
               bindsTo = [ "systemd-networkd.service" ];
           };
       };
-  };
+  })
 
+  ];
 }
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces.nix b/nixpkgs/nixos/modules/tasks/network-interfaces.nix
index fabddece376b..954d7ffba71d 100644
--- a/nixpkgs/nixos/modules/tasks/network-interfaces.nix
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces.nix
@@ -172,13 +172,13 @@ let
         type = types.enum (lib.attrNames tempaddrValues);
         default = cfg.tempAddresses;
         defaultText = literalExpression ''config.networking.tempAddresses'';
-        description = ''
+        description = lib.mdDoc ''
           When IPv6 is enabled with SLAAC, this option controls the use of
           temporary address (aka privacy extensions) on this
           interface. This is used to reduce tracking.
 
           See also the global option
-          <xref linkend="opt-networking.tempAddresses"/>, which
+          [](#opt-networking.tempAddresses), which
           applies to all interfaces where this is not set.
 
           Possible values are:
@@ -227,15 +227,19 @@ let
           { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
         ];
         type = with types; listOf (submodule (routeOpts 4));
-        description = ''
+        description = lib.mdDoc ''
           List of extra IPv4 static routes that will be assigned to the interface.
-          <warning><para>If the route type is the default <literal>unicast</literal>, then the scope
-          is set differently depending on the value of <option>networking.useNetworkd</option>:
-          the script-based backend sets it to <literal>link</literal>, while networkd sets
-          it to <literal>global</literal>.</para></warning>
+
+          ::: {.warning}
+          If the route type is the default `unicast`, then the scope
+          is set differently depending on the value of {option}`networking.useNetworkd`:
+          the script-based backend sets it to `link`, while networkd sets
+          it to `global`.
+          :::
+
           If you want consistency between the two implementations,
           set the scope of the route manually with
-          <literal>networking.interfaces.eth0.ipv4.routes = [{ options.scope = "global"; }]</literal>
+          `networking.interfaces.eth0.ipv4.routes = [{ options.scope = "global"; }]`
           for example.
         '';
       };
@@ -407,17 +411,10 @@ let
       description = "generate IPv6 temporary addresses and use these as source addresses in routing";
     };
   };
-  tempaddrDoc = ''
-    <itemizedlist>
-     ${concatStringsSep "\n" (mapAttrsToList (name: { description, ... }: ''
-       <listitem>
-         <para>
-           <literal>"${name}"</literal> to ${description};
-         </para>
-       </listitem>
-     '') tempaddrValues)}
-    </itemizedlist>
-  '';
+  tempaddrDoc = concatStringsSep "\n"
+    (mapAttrsToList
+      (name: { description, ... }: ''- `"${name}"` to ${description};'')
+      tempaddrValues);
 
   hostidFile = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } ''
       hi="${cfg.hostId}"
@@ -437,7 +434,8 @@ in
   options = {
 
     networking.hostName = mkOption {
-      default = "nixos";
+      default = config.system.nixos.distroId;
+      defaultText = literalExpression "config.system.nixos.distroId";
       # Only allow hostnames without the domain name part (i.e. no FQDNs, see
       # e.g. "man 5 hostname") and require valid DNS labels (recommended
       # syntax). Note: We also allow underscores for compatibility/legacy
@@ -476,9 +474,29 @@ in
       defaultText = literalExpression ''"''${networking.hostName}.''${networking.domain}"'';
       description = lib.mdDoc ''
         The fully qualified domain name (FQDN) of this host. It is the result
-        of combining networking.hostName and networking.domain. Using this
+        of combining `networking.hostName` and `networking.domain.` Using this
         option will result in an evaluation error if the hostname is empty or
         no domain is specified.
+
+        Modules that accept a mere `networing.hostName` but prefer a fully qualified
+        domain name may use `networking.fqdnOrHostName` instead.
+      '';
+    };
+
+    networking.fqdnOrHostName = mkOption {
+      readOnly = true;
+      type = types.str;
+      default = if cfg.domain == null then cfg.hostName else cfg.fqdn;
+      defaultText = literalExpression ''
+        if cfg.domain == null then cfg.hostName else cfg.fqdn
+      '';
+      description = lib.mdDoc ''
+        Either the fully qualified domain name (FQDN), or just the host name if
+        it does not exists.
+
+        This is a convenience option for modules to read instead of `fqdn` when
+        a mere `hostName` is also an acceptable value; this option does not
+        throw an error when `domain` is unset.
       '';
     };
 
@@ -607,6 +625,10 @@ in
         The configuration for each network interface.  If
         {option}`networking.useDHCP` is true, then every
         interface not listed here will be configured using DHCP.
+
+        Please note that {option}`systemd.network.netdevs` has more features
+        and is better maintained. When building new things, it is advised to
+        use that instead.
       '';
       type = with types; attrsOf (submodule interfaceOpts);
     };
@@ -785,7 +807,7 @@ in
               default = null;
               example = "fast";
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 DEPRECATED, use `driverOptions`.
                 Option specifying the rate in which we'll ask our link partner
                 to transmit LACPDU packets in 802.3ad mode.
@@ -796,7 +818,7 @@ in
               default = null;
               example = 100;
               type = types.nullOr types.int;
-              description = ''
+              description = lib.mdDoc ''
                 DEPRECATED, use `driverOptions`.
                 Miimon is the number of millisecond in between each round of polling
                 by the device driver for failed links. By default polling is not
@@ -809,7 +831,7 @@ in
               default = null;
               example = "active-backup";
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 DEPRECATED, use `driverOptions`.
                 The mode which the bond will be running. The default mode for
                 the bonding driver is balance-rr, optimizing for throughput.
@@ -822,7 +844,7 @@ in
               default = null;
               example = "layer2+3";
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 DEPRECATED, use `driverOptions`.
                 Selects the transmit hash policy to use for slave selection in
                 balance-xor, 802.3ad, and tlb modes.
@@ -1241,17 +1263,15 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "02:00:00:00:00:01";
-            description = ''
-              MAC address to use for the device. If <literal>null</literal>, then the MAC of the
+            description = lib.mdDoc ''
+              MAC address to use for the device. If `null`, then the MAC of the
               underlying hardware WLAN device is used.
 
               INFO: Locally administered MAC addresses are of the form:
-              <itemizedlist>
-              <listitem><para>x2:xx:xx:xx:xx:xx</para></listitem>
-              <listitem><para>x6:xx:xx:xx:xx:xx</para></listitem>
-              <listitem><para>xA:xx:xx:xx:xx:xx</para></listitem>
-              <listitem><para>xE:xx:xx:xx:xx:xx</para></listitem>
-              </itemizedlist>
+              - x2:xx:xx:xx:xx:xx
+              - x6:xx:xx:xx:xx:xx
+              - xA:xx:xx:xx:xx:xx
+              - xE:xx:xx:xx:xx:xx
             '';
           };
 
@@ -1287,10 +1307,10 @@ in
         if ''${config.${opt.enableIPv6}} then "default" else "disabled"
       '';
       type = types.enum (lib.attrNames tempaddrValues);
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable IPv6 Privacy Extensions for interfaces not
         configured explicitly in
-        <xref linkend="opt-networking.interfaces._name_.tempAddress"/>.
+        [](#opt-networking.interfaces._name_.tempAddress).
 
         This sets the ipv6.conf.*.use_tempaddr sysctl for all
         interfaces. Possible values are:
@@ -1357,13 +1377,13 @@ in
       "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
+        (i: nameValuePair "net.ipv4.conf.${replaceStrings ["."] ["/"] i.name}.proxy_arp" i.proxyARP))
       // listToAttrs (forEach interfaces
         (i: let
           opt = i.tempAddress;
           val = tempaddrValues.${opt}.sysctl;
-         in nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" val));
+         in nameValuePair "net.ipv6.conf.${replaceStrings ["."] ["/"] i.name}.use_tempaddr" val));
 
     security.wrappers = {
       ping = {
@@ -1392,9 +1412,10 @@ in
     # Set the host and domain names in the activation script.  Don't
     # clear it if it's not configured in the NixOS configuration,
     # since it may have been set by dhcpcd in the meantime.
-    system.activationScripts.hostname =
-      optionalString (cfg.hostName != "") ''
-        hostname "${cfg.hostName}"
+    system.activationScripts.hostname = let
+        effectiveHostname = config.boot.kernel.sysctl."kernel.hostname" or cfg.hostName;
+      in optionalString (effectiveHostname != "") ''
+        hostname "${effectiveHostname}"
       '';
     system.activationScripts.domain =
       optionalString (cfg.domain != null) ''
@@ -1475,7 +1496,7 @@ in
           in
           ''
             # override to ${msg} for ${i.name}
-            ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${val}"
+            ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceStrings ["."] ["/"] i.name}.use_tempaddr=${val}"
           '') (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
       })
     ] ++ lib.optional (cfg.wlanInterfaces != {})
diff --git a/nixpkgs/nixos/modules/tasks/powertop.nix b/nixpkgs/nixos/modules/tasks/powertop.nix
index e8064f9fa80c..3839b7a4260e 100644
--- a/nixpkgs/nixos/modules/tasks/powertop.nix
+++ b/nixpkgs/nixos/modules/tasks/powertop.nix
@@ -7,7 +7,7 @@ let
 in {
   ###### interface
 
-  options.powerManagement.powertop.enable = mkEnableOption "powertop auto tuning on startup";
+  options.powerManagement.powertop.enable = mkEnableOption (lib.mdDoc "powertop auto tuning on startup");
 
   ###### implementation
 
diff --git a/nixpkgs/nixos/modules/tasks/snapraid.nix b/nixpkgs/nixos/modules/tasks/snapraid.nix
index 634ffa031113..243d25f88423 100644
--- a/nixpkgs/nixos/modules/tasks/snapraid.nix
+++ b/nixpkgs/nixos/modules/tasks/snapraid.nix
@@ -6,7 +6,7 @@ let cfg = config.snapraid;
 in
 {
   options.snapraid = with types; {
-    enable = mkEnableOption "SnapRAID";
+    enable = mkEnableOption (lib.mdDoc "SnapRAID");
     dataDisks = mkOption {
       default = { };
       example = {
diff --git a/nixpkgs/nixos/modules/tasks/stratis.nix b/nixpkgs/nixos/modules/tasks/stratis.nix
new file mode 100644
index 000000000000..9a85fe23f248
--- /dev/null
+++ b/nixpkgs/nixos/modules/tasks/stratis.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.stratis;
+in
+{
+  options.services.stratis = {
+    enable = lib.mkEnableOption (lib.mdDoc "Stratis Storage - Easy to use local storage management for Linux");
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.stratis-cli ];
+    systemd.packages = [ pkgs.stratisd ];
+    services.dbus.packages = [ pkgs.stratisd ];
+    services.udev.packages = [ pkgs.stratisd ];
+    systemd.services.stratisd.wantedBy = [ "sysinit.target" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/tasks/swraid.nix b/nixpkgs/nixos/modules/tasks/swraid.nix
index 26d6bd914d5e..7832bbf92016 100644
--- a/nixpkgs/nixos/modules/tasks/swraid.nix
+++ b/nixpkgs/nixos/modules/tasks/swraid.nix
@@ -5,7 +5,7 @@
 in {
 
   options.boot.initrd.services.swraid = {
-    enable = (lib.mkEnableOption "swraid support using mdadm") // {
+    enable = (lib.mkEnableOption (lib.mdDoc "swraid support using mdadm")) // {
       visible = false; # only has effect when the new stage 1 is in place
     };
 
diff --git a/nixpkgs/nixos/modules/testing/minimal-kernel.nix b/nixpkgs/nixos/modules/testing/minimal-kernel.nix
deleted file mode 100644
index 7c2b9c05cf9a..000000000000
--- a/nixpkgs/nixos/modules/testing/minimal-kernel.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  configfile = builtins.storePath (builtins.toFile "config" (lib.concatStringsSep "\n"
-    (map (builtins.getAttr "configLine") config.system.requiredKernelConfig))
-  );
-
-  origKernel = pkgs.buildLinux {
-    inherit (pkgs.linux) src version stdenv;
-    inherit configfile;
-    allowImportFromDerivation = true;
-    kernelPatches = [ pkgs.kernelPatches.cifs_timeout_2_6_38 ];
-  };
-
-  kernel = origKernel // (derivation (origKernel.drvAttrs // {
-    configurePhase = ''
-      runHook preConfigure
-      mkdir ../build
-      make $makeFlags "''${makeFlagsArray[@]}" mrproper
-      make $makeFlags "''${makeFlagsArray[@]}" KCONFIG_ALLCONFIG=${configfile} allnoconfig
-      runHook postConfigure
-    '';
-  }));
-
-   kernelPackages = pkgs.linuxPackagesFor kernel;
-in {
-  boot.kernelPackages = kernelPackages;
-}
diff --git a/nixpkgs/nixos/modules/testing/service-runner.nix b/nixpkgs/nixos/modules/testing/service-runner.nix
index 9060be3cca11..bdb35f128a73 100644
--- a/nixpkgs/nixos/modules/testing/service-runner.nix
+++ b/nixpkgs/nixos/modules/testing/service-runner.nix
@@ -107,7 +107,7 @@ let
   opts = { config, name, ... }: {
     options.runner = mkOption {
     internal = true;
-    description = ''
+    description = lib.mdDoc ''
         A script that runs the service outside of systemd,
         useful for testing or for using NixOS services outside
         of NixOS.
diff --git a/nixpkgs/nixos/modules/testing/test-instrumentation.nix b/nixpkgs/nixos/modules/testing/test-instrumentation.nix
index 4ab2578eb81e..67fdc0ea4335 100644
--- a/nixpkgs/nixos/modules/testing/test-instrumentation.nix
+++ b/nixpkgs/nixos/modules/testing/test-instrumentation.nix
@@ -36,8 +36,16 @@ in
             while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
             echo "connecting to host..." >&2
             stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
-            echo
-            PS1= exec /bin/sh
+            # The following line is essential since it signals to
+            # the test driver that the shell is ready.
+            # See: the connect method in the Machine class.
+            echo "Spawning backdoor root shell..."
+            # Passing the terminal device makes bash run non-interactively.
+            # Otherwise we get errors on the terminal because bash tries to
+            # setup things like job control.
+            # Note: calling bash explicitly here instead of sh makes sure that
+            # we can also run non-NixOS guests during tests.
+            PS1= exec /usr/bin/env bash --norc /dev/hvc0
           '';
         serviceConfig.KillSignal = "SIGHUP";
       };
@@ -96,6 +104,12 @@ in
         MaxLevelConsole=debug
       '';
 
+    boot.initrd.systemd.contents."/etc/systemd/journald.conf".text = ''
+      [Journal]
+      ForwardToConsole=yes
+      MaxLevelConsole=debug
+    '';
+
     systemd.extraConfig = ''
       # Don't clobber the console with duplicate systemd messages.
       ShowStatus=no
@@ -107,6 +121,8 @@ in
       DefaultTimeoutStartSec=300
     '';
 
+    boot.initrd.systemd.extraConfig = config.systemd.extraConfig;
+
     boot.consoleLogLevel = 7;
 
     # Prevent tests from accessing the Internet.
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix b/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix
index 0324c5332cff..ff88f02e5d33 100644
--- a/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix
@@ -488,5 +488,101 @@ let self = {
   "22.05".us-west-1.aarch64-linux.hvm-ebs = "ami-0f96be48071c13ab2";
   "22.05".us-west-2.aarch64-linux.hvm-ebs = "ami-084bc5d777585adfb";
 
-  latest = self."22.05";
+  # 22.11.466.596a8e828c5
+
+  "22.11".eu-west-1.x86_64-linux.hvm-ebs = "ami-01aafe08a4e74bd9a";
+  "22.11".af-south-1.x86_64-linux.hvm-ebs = "ami-0d937fc7bf7b8c2ed";
+  "22.11".ap-east-1.x86_64-linux.hvm-ebs = "ami-020e59f6affef2732";
+  "22.11".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-04a7bd7a969506a87";
+  "22.11".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-007b9209171e2dcdd";
+  "22.11".ap-northeast-3.x86_64-linux.hvm-ebs = "ami-0c4d0b584cd570584";
+  "22.11".ap-south-1.x86_64-linux.hvm-ebs = "ami-02aa47f84c215d593";
+  "22.11".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-067a7fca4a01c4dda";
+  "22.11".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0638db75ba113c635";
+  "22.11".ap-southeast-3.x86_64-linux.hvm-ebs = "ami-08dcda749c59e8747";
+  "22.11".ca-central-1.x86_64-linux.hvm-ebs = "ami-09b007688e369f794";
+  "22.11".eu-central-1.x86_64-linux.hvm-ebs = "ami-05df1b211df600977";
+  "22.11".eu-north-1.x86_64-linux.hvm-ebs = "ami-0427d0897b928e191";
+  "22.11".eu-south-1.x86_64-linux.hvm-ebs = "ami-051beda489f0dd109";
+  "22.11".eu-west-2.x86_64-linux.hvm-ebs = "ami-0c2090b73fc610ac3";
+  "22.11".eu-west-3.x86_64-linux.hvm-ebs = "ami-0d03a150cf6c07022";
+  "22.11".me-south-1.x86_64-linux.hvm-ebs = "ami-0443b1af94bff9e3d";
+  "22.11".sa-east-1.x86_64-linux.hvm-ebs = "ami-07b2ce95ba17b6bc1";
+  "22.11".us-east-1.x86_64-linux.hvm-ebs = "ami-0508167db03652cc4";
+  "22.11".us-east-2.x86_64-linux.hvm-ebs = "ami-0e41ac272a7d67029";
+  "22.11".us-west-1.x86_64-linux.hvm-ebs = "ami-02f3fb062ee9af563";
+  "22.11".us-west-2.x86_64-linux.hvm-ebs = "ami-06b260b3a958948a0";
+
+  "22.11".eu-west-1.aarch64-linux.hvm-ebs = "ami-0c4132540cabbc7df";
+  "22.11".af-south-1.aarch64-linux.hvm-ebs = "ami-0f12780247b337357";
+  "22.11".ap-east-1.aarch64-linux.hvm-ebs = "ami-04789617e858da6fb";
+  "22.11".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-0f4d8517ab163b274";
+  "22.11".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-051a06893bcc696c1";
+  "22.11".ap-northeast-3.aarch64-linux.hvm-ebs = "ami-05a086610680a7d8b";
+  "22.11".ap-south-1.aarch64-linux.hvm-ebs = "ami-04cd79197824124cd";
+  "22.11".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-0437f330961467257";
+  "22.11".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-000c2ecbc430c36d7";
+  "22.11".ap-southeast-3.aarch64-linux.hvm-ebs = "ami-062e917296b5087c0";
+  "22.11".ca-central-1.aarch64-linux.hvm-ebs = "ami-0c91995b735d1b8b6";
+  "22.11".eu-central-1.aarch64-linux.hvm-ebs = "ami-0537d704b177a676b";
+  "22.11".eu-north-1.aarch64-linux.hvm-ebs = "ami-05f1f532f90d8e16c";
+  "22.11".eu-south-1.aarch64-linux.hvm-ebs = "ami-097fe290eafff61ad";
+  "22.11".eu-west-2.aarch64-linux.hvm-ebs = "ami-053b6cc7a3394891a";
+  "22.11".eu-west-3.aarch64-linux.hvm-ebs = "ami-0a5b6d023afde63c3";
+  "22.11".me-south-1.aarch64-linux.hvm-ebs = "ami-024fcb01f8638ed08";
+  "22.11".sa-east-1.aarch64-linux.hvm-ebs = "ami-06d72c6e930037236";
+  "22.11".us-east-1.aarch64-linux.hvm-ebs = "ami-0b33ffb684d6b07b5";
+  "22.11".us-east-2.aarch64-linux.hvm-ebs = "ami-033ff64078c59f378";
+  "22.11".us-west-1.aarch64-linux.hvm-ebs = "ami-052d52b9e30a18562";
+  "22.11".us-west-2.aarch64-linux.hvm-ebs = "ami-07418b6a4782c9521";
+
+  # 23.05.426.afc48694f2a
+
+  "23.05".eu-west-1.x86_64-linux.hvm-ebs = "ami-0fc7825fe890f87d1";
+  "23.05".af-south-1.x86_64-linux.hvm-ebs = "ami-0df2f7b42bfbd53e5";
+  "23.05".ap-east-1.x86_64-linux.hvm-ebs = "ami-07ba84d7321f6f4bb";
+  "23.05".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-0e37827874573dbbf";
+  "23.05".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0ff5b3b7738651895";
+  "23.05".ap-northeast-3.x86_64-linux.hvm-ebs = "ami-0a7861571eb44c70c";
+  "23.05".ap-south-1.x86_64-linux.hvm-ebs = "ami-05c4802ca81d7c95b";
+  "23.05".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0aee8193da16bd2db";
+  "23.05".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-008be032289f60d16";
+  "23.05".ap-southeast-3.x86_64-linux.hvm-ebs = "ami-033debde7c1659c96";
+  "23.05".ca-central-1.x86_64-linux.hvm-ebs = "ami-031821b5f83896474";
+  "23.05".eu-central-1.x86_64-linux.hvm-ebs = "ami-0d6ee9d5e1c985df6";
+  "23.05".eu-north-1.x86_64-linux.hvm-ebs = "ami-0cecb1f67b2a837f6";
+  "23.05".eu-south-1.x86_64-linux.hvm-ebs = "ami-0f9fee15eb5a64ac4";
+  "23.05".eu-west-2.x86_64-linux.hvm-ebs = "ami-0e62fef78d2c4f031";
+  "23.05".eu-west-3.x86_64-linux.hvm-ebs = "ami-01a6e4c1659b08390";
+  "23.05".me-south-1.x86_64-linux.hvm-ebs = "ami-0a01a7eeffa8f0fd5";
+  "23.05".sa-east-1.x86_64-linux.hvm-ebs = "ami-09a1760227f929ccf";
+  "23.05".us-east-1.x86_64-linux.hvm-ebs = "ami-07df5833f04703a2a";
+  "23.05".us-east-2.x86_64-linux.hvm-ebs = "ami-04dd2f100d9665df5";
+  "23.05".us-west-1.x86_64-linux.hvm-ebs = "ami-0fe502361fea4216c";
+  "23.05".us-west-2.x86_64-linux.hvm-ebs = "ami-0749963dd978a57c7";
+
+  "23.05".eu-west-1.aarch64-linux.hvm-ebs = "ami-0a0609421e5638005";
+  "23.05".af-south-1.aarch64-linux.hvm-ebs = "ami-05d95a055aba9373e";
+  "23.05".ap-east-1.aarch64-linux.hvm-ebs = "ami-08ae0190b1357465b";
+  "23.05".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-09418b2049c3c9533";
+  "23.05".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-040713ad23b404271";
+  "23.05".ap-northeast-3.aarch64-linux.hvm-ebs = "ami-0c888d6c1d989db68";
+  "23.05".ap-south-1.aarch64-linux.hvm-ebs = "ami-02da38deb21545675";
+  "23.05".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-06df0713468bea276";
+  "23.05".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-0171ee37ae5104c06";
+  "23.05".ap-southeast-3.aarch64-linux.hvm-ebs = "ami-075da61f5fef1fe80";
+  "23.05".ca-central-1.aarch64-linux.hvm-ebs = "ami-0ba8bd0a3d0a596f8";
+  "23.05".eu-central-1.aarch64-linux.hvm-ebs = "ami-0891608ae66031439";
+  "23.05".eu-north-1.aarch64-linux.hvm-ebs = "ami-0a3ad7ef18d595c68";
+  "23.05".eu-south-1.aarch64-linux.hvm-ebs = "ami-0fa86b680aa9a0444";
+  "23.05".eu-west-2.aarch64-linux.hvm-ebs = "ami-0a415791078f05970";
+  "23.05".eu-west-3.aarch64-linux.hvm-ebs = "ami-05d9b146317962e3b";
+  "23.05".me-south-1.aarch64-linux.hvm-ebs = "ami-0019b591acf30aa66";
+  "23.05".sa-east-1.aarch64-linux.hvm-ebs = "ami-030d6c30d91f06cc7";
+  "23.05".us-east-1.aarch64-linux.hvm-ebs = "ami-0a061ca437b63df33";
+  "23.05".us-east-2.aarch64-linux.hvm-ebs = "ami-0bf0b2b8fdfda30e8";
+  "23.05".us-west-1.aarch64-linux.hvm-ebs = "ami-0e75c8f3deb1f842b";
+  "23.05".us-west-2.aarch64-linux.hvm-ebs = "ami-0d0979d889078d036";
+
+  latest = self."23.05";
 }; in self
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-image.nix b/nixpkgs/nixos/modules/virtualisation/amazon-image.nix
index 12fe6fa44479..aa44f2642697 100644
--- a/nixpkgs/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-image.nix
@@ -10,11 +10,6 @@ with lib;
 
 let
   cfg = config.ec2;
-  metadataFetcher = import ./ec2-metadata-fetcher.nix {
-    inherit (pkgs) curl;
-    targetRoot = "$targetRoot/";
-    wgetExtraOptions = "-q";
-  };
 in
 
 {
@@ -30,19 +25,9 @@ in
 
   config = {
 
-    assertions = [
-      { assertion = cfg.hvm;
-        message = "Paravirtualized EC2 instances are no longer supported.";
-      }
-      { assertion = cfg.efi -> cfg.hvm;
-        message = "EC2 instances using EFI must be HVM instances.";
-      }
-      { assertion = versionOlder config.boot.kernelPackages.kernel.version "5.17";
-        message = "ENA driver fails to build with kernel >= 5.17";
-      }
-    ];
+    assertions = [ ];
 
-    boot.growPartition = cfg.hvm;
+    boot.growPartition = true;
 
     fileSystems."/" = mkIf (!cfg.zfs.enable) {
       device = "/dev/disk/by-label/nixos";
@@ -64,9 +49,9 @@ in
     boot.extraModulePackages = [
       config.boot.kernelPackages.ena
     ];
-    boot.initrd.kernelModules = [ "xen-blkfront" "xen-netfront" ];
-    boot.initrd.availableKernelModules = [ "ixgbevf" "ena" "nvme" ];
-    boot.kernelParams = mkIf cfg.hvm [ "console=ttyS0,115200n8" "random.trust_cpu=on" ];
+    boot.initrd.kernelModules = [ "xen-blkfront" ];
+    boot.initrd.availableKernelModules = [ "nvme" ];
+    boot.kernelParams = [ "console=ttyS0,115200n8" "random.trust_cpu=on" ];
 
     # Prevent the nouveau kernel module from being loaded, as it
     # interferes with the nvidia/nvidia-uvm modules needed for CUDA.
@@ -74,10 +59,7 @@ in
     # boot.
     boot.blacklistedKernelModules = [ "nouveau" "xen_fbfront" ];
 
-    # Generate a GRUB menu.  Amazon's pv-grub uses this to boot our kernel/initrd.
-    boot.loader.grub.version = if cfg.hvm then 2 else 1;
-    boot.loader.grub.device = if (cfg.hvm && !cfg.efi) then "/dev/xvda" else "nodev";
-    boot.loader.grub.extraPerEntryConfig = mkIf (!cfg.hvm) "root (hd0)";
+    boot.loader.grub.device = if cfg.efi then "nodev" else "/dev/xvda";
     boot.loader.grub.efiSupport = cfg.efi;
     boot.loader.grub.efiInstallAsRemovable = cfg.efi;
     boot.loader.timeout = 1;
@@ -87,72 +69,19 @@ in
       terminal_input console serial
     '';
 
-    boot.initrd.network.enable = true;
-
-    # Mount all formatted ephemeral disks and activate all swap devices.
-    # We cannot do this with the ‘fileSystems’ and ‘swapDevices’ options
-    # because the set of devices is dependent on the instance type
-    # (e.g. "m1.small" has one ephemeral filesystem and one swap device,
-    # while "m1.large" has two ephemeral filesystems and no swap
-    # devices).  Also, put /tmp and /var on /disk0, since it has a lot
-    # more space than the root device.  Similarly, "move" /nix to /disk0
-    # by layering a unionfs-fuse mount on top of it so we have a lot more space for
-    # Nix operations.
-    boot.initrd.postMountCommands =
-      ''
-        ${metadataFetcher}
-
-        diskNr=0
-        diskForUnionfs=
-        for device in /dev/xvd[abcde]*; do
-            if [ "$device" = /dev/xvda -o "$device" = /dev/xvda1 ]; then continue; fi
-            fsType=$(blkid -o value -s TYPE "$device" || true)
-            if [ "$fsType" = swap ]; then
-                echo "activating swap device $device..."
-                swapon "$device" || true
-            elif [ "$fsType" = ext3 ]; then
-                mp="/disk$diskNr"
-                diskNr=$((diskNr + 1))
-                if mountFS "$device" "$mp" "" ext3; then
-                    if [ -z "$diskForUnionfs" ]; then diskForUnionfs="$mp"; fi
-                fi
-            else
-                echo "skipping unknown device type $device"
-            fi
-        done
-
-        if [ -n "$diskForUnionfs" ]; then
-            mkdir -m 755 -p $targetRoot/$diskForUnionfs/root
-
-            mkdir -m 1777 -p $targetRoot/$diskForUnionfs/root/tmp $targetRoot/tmp
-            mount --bind $targetRoot/$diskForUnionfs/root/tmp $targetRoot/tmp
-
-            if [ "$(cat "$metaDir/ami-manifest-path")" != "(unknown)" ]; then
-                mkdir -m 755 -p $targetRoot/$diskForUnionfs/root/var $targetRoot/var
-                mount --bind $targetRoot/$diskForUnionfs/root/var $targetRoot/var
-
-                mkdir -p /unionfs-chroot/ro-nix
-                mount --rbind $targetRoot/nix /unionfs-chroot/ro-nix
-
-                mkdir -m 755 -p $targetRoot/$diskForUnionfs/root/nix
-                mkdir -p /unionfs-chroot/rw-nix
-                mount --rbind $targetRoot/$diskForUnionfs/root/nix /unionfs-chroot/rw-nix
-
-                unionfs -o allow_other,cow,nonempty,chroot=/unionfs-chroot,max_files=32768 /rw-nix=RW:/ro-nix=RO $targetRoot/nix
-            fi
-        fi
-      '';
-
-    boot.initrd.extraUtilsCommands =
-      ''
-        # We need swapon in the initrd.
-        copy_bin_and_libs ${pkgs.util-linux}/sbin/swapon
-      '';
+    systemd.services.fetch-ec2-metadata = {
+      wantedBy = [ "multi-user.target" ];
+      after = ["network-online.target"];
+      path = [ pkgs.curl ];
+      script = builtins.readFile ./ec2-metadata-fetcher.sh;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.StandardOutput = "journal+console";
+    };
 
     # Allow root logins only using the SSH key that the user specified
     # at instance creation time.
     services.openssh.enable = true;
-    services.openssh.permitRootLogin = "prohibit-password";
+    services.openssh.settings.PermitRootLogin = "prohibit-password";
 
     # Enable the serial console on ttyS0
     systemd.services."serial-getty@ttyS0".enable = true;
@@ -166,8 +95,6 @@ in
     # Always include cryptsetup so that Charon can use it.
     environment.systemPackages = [ pkgs.cryptsetup ];
 
-    boot.initrd.supportedFilesystems = [ "unionfs-fuse" ];
-
     # EC2 has its own NTP server provided by the hypervisor
     networking.timeServers = [ "169.254.169.123" ];
 
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-options.nix b/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
index 52c960d453d3..3ea4a6cf7818 100644
--- a/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
@@ -8,13 +8,13 @@ in {
         enable = lib.mkOption {
           default = false;
           internal = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether the EC2 instance uses a ZFS root.
           '';
         };
 
         datasets = lib.mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Datasets to create under the `tank` and `boot` zpools.
 
             **NOTE:** This option is used only at image creation time, and
@@ -28,34 +28,33 @@ in {
             options = {
               mount = lib.mkOption {
                 description = lib.mdDoc "Where to mount this dataset.";
-                type = types.nullOr types.string;
+                type = types.nullOr types.str;
                 default = null;
               };
 
               properties = lib.mkOption {
                 description = lib.mdDoc "Properties to set on this dataset.";
-                type = types.attrsOf types.string;
+                type = types.attrsOf types.str;
                 default = {};
               };
             };
           });
         };
       };
-      hvm = lib.mkOption {
-        default = lib.versionAtLeast config.system.stateVersion "17.03";
-        internal = true;
-        description = ''
-          Whether the EC2 instance is a HVM instance.
-        '';
-      };
       efi = lib.mkOption {
         default = pkgs.stdenv.hostPlatform.isAarch64;
         defaultText = literalExpression "pkgs.stdenv.hostPlatform.isAarch64";
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the EC2 instance is using EFI.
         '';
       };
+      hvm = lib.mkOption {
+        description = "Unused legacy option. While support for non-hvm has been dropped, we keep this option around so that NixOps remains compatible with a somewhat recent `nixpkgs` and machines with an old `stateVersion`.";
+        internal = true;
+        default = true;
+        readOnly = true;
+      };
     };
   };
 
diff --git a/nixpkgs/nixos/modules/virtualisation/anbox.nix b/nixpkgs/nixos/modules/virtualisation/anbox.nix
index 6ba71ede7df0..c7e9e23c4c92 100644
--- a/nixpkgs/nixos/modules/virtualisation/anbox.nix
+++ b/nixpkgs/nixos/modules/virtualisation/anbox.nix
@@ -31,7 +31,7 @@ in
 
   options.virtualisation.anbox = {
 
-    enable = mkEnableOption "Anbox";
+    enable = mkEnableOption (lib.mdDoc "Anbox");
 
     image = mkOption {
       default = pkgs.anbox.image;
diff --git a/nixpkgs/nixos/modules/virtualisation/appvm.nix b/nixpkgs/nixos/modules/virtualisation/appvm.nix
index b23b321095cf..9fe2995d37a0 100644
--- a/nixpkgs/nixos/modules/virtualisation/appvm.nix
+++ b/nixpkgs/nixos/modules/virtualisation/appvm.nix
@@ -20,7 +20,7 @@ in {
       user = mkOption {
         type = types.str;
         description = lib.mdDoc ''
-          AppVM user login. Currenly only AppVMs are supported for a single user only.
+          AppVM user login. Currently only AppVMs are supported for a single user only.
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch b/nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch
deleted file mode 100644
index 2a7ad08a4afc..000000000000
--- a/nixpkgs/nixos/modules/virtualisation/azure-agent-entropy.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- a/waagent	2016-03-12 09:58:15.728088851 +0200
-+++ a/waagent	2016-03-12 09:58:43.572680025 +0200
-@@ -6173,10 +6173,10 @@
-             Log("MAC  address: " + ":".join(["%02X" % Ord(a) for a in mac]))
-         
-         # Consume Entropy in ACPI table provided by Hyper-V
--        try:
--            SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
--        except:
--            pass
-+        #try:
-+        #    SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
-+        #except:
-+        #    pass
- 
-         Log("Probing for Azure environment.")
-         self.Endpoint = self.DoDhcpWork()
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-agent.nix b/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
index 31047c4ddc0e..6e6021cf80fe 100644
--- a/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
@@ -1,51 +1,10 @@
 { config, lib, pkgs, ... }:
 
 with lib;
-
 let
 
   cfg = config.virtualisation.azure.agent;
 
-  waagent = with pkgs; stdenv.mkDerivation rec {
-    name = "waagent-2.0";
-    src = pkgs.fetchFromGitHub {
-      owner = "Azure";
-      repo = "WALinuxAgent";
-      rev = "1b3a8407a95344d9d12a2a377f64140975f1e8e4";
-      sha256 = "10byzvmpgrmr4d5mdn2kq04aapqb3sgr1admk13wjmy5cd6bwd2x";
-    };
-
-    patches = [ ./azure-agent-entropy.patch ];
-
-    buildInputs = [ makeWrapper python pythonPackages.wrapPython ];
-    runtimeDeps = [ findutils gnugrep gawk coreutils openssl openssh
-                    nettools # for hostname
-                    procps # for pidof
-                    shadow # for useradd, usermod
-                    util-linux # for (u)mount, fdisk, sfdisk, mkswap
-                    parted
-                  ];
-    pythonPath = [ pythonPackages.pyasn1 ];
-
-    configurePhase = false;
-    buildPhase = false;
-
-    installPhase = ''
-      substituteInPlace config/99-azure-product-uuid.rules \
-          --replace /bin/chmod "${coreutils}/bin/chmod"
-      mkdir -p $out/lib/udev/rules.d
-      cp config/*.rules $out/lib/udev/rules.d
-
-      mkdir -p $out/bin
-      cp waagent $out/bin/
-      chmod +x $out/bin/waagent
-
-      wrapProgram "$out/bin/waagent" \
-          --prefix PYTHONPATH : $PYTHONPATH \
-          --prefix PATH : "${makeBinPath runtimeDeps}"
-    '';
-  };
-
   provisionedHook = pkgs.writeScript "provisioned-hook" ''
     #!${pkgs.runtimeShell}
     /run/current-system/systemd/bin/systemctl start provisioned.target
@@ -74,14 +33,15 @@ in
 
   ###### implementation
 
-  config = mkIf cfg.enable {
-    assertions = [ {
+  config = lib.mkIf cfg.enable {
+    assertions = [{
       assertion = pkgs.stdenv.hostPlatform.isx86;
       message = "Azure not currently supported on ${pkgs.stdenv.hostPlatform.system}";
-    } {
-      assertion = config.networking.networkmanager.enable == false;
-      message = "Windows Azure Linux Agent is not compatible with NetworkManager";
-    } ];
+    }
+      {
+        assertion = config.networking.networkmanager.enable == false;
+        message = "Windows Azure Linux Agent is not compatible with NetworkManager";
+      }];
 
     boot.initrd.kernelModules = [ "ata_piix" ];
     networking.firewall.allowedUDPPorts = [ 68 ];
@@ -89,13 +49,19 @@ in
 
     environment.etc."waagent.conf".text = ''
         #
-        # Windows Azure Linux Agent Configuration
+        # Microsoft Azure Linux Agent Configuration
         #
 
-        Role.StateConsumer=${provisionedHook}
+        # Enable extension handling. Do not disable this unless you do not need password reset,
+        # backup, monitoring, or any extension handling whatsoever.
+        Extensions.Enabled=y
 
-        # Enable instance creation
-        Provisioning.Enabled=y
+        # How often (in seconds) to poll for new goal states
+        Extensions.GoalStatePeriod=6
+
+        # Which provisioning agent to use. Supported values are "auto" (default), "waagent",
+        # "cloud-init", or "disabled".
+        Provisioning.Agent=disabled
 
         # Password authentication for root account will be unavailable.
         Provisioning.DeleteRootPassword=n
@@ -103,18 +69,31 @@ in
         # Generate fresh host key pair.
         Provisioning.RegenerateSshHostKeyPair=n
 
-        # Supported values are "rsa", "dsa" and "ecdsa".
+        # Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+        # The "auto" option is supported on OpenSSH 5.9 (2011) and later.
         Provisioning.SshHostKeyPairType=ed25519
 
         # Monitor host name changes and publish changes via DHCP requests.
         Provisioning.MonitorHostName=y
 
+        # How often (in seconds) to monitor host name changes.
+        Provisioning.MonitorHostNamePeriod=30
+
         # Decode CustomData from Base64.
         Provisioning.DecodeCustomData=n
 
         # Execute CustomData after provisioning.
         Provisioning.ExecuteCustomData=n
 
+        # Algorithm used by crypt when generating password hash.
+        #Provisioning.PasswordCryptId=6
+
+        # Length of random salt used when generating password hash.
+        #Provisioning.PasswordCryptSaltLength=10
+
+        # Allow reset password of sys user
+        Provisioning.AllowResetSysUser=n
+
         # Format if unformatted. If 'n', resource disk will not be mounted.
         ResourceDisk.Format=${if cfg.mountResourceDisk then "y" else "n"}
 
@@ -125,22 +104,103 @@ in
         # Mount point for the resource disk
         ResourceDisk.MountPoint=/mnt/resource
 
-        # Respond to load balancer probes if requested by Windows Azure.
-        LBProbeResponder=y
+        # Create and use swapfile on resource disk.
+        ResourceDisk.EnableSwap=n
+
+        # Size of the swapfile.
+        ResourceDisk.SwapSizeMB=0
 
-        # Enable logging to serial console (y|n)
-        # When stdout is not enough...
-        # 'y' if not set
-        Logs.Console=y
+        # Comma-separated list of mount options. See mount(8) for valid options.
+        ResourceDisk.MountOptions=None
 
         # Enable verbose logging (y|n)
         Logs.Verbose=${if cfg.verboseLogging then "y" else "n"}
 
+        # Enable Console logging, default is y
+        # Logs.Console=y
+
+        # Enable periodic log collection, default is n
+        Logs.Collect=n
+
+        # How frequently to collect logs, default is each hour
+        Logs.CollectPeriod=3600
+
+        # Is FIPS enabled
+        OS.EnableFIPS=n
+
         # Root device timeout in seconds.
         OS.RootDeviceScsiTimeout=300
+
+        # How often (in seconds) to set the root device timeout.
+        OS.RootDeviceScsiTimeoutPeriod=30
+
+        # If "None", the system default version is used.
+        OS.OpensslPath=${pkgs.openssl_3.bin}/bin/openssl
+
+        # Set the SSH ClientAliveInterval
+        # OS.SshClientAliveInterval=180
+
+        # Set the path to SSH keys and configuration files
+        OS.SshDir=/etc/ssh
+
+        # If set, agent will use proxy server to access internet
+        #HttpProxy.Host=None
+        #HttpProxy.Port=None
+
+        # Detect Scvmm environment, default is n
+        # DetectScvmmEnv=n
+
+        #
+        # Lib.Dir=/var/lib/waagent
+
+        #
+        # DVD.MountPoint=/mnt/cdrom/secure
+
+        #
+        # Pid.File=/var/run/waagent.pid
+
+        #
+        # Extension.LogDir=/var/log/azure
+
+        #
+        # Home.Dir=/home
+
+        # Enable RDMA management and set up, should only be used in HPC images
+        OS.EnableRDMA=n
+
+        # Enable checking RDMA driver version and update
+        # OS.CheckRdmaDriver=y
+
+        # Enable or disable goal state processing auto-update, default is enabled
+        AutoUpdate.Enabled=n
+
+        # Determine the update family, this should not be changed
+        # AutoUpdate.GAFamily=Prod
+
+        # Determine if the overprovisioning feature is enabled. If yes, hold extension
+        # handling until inVMArtifactsProfile.OnHold is false.
+        # Default is enabled
+        EnableOverProvisioning=n
+
+        # Allow fallback to HTTP if HTTPS is unavailable
+        # Note: Allowing HTTP (vs. HTTPS) may cause security risks
+        # OS.AllowHTTP=n
+
+        # Add firewall rules to protect access to Azure host node services
+        OS.EnableFirewall=n
+
+        # How often (in seconds) to check the firewall rules
+        OS.EnableFirewallPeriod=30
+
+        # How often (in seconds) to remove the udev rules for persistent network interface
+        # names (75-persistent-net-generator.rules and /etc/udev/rules.d/70-persistent-net.rules)
+        OS.RemovePersistentNetRulesPeriod=30
+
+        # How often (in seconds) to monitor for DHCP client restarts
+        OS.MonitorDhcpClientRestartPeriod=30
     '';
 
-    services.udev.packages = [ waagent ];
+    services.udev.packages = [ pkgs.waagent ];
 
     networking.dhcpcd.persistent = true;
 
@@ -157,23 +217,24 @@ in
       description = "Services Requiring Azure VM provisioning to have finished";
     };
 
-  systemd.services.consume-hypervisor-entropy =
-    { description = "Consume entropy in ACPI table provided by Hyper-V";
-
-      wantedBy = [ "sshd.service" "waagent.service" ];
-      before = [ "sshd.service" "waagent.service" ];
-
-      path  = [ pkgs.coreutils ];
-      script =
-        ''
-          echo "Fetching entropy..."
-          cat /sys/firmware/acpi/tables/OEM0 > /dev/random
-        '';
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
-      serviceConfig.StandardError = "journal+console";
-      serviceConfig.StandardOutput = "journal+console";
-     };
+    systemd.services.consume-hypervisor-entropy =
+      {
+        description = "Consume entropy in ACPI table provided by Hyper-V";
+
+        wantedBy = [ "sshd.service" "waagent.service" ];
+        before = [ "sshd.service" "waagent.service" ];
+
+        path = [ pkgs.coreutils ];
+        script =
+          ''
+            echo "Fetching entropy..."
+            cat /sys/firmware/acpi/tables/OEM0 > /dev/random
+          '';
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.StandardError = "journal+console";
+        serviceConfig.StandardOutput = "journal+console";
+      };
 
     systemd.services.waagent = {
       wantedBy = [ "multi-user.target" ];
@@ -184,11 +245,10 @@ in
       description = "Windows Azure Agent Service";
       unitConfig.ConditionPathExists = "/etc/waagent.conf";
       serviceConfig = {
-        ExecStart = "${waagent}/bin/waagent -daemon";
+        ExecStart = "${pkgs.waagent}/bin/waagent -daemon";
         Type = "simple";
       };
     };
 
   };
-
 }
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-common.nix b/nixpkgs/nixos/modules/virtualisation/azure-common.nix
index dc7853b95032..cd1ffdb6cbcc 100644
--- a/nixpkgs/nixos/modules/virtualisation/azure-common.nix
+++ b/nixpkgs/nixos/modules/virtualisation/azure-common.nix
@@ -12,7 +12,6 @@ with lib;
 
   # Generate a GRUB menu.
   boot.loader.grub.device = "/dev/sda";
-  boot.loader.grub.version = 2;
   boot.loader.timeout = 0;
 
   boot.growPartition = true;
@@ -30,10 +29,8 @@ with lib;
   # Allow root logins only using the SSH key that the user specified
   # at instance creation time, ping client connections to avoid timeouts
   services.openssh.enable = true;
-  services.openssh.permitRootLogin = "prohibit-password";
-  services.openssh.extraConfig = ''
-    ClientAliveInterval 180
-  '';
+  services.openssh.settings.PermitRootLogin = "prohibit-password";
+  services.openssh.settings.ClientAliveInterval = 180;
 
   # Force getting the hostname from Azure
   networking.hostName = mkDefault "";
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-image.nix b/nixpkgs/nixos/modules/virtualisation/azure-image.nix
index 03dd3c051309..17cfd3938305 100644
--- a/nixpkgs/nixos/modules/virtualisation/azure-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/azure-image.nix
@@ -12,7 +12,7 @@ in
       type = with types; either (enum [ "auto" ]) int;
       default = "auto";
       example = 2048;
-      description = ''
+      description = lib.mdDoc ''
         Size of disk image. Unit is MB.
       '';
     };
diff --git a/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix b/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix
index 9641b693f184..15f8fd6d8f7d 100644
--- a/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/brightbox-image.nix
@@ -103,7 +103,7 @@ in
   # Allow root logins only using the SSH key that the user specified
   # at instance creation time.
   services.openssh.enable = true;
-  services.openssh.permitRootLogin = "prohibit-password";
+  services.openssh.settings.PermitRootLogin = "prohibit-password";
 
   # Force getting the hostname from Google Compute.
   networking.hostName = mkDefault "";
diff --git a/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix b/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix
index 78afebdc5dd3..7df3c9c613b4 100644
--- a/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix
+++ b/nixpkgs/nixos/modules/virtualisation/cloudstack-config.nix
@@ -21,7 +21,7 @@ with lib;
     # Allow root logins
     services.openssh = {
       enable = true;
-      permitRootLogin = "prohibit-password";
+      settings.PermitRootLogin = "prohibit-password";
     };
 
     # Cloud-init configuration.
diff --git a/nixpkgs/nixos/modules/virtualisation/container-config.nix b/nixpkgs/nixos/modules/virtualisation/container-config.nix
index 94f28ea80d09..2460ec45e3fc 100644
--- a/nixpkgs/nixos/modules/virtualisation/container-config.nix
+++ b/nixpkgs/nixos/modules/virtualisation/container-config.nix
@@ -7,6 +7,13 @@ with lib;
   config = mkIf config.boot.isContainer {
 
     # Disable some features that are not useful in a container.
+
+    # containers don't have a kernel
+    boot.kernel.enable = false;
+    boot.modprobeConfig.enable = false;
+
+    console.enable = mkDefault false;
+
     nix.optimise.automatic = mkDefault false; # the store is host managed
     powerManagement.enable = mkDefault false;
     documentation.nixos.enable = mkDefault false;
@@ -16,6 +23,12 @@ with lib;
     # Containers should be light-weight, so start sshd on demand.
     services.openssh.startWhenNeeded = mkDefault true;
 
+    # containers do not need to setup devices
+    services.udev.enable = false;
+
+    # containers normally do not need to manage logical volumes
+    services.lvm.enable = lib.mkDefault false;
+
     # Shut up warnings about not having a boot loader.
     system.build.installBootLoader = lib.mkDefault "${pkgs.coreutils}/bin/true";
 
diff --git a/nixpkgs/nixos/modules/virtualisation/containerd.nix b/nixpkgs/nixos/modules/virtualisation/containerd.nix
index 96fa6761296b..f6e3c8387298 100644
--- a/nixpkgs/nixos/modules/virtualisation/containerd.nix
+++ b/nixpkgs/nixos/modules/virtualisation/containerd.nix
@@ -19,7 +19,7 @@ in
 {
 
   options.virtualisation.containerd = with lib.types; {
-    enable = lib.mkEnableOption "containerd container runtime";
+    enable = lib.mkEnableOption (lib.mdDoc "containerd container runtime");
 
     configFile = lib.mkOption {
       default = null;
diff --git a/nixpkgs/nixos/modules/virtualisation/containers.nix b/nixpkgs/nixos/modules/virtualisation/containers.nix
index 956844352f9a..3e33cabf2660 100644
--- a/nixpkgs/nixos/modules/virtualisation/containers.nix
+++ b/nixpkgs/nixos/modules/virtualisation/containers.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, pkgs, ... }:
 let
   cfg = config.virtualisation.containers;
 
@@ -11,20 +11,6 @@ in
     maintainers = [ ] ++ lib.teams.podman.members;
   };
 
-
-  imports = [
-    (
-      lib.mkRemovedOptionModule
-        [ "virtualisation" "containers" "users" ]
-        "All users with `isNormalUser = true` set now get appropriate subuid/subgid mappings."
-    )
-    (
-      lib.mkRemovedOptionModule
-        [ "virtualisation" "containers" "containersConf" "extraConfig" ]
-        "Use virtualisation.containers.containersConf.settings instead."
-    )
-  ];
-
   options.virtualisation.containers = {
 
     enable =
@@ -150,7 +136,7 @@ in
 
     environment.etc."containers/policy.json".source =
       if cfg.policy != { } then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
-      else utils.copyFile "${pkgs.skopeo.src}/default-policy.json";
+      else "${pkgs.skopeo.policy}/default-policy.json";
   };
 
 }
diff --git a/nixpkgs/nixos/modules/virtualisation/cri-o.nix b/nixpkgs/nixos/modules/virtualisation/cri-o.nix
index 3c9b0a061d28..dacd700537c7 100644
--- a/nixpkgs/nixos/modules/virtualisation/cri-o.nix
+++ b/nixpkgs/nixos/modules/virtualisation/cri-o.nix
@@ -1,29 +1,28 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 let
   cfg = config.virtualisation.cri-o;
 
-  crioPackage = (pkgs.cri-o.override { inherit (cfg) extraPackages; });
+  crioPackage = pkgs.cri-o.override {
+    extraPackages = cfg.extraPackages
+      ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
+  };
 
   format = pkgs.formats.toml { };
 
   cfgFile = format.generate "00-default.conf" cfg.settings;
 in
 {
-  imports = [
-    (mkRenamedOptionModule [ "virtualisation" "cri-o" "registries" ] [ "virtualisation" "containers" "registries" "search" ])
-  ];
-
   meta = {
     maintainers = teams.podman.members;
   };
 
   options.virtualisation.cri-o = {
-    enable = mkEnableOption "Container Runtime Interface for OCI (CRI-O)";
+    enable = mkEnableOption (lib.mdDoc "Container Runtime Interface for OCI (CRI-O)");
 
     storageDriver = mkOption {
-      type = types.enum [ "btrfs" "overlay" "vfs" ];
+      type = types.enum [ "aufs" "btrfs" "devmapper" "overlay" "vfs" "zfs" ];
       default = "overlay";
       description = lib.mdDoc "Storage driver to be used";
     };
@@ -72,7 +71,7 @@ in
       type = types.package;
       default = crioPackage;
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         The final CRI-O package (including extra packages).
       '';
     };
@@ -80,7 +79,7 @@ in
     networkDir = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = "Override the network_dir option.";
+      description = lib.mdDoc "Override the network_dir option.";
       internal = true;
     };
 
@@ -97,7 +96,7 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = [ cfg.package pkgs.cri-tools ];
 
-    environment.etc."crictl.yaml".source = utils.copyFile "${pkgs.cri-o-unwrapped.src}/crictl.yaml";
+    environment.etc."crictl.yaml".source = "${cfg.package}/etc/crictl.yaml";
 
     virtualisation.cri-o.settings.crio = {
       storage_driver = cfg.storageDriver;
@@ -128,8 +127,8 @@ in
       };
     };
 
-    environment.etc."cni/net.d/10-crio-bridge.conf".source = utils.copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/10-crio-bridge.conf";
-    environment.etc."cni/net.d/99-loopback.conf".source = utils.copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/99-loopback.conf";
+    environment.etc."cni/net.d/10-crio-bridge.conflist".source = "${cfg.package}/etc/cni/net.d/10-crio-bridge.conflist";
+    environment.etc."cni/net.d/99-loopback.conflist".source = "${cfg.package}/etc/cni/net.d/99-loopback.conflist";
     environment.etc."crio/crio.conf.d/00-default.conf".source = cfgFile;
 
     # Enable common /etc/containers configuration
diff --git a/nixpkgs/nixos/modules/virtualisation/digital-ocean-config.nix b/nixpkgs/nixos/modules/virtualisation/digital-ocean-config.nix
index 754bc1a51857..e004b7880aad 100644
--- a/nixpkgs/nixos/modules/virtualisation/digital-ocean-config.nix
+++ b/nixpkgs/nixos/modules/virtualisation/digital-ocean-config.nix
@@ -49,7 +49,7 @@ with lib;
       };
       services.openssh = {
         enable = mkDefault true;
-        passwordAuthentication = mkDefault false;
+        settings.PasswordAuthentication = mkDefault false;
       };
       services.do-agent.enable = mkDefault true;
       networking = {
diff --git a/nixpkgs/nixos/modules/virtualisation/digital-ocean-init.nix b/nixpkgs/nixos/modules/virtualisation/digital-ocean-init.nix
index e29e34c4775f..1a5d4e898e96 100644
--- a/nixpkgs/nixos/modules/virtualisation/digital-ocean-init.nix
+++ b/nixpkgs/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -20,7 +20,7 @@ in {
   options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
     type = types.path;
     default = defaultConfigFile;
-    defaultText = literalDocBook ''
+    defaultText = literalMD ''
       The default configuration imports user-data if applicable and
       `(modulesPath + "/virtualisation/digital-ocean-config.nix")`.
     '';
diff --git a/nixpkgs/nixos/modules/virtualisation/docker.nix b/nixpkgs/nixos/modules/virtualisation/docker.nix
index 66c94f30088f..046b8e2f7901 100644
--- a/nixpkgs/nixos/modules/virtualisation/docker.nix
+++ b/nixpkgs/nixos/modules/virtualisation/docker.nix
@@ -100,7 +100,7 @@ in
 
     logDriver =
       mkOption {
-        type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs"];
+        type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs" "local"];
         default = "journald";
         description =
           lib.mdDoc ''
@@ -163,7 +163,7 @@ in
   ###### implementation
 
   config = mkIf cfg.enable (mkMerge [{
-      boot.kernelModules = [ "bridge" "veth" ];
+      boot.kernelModules = [ "bridge" "veth" "br_netfilter" "xt_nat" ];
       boot.kernel.sysctl = {
         "net.ipv4.conf.all.forwarding" = mkOverride 98 true;
         "net.ipv4.conf.default.forwarding" = mkOverride 98 true;
@@ -221,6 +221,8 @@ in
         '';
 
         startAt = optional cfg.autoPrune.enable cfg.autoPrune.dates;
+        after = [ "docker.service" ];
+        requires = [ "docker.service" ];
       };
 
       assertions = [
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-data.nix b/nixpkgs/nixos/modules/virtualisation/ec2-data.nix
index 1b764e7e4d80..0cc6d9938e22 100644
--- a/nixpkgs/nixos/modules/virtualisation/ec2-data.nix
+++ b/nixpkgs/nixos/modules/virtualisation/ec2-data.nix
@@ -18,6 +18,7 @@ with lib;
 
         wantedBy = [ "multi-user.target" "sshd.service" ];
         before = [ "sshd.service" ];
+        after = ["fetch-ec2-metadata.service"];
 
         path = [ pkgs.iproute2 ];
 
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix b/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix
deleted file mode 100644
index 760f024f33fb..000000000000
--- a/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.nix
+++ /dev/null
@@ -1,77 +0,0 @@
-{ curl, targetRoot, wgetExtraOptions }:
-# Note: be very cautious about dependencies, each dependency grows
-# the closure of the initrd. Ideally we would not even require curl,
-# but there is no reasonable way to send an HTTP PUT request without
-# it. Note: do not be fooled: the wget referenced in this script
-# is busybox's wget, not the fully featured one with --method support.
-#
-# Make sure that every package you depend on here is already listed as
-# a channel blocker for both the full-sized and small channels.
-# Otherwise, we risk breaking user deploys in released channels.
-#
-# Also note: OpenStack's metadata service for its instances aims to be
-# compatible with the EC2 IMDS. Where possible, try to keep the set of
-# fetched metadata in sync with ./openstack-metadata-fetcher.nix .
-''
-  metaDir=${targetRoot}etc/ec2-metadata
-  mkdir -m 0755 -p "$metaDir"
-  rm -f "$metaDir/*"
-
-  get_imds_token() {
-    # retry-delay of 1 selected to give the system a second to get going,
-    # but not add a lot to the bootup time
-    ${curl}/bin/curl \
-      -v \
-      --retry 3 \
-      --retry-delay 1 \
-      --fail \
-      -X PUT \
-      --connect-timeout 1 \
-      -H "X-aws-ec2-metadata-token-ttl-seconds: 600" \
-      http://169.254.169.254/latest/api/token
-  }
-
-  preflight_imds_token() {
-    # retry-delay of 1 selected to give the system a second to get going,
-    # but not add a lot to the bootup time
-    ${curl}/bin/curl \
-      -v \
-      --retry 3 \
-      --retry-delay 1 \
-      --fail \
-      --connect-timeout 1 \
-      -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
-      http://169.254.169.254/1.0/meta-data/instance-id
-  }
-
-  try=1
-  while [ $try -le 3 ]; do
-    echo "(attempt $try/3) getting an EC2 instance metadata service v2 token..."
-    IMDS_TOKEN=$(get_imds_token) && break
-    try=$((try + 1))
-    sleep 1
-  done
-
-  if [ "x$IMDS_TOKEN" == "x" ]; then
-    echo "failed to fetch an IMDS2v token."
-  fi
-
-  try=1
-  while [ $try -le 10 ]; do
-    echo "(attempt $try/10) validating the EC2 instance metadata service v2 token..."
-    preflight_imds_token && break
-    try=$((try + 1))
-    sleep 1
-  done
-
-  echo "getting EC2 instance metadata..."
-
-  wget_imds() {
-    wget ${wgetExtraOptions} --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@";
-  }
-
-  wget_imds -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
-  (umask 077 && wget_imds -O "$metaDir/user-data" http://169.254.169.254/1.0/user-data)
-  wget_imds -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
-  wget_imds -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
-''
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.sh b/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
new file mode 100644
index 000000000000..716aff7c22fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
@@ -0,0 +1,66 @@
+metaDir=/etc/ec2-metadata
+mkdir -m 0755 -p "$metaDir"
+rm -f "$metaDir/*"
+
+get_imds_token() {
+  # retry-delay of 1 selected to give the system a second to get going,
+  # but not add a lot to the bootup time
+  curl \
+    --silent \
+    --show-error \
+    --retry 3 \
+    --retry-delay 1 \
+    --fail \
+    -X PUT \
+    --connect-timeout 1 \
+    -H "X-aws-ec2-metadata-token-ttl-seconds: 600" \
+    http://169.254.169.254/latest/api/token
+}
+
+preflight_imds_token() {
+  # retry-delay of 1 selected to give the system a second to get going,
+  # but not add a lot to the bootup time
+  curl \
+    --silent \
+    --show-error \
+    --retry 3 \
+    --retry-delay 1 \
+    --fail \
+    --connect-timeout 1 \
+    -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
+    -o /dev/null \
+    http://169.254.169.254/1.0/meta-data/instance-id
+}
+
+try=1
+while [ $try -le 3 ]; do
+  echo "(attempt $try/3) getting an EC2 instance metadata service v2 token..."
+  IMDS_TOKEN=$(get_imds_token) && break
+  try=$((try + 1))
+  sleep 1
+done
+
+if [ "x$IMDS_TOKEN" == "x" ]; then
+  echo "failed to fetch an IMDS2v token."
+fi
+
+try=1
+while [ $try -le 10 ]; do
+  echo "(attempt $try/10) validating the EC2 instance metadata service v2 token..."
+  preflight_imds_token && break
+  try=$((try + 1))
+  sleep 1
+done
+
+echo "getting EC2 instance metadata..."
+
+get_imds() {
+  # --fail to avoid populating missing files with 404 HTML response body
+  # || true to allow the script to continue even when encountering a 404
+  curl --silent --show-error --fail --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@" || true
+}
+
+get_imds -o "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
+(umask 077 && get_imds -o "$metaDir/user-data" http://169.254.169.254/1.0/user-data)
+get_imds -o "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
+get_imds -o "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
diff --git a/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix b/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix
index af1736fcf0b8..dd87df9a2780 100644
--- a/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix
+++ b/nixpkgs/nixos/modules/virtualisation/ecs-agent.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.ecs-agent;
 in {
   options.services.ecs-agent = {
-    enable = mkEnableOption "Amazon ECS agent";
+    enable = mkEnableOption (lib.mdDoc "Amazon ECS agent");
 
     package = mkOption {
       type = types.path;
diff --git a/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix b/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix
index 44d2a589511f..cf94ce0faf36 100644
--- a/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixpkgs/nixos/modules/virtualisation/google-compute-config.nix
@@ -1,5 +1,15 @@
 { config, lib, pkgs, ... }:
-with lib;
+
+let
+  inherit (lib)
+    boolToString
+    mkDefault
+    mkIf
+    optional
+    readFile
+  ;
+in
+
 {
   imports = [
     ../profiles/headless.nix
@@ -29,8 +39,8 @@ with lib;
   # Allow root logins only using SSH keys
   # and disable password authentication in general
   services.openssh.enable = true;
-  services.openssh.permitRootLogin = "prohibit-password";
-  services.openssh.passwordAuthentication = mkDefault false;
+  services.openssh.settings.PermitRootLogin = "prohibit-password";
+  services.openssh.settings.PasswordAuthentication = mkDefault false;
 
   # enable OS Login. This also requires setting enable-oslogin=TRUE metadata on
   # instance or project level
@@ -65,7 +75,7 @@ with lib;
   systemd.services.google-guest-agent = {
     wantedBy = [ "multi-user.target" ];
     restartTriggers = [ config.environment.etc."default/instance_configs.cfg".source ];
-    path = lib.optional config.users.mutableUsers pkgs.shadow;
+    path = optional config.users.mutableUsers pkgs.shadow;
   };
   systemd.services.google-startup-scripts.wantedBy = [ "multi-user.target" ];
   systemd.services.google-shutdown-scripts.wantedBy = [ "multi-user.target" ];
@@ -76,7 +86,7 @@ with lib;
 
   users.groups.google-sudoers = mkIf config.users.mutableUsers { };
 
-  boot.extraModprobeConfig = lib.readFile "${pkgs.google-guest-configs}/etc/modprobe.d/gce-blacklist.conf";
+  boot.extraModprobeConfig = readFile "${pkgs.google-guest-configs}/etc/modprobe.d/gce-blacklist.conf";
 
   environment.etc."sysctl.d/60-gce-network-security.conf".source = "${pkgs.google-guest-configs}/etc/sysctl.d/60-gce-network-security.conf";
 
diff --git a/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix b/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix
index 0c72696f802b..197ebb18b9ad 100644
--- a/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/google-compute-image.nix
@@ -21,7 +21,7 @@ in
       type = with types; either (enum [ "auto" ]) int;
       default = "auto";
       example = 1536;
-      description = ''
+      description = lib.mdDoc ''
         Size of disk image. Unit is MB.
       '';
     };
@@ -29,7 +29,7 @@ in
     virtualisation.googleComputeImage.configFile = mkOption {
       type = with types; nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A path to a configuration file which will be placed at `/etc/nixos/configuration.nix`
         and be used when switching to a new configuration.
         If set to `null`, a default configuration is used, where the only import is
@@ -40,7 +40,7 @@ in
     virtualisation.googleComputeImage.compressionLevel = mkOption {
       type = types.int;
       default = 6;
-      description = ''
+      description = lib.mdDoc ''
         GZIP compression level of the resulting disk image (1-9).
       '';
     };
diff --git a/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix b/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix
index e6ba38370f22..cba4f92abe82 100644
--- a/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix
+++ b/nixpkgs/nixos/modules/virtualisation/hyperv-guest.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     virtualisation.hypervGuest = {
-      enable = mkEnableOption "Hyper-V Guest Support";
+      enable = mkEnableOption (lib.mdDoc "Hyper-V Guest Support");
 
       videoMode = mkOption {
         type = types.str;
@@ -56,8 +56,6 @@ in {
     systemd = {
       packages = [ config.boot.kernelPackages.hyperv-daemons.lib ];
 
-      services.hv-vss.unitConfig.ConditionPathExists = [ "/dev/vmbus/hv_vss" ];
-
       targets.hyperv-daemons = {
         wantedBy = [ "multi-user.target" ];
       };
diff --git a/nixpkgs/nixos/modules/virtualisation/hyperv-image.nix b/nixpkgs/nixos/modules/virtualisation/hyperv-image.nix
index 6845d6750092..efaea0c110d2 100644
--- a/nixpkgs/nixos/modules/virtualisation/hyperv-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/hyperv-image.nix
@@ -12,21 +12,21 @@ in {
         type = with types; either (enum [ "auto" ]) int;
         default = "auto";
         example = 2048;
-        description = ''
+        description = lib.mdDoc ''
           The size of the hyper-v base image in MiB.
         '';
       };
       vmDerivationName = mkOption {
         type = types.str;
         default = "nixos-hyperv-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
-        description = ''
+        description = lib.mdDoc ''
           The name of the derivation for the hyper-v appliance.
         '';
       };
       vmFileName = mkOption {
         type = types.str;
         default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.vhdx";
-        description = ''
+        description = lib.mdDoc ''
           The file name of the hyper-v appliance.
         '';
       };
diff --git a/nixpkgs/nixos/modules/virtualisation/kvmgt.nix b/nixpkgs/nixos/modules/virtualisation/kvmgt.nix
index 5ea71c3a9143..1e02636f81f4 100644
--- a/nixpkgs/nixos/modules/virtualisation/kvmgt.nix
+++ b/nixpkgs/nixos/modules/virtualisation/kvmgt.nix
@@ -10,18 +10,18 @@ let
   vgpuOptions = {
     uuid = mkOption {
       type = with types; listOf str;
-      description = "UUID(s) of VGPU device. You can generate one with <package>libossp_uuid</package>.";
+      description = lib.mdDoc "UUID(s) of VGPU device. You can generate one with `libossp_uuid`.";
     };
   };
 
 in {
   options = {
     virtualisation.kvmgt = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         KVMGT (iGVT-g) VGPU support. Allows Qemu/KVM guests to share host's Intel integrated graphics card.
         Currently only one graphical device can be shared. To allow users to access the device without root add them
-        to the kvm group: <literal>users.extraUsers.&lt;yourusername&gt;.extraGroups = [ "kvm" ];</literal>
-      '';
+        to the kvm group: `users.extraUsers.<yourusername>.extraGroups = [ "kvm" ];`
+      '');
       # multi GPU support is under the question
       device = mkOption {
         type = types.str;
@@ -31,9 +31,9 @@ in {
       vgpus = mkOption {
         default = {};
         type = with types; attrsOf (submodule [ { options = vgpuOptions; } ]);
-        description = ''
-          Virtual GPUs to be used in Qemu. You can find devices via <command>ls /sys/bus/pci/devices/*/mdev_supported_types</command>
-          and find info about device via <command>cat /sys/bus/pci/devices/*/mdev_supported_types/i915-GVTg_V5_4/description</command>
+        description = lib.mdDoc ''
+          Virtual GPUs to be used in Qemu. You can find devices via {command}`ls /sys/bus/pci/devices/*/mdev_supported_types`
+          and find info about device via {command}`cat /sys/bus/pci/devices/*/mdev_supported_types/i915-GVTg_V5_4/description`
         '';
         example = {
           i915-GVTg_V5_8.uuid = [ "a297db4a-f4c2-11e6-90f6-d3b88d6c9525" ];
diff --git a/nixpkgs/nixos/modules/virtualisation/libvirtd.nix b/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
index 817d7180a022..8dfe04cea542 100644
--- a/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
@@ -81,7 +81,7 @@ let
         type = types.package;
         default = pkgs.qemu;
         defaultText = literalExpression "pkgs.qemu";
-        description = ''
+        description = lib.mdDoc ''
           Qemu package to use with libvirt.
           `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
           `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
@@ -220,6 +220,17 @@ in
       '';
     };
 
+    parallelShutdown = mkOption {
+      type = types.ints.unsigned;
+      default = 0;
+      description = lib.mdDoc ''
+        Number of guests that will be shutdown concurrently, taking effect when onShutdown
+        is set to "shutdown". If set to 0, guests will be shutdown one after another.
+        Number of guests on shutdown at any time will not exceed number set in this
+        variable.
+      '';
+    };
+
     allowedBridges = mkOption {
       type = types.listOf types.str;
       default = [ "virbr0" ];
@@ -282,7 +293,7 @@ in
       setuid = true;
       owner = "root";
       group = "root";
-      source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
+      source = "${cfg.qemu.package}/libexec/qemu-bridge-helper";
     };
 
     systemd.packages = [ cfg.package ];
@@ -297,7 +308,9 @@ in
             libvirt/nwfilter/*.xml );
         do
             mkdir -p /var/lib/$(dirname $i) -m 755
-            cp -npd ${cfg.package}/var/lib/$i /var/lib/$i
+            if [ ! -e /var/lib/$i ]; then
+              cp -pd ${cfg.package}/var/lib/$i /var/lib/$i
+            fi
         done
 
         # Copy generated qemu config to libvirt directory
@@ -308,7 +321,7 @@ in
           ln -s --force "$emulator" /run/${dirName}/nix-emulators/
         done
 
-        for helper in libexec/qemu-bridge-helper bin/qemu-pr-helper; do
+        for helper in bin/qemu-pr-helper; do
           ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/
         done
 
@@ -336,6 +349,7 @@ in
     };
 
     systemd.services.libvirtd = {
+      wantedBy = [ "multi-user.target" ];
       requires = [ "libvirtd-config.service" ];
       after = [ "libvirtd-config.service" ]
         ++ optional vswitch.enable "ovs-vswitchd.service";
@@ -372,6 +386,7 @@ in
 
       environment.ON_BOOT = "${cfg.onBoot}";
       environment.ON_SHUTDOWN = "${cfg.onShutdown}";
+      environment.PARALLEL_SHUTDOWN = "${toString cfg.parallelShutdown}";
     };
 
     systemd.sockets.virtlogd = {
@@ -401,13 +416,16 @@ in
     # https://libvirt.org/daemons.html#monolithic-systemd-integration
     systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
 
-    security.polkit.extraConfig = ''
-      polkit.addRule(function(action, subject) {
-        if (action.id == "org.libvirt.unix.manage" &&
-          subject.isInGroup("libvirtd")) {
-          return polkit.Result.YES;
-        }
-      });
-    '';
+    security.polkit = {
+      enable = true;
+      extraConfig = ''
+        polkit.addRule(function(action, subject) {
+          if (action.id == "org.libvirt.unix.manage" &&
+            subject.isInGroup("libvirtd")) {
+            return polkit.Result.YES;
+          }
+        });
+      '';
+    };
   };
 }
diff --git a/nixpkgs/nixos/modules/virtualisation/linode-config.nix b/nixpkgs/nixos/modules/virtualisation/linode-config.nix
new file mode 100644
index 000000000000..bbf81bda9c02
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/linode-config.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+with lib;
+{
+  imports = [ ../profiles/qemu-guest.nix ];
+
+  services.openssh = {
+    enable = true;
+
+    settings.PermitRootLogin = "prohibit-password";
+    settings.PasswordAuthentication = mkDefault false;
+  };
+
+  networking = {
+    usePredictableInterfaceNames = false;
+    useDHCP = false;
+    interfaces.eth0 = {
+      useDHCP = true;
+
+      # Linode expects IPv6 privacy extensions to be disabled, so disable them
+      # See: https://www.linode.com/docs/guides/manual-network-configuration/#static-vs-dynamic-addressing
+      tempAddress = "disabled";
+    };
+  };
+
+  # Install diagnostic tools for Linode support
+  environment.systemPackages = with pkgs; [
+    inetutils
+    mtr
+    sysstat
+  ];
+
+  fileSystems."/" = {
+    fsType = "ext4";
+    device = "/dev/sda";
+    autoResize = true;
+  };
+
+  swapDevices = mkDefault [{ device = "/dev/sdb"; }];
+
+  # Enable LISH and Linode Booting w/ GRUB
+  boot = {
+    # Add Required Kernel Modules
+    # NOTE: These are not documented in the install guide
+    initrd.availableKernelModules = [
+      "virtio_pci"
+      "virtio_scsi"
+      "ahci"
+      "sd_mod"
+    ];
+
+    # Set Up LISH Serial Connection
+    kernelParams = [ "console=ttyS0,19200n8" ];
+    kernelModules = [ "virtio_net" ];
+
+    loader = {
+      # Increase Timeout to Allow LISH Connection
+      # NOTE: The image generator tries to set a timeout of 0, so we must force
+      timeout = lib.mkForce 10;
+
+      grub = {
+        enable = true;
+        version = 2;
+        forceInstall = true;
+        device = "nodev";
+
+        # Allow serial connection for GRUB to be able to use LISH
+        extraConfig = ''
+          serial --speed=19200 --unit=0 --word=8 --parity=no --stop=1;
+          terminal_input serial;
+          terminal_output serial
+        '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/linode-image.nix b/nixpkgs/nixos/modules/virtualisation/linode-image.nix
new file mode 100644
index 000000000000..51f793ac011d
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/linode-image.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.virtualisation.linodeImage;
+  defaultConfigFile = pkgs.writeText "configuration.nix" ''
+    _: {
+      imports = [
+        <nixpkgs/nixos/modules/virtualisation/linode-image.nix>
+      ];
+    }
+  '';
+in
+{
+  imports = [ ./linode-config.nix ];
+
+  options = {
+    virtualisation.linodeImage.diskSize = mkOption {
+      type = with types; either (enum (singleton "auto")) ints.positive;
+      default = "auto";
+      example = 1536;
+      description = ''
+        Size of disk image in MB.
+      '';
+    };
+
+    virtualisation.linodeImage.configFile = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      description = ''
+        A path to a configuration file which will be placed at `/etc/nixos/configuration.nix`
+        and be used when switching to a new configuration.
+        If set to `null`, a default configuration is used, where the only import is
+        `<nixpkgs/nixos/modules/virtualisation/linode-image.nix>`
+      '';
+    };
+
+    virtualisation.linodeImage.compressionLevel = mkOption {
+      type = types.ints.between 1 9;
+      default = 6;
+      description = ''
+        GZIP compression level of the resulting disk image (1-9).
+      '';
+    };
+  };
+
+  config = {
+    system.build.linodeImage = import ../../lib/make-disk-image.nix {
+      name = "linode-image";
+      # NOTE: Linode specifically requires images to be `gzip`-ed prior to upload
+      # See: https://www.linode.com/docs/products/tools/images/guides/upload-an-image/#requirements-and-considerations
+      postVM = ''
+        ${pkgs.gzip}/bin/gzip -${toString cfg.compressionLevel} -c -- $diskImage > \
+        $out/nixos-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img.gz
+        rm $diskImage
+      '';
+      format = "raw";
+      partitionTableType = "none";
+      configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile;
+      inherit (cfg) diskSize;
+      inherit config lib pkgs;
+    };
+  };
+
+  meta.maintainers = with maintainers; [ cyntheticfox ];
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
index d3a2e0ed151d..55b285b69147 100644
--- a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
@@ -5,22 +5,22 @@ with lib;
 let
   templateSubmodule = { ... }: {
     options = {
-      enable = mkEnableOption "this template";
+      enable = mkEnableOption (lib.mdDoc "this template");
 
       target = mkOption {
-        description = "Path in the container";
+        description = lib.mdDoc "Path in the container";
         type = types.path;
       };
       template = mkOption {
-        description = ".tpl file for rendering the target";
+        description = lib.mdDoc ".tpl file for rendering the target";
         type = types.path;
       };
       when = mkOption {
-        description = "Events which trigger a rewrite (create, copy)";
+        description = lib.mdDoc "Events which trigger a rewrite (create, copy)";
         type = types.listOf (types.str);
       };
       properties = mkOption {
-        description = "Additional properties";
+        description = lib.mdDoc "Additional properties";
         type = types.attrs;
         default = {};
       };
@@ -51,14 +51,14 @@ in
 {
   imports = [
     ../installer/cd-dvd/channel.nix
-    ../profiles/minimal.nix
     ../profiles/clone-config.nix
+    ../profiles/minimal.nix
   ];
 
   options = {
     virtualisation.lxc = {
       templates = mkOption {
-        description = "Templates for LXD";
+        description = lib.mdDoc "Templates for LXD";
         type = types.attrsOf (types.submodule (templateSubmodule));
         default = {};
         example = literalExpression ''
@@ -88,6 +88,16 @@ in
           };
         '';
       };
+
+      privilegedContainer = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether this LXC container will be running as a privileged container or not. If set to `true` then
+          additional configuration will be applied to the `systemd` instance running within the container as
+          recommended by [distrobuilder](https://linuxcontainers.org/distrobuilder/introduction/).
+        '';
+      };
     };
   };
 
@@ -113,8 +123,8 @@ in
             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";
+              description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
+              os = "${config.system.nixos.distroId}";
               release = "${config.system.nixos.codeName}";
             };
             templates = templates.properties;
@@ -140,19 +150,49 @@ in
           source = config.system.build.toplevel + "/init";
           target = "/sbin/init";
         }
+        # Technically this is not required for lxc, but having also make this configuration work with systemd-nspawn.
+        # Nixos will setup the same symlink after start.
+        {
+          source = config.system.build.toplevel + "/etc/os-release";
+          target = "/etc/os-release";
+        }
       ];
 
       extraCommands = "mkdir -p proc sys dev";
     };
 
-    # Add the overrides from lxd distrobuilder
-    systemd.extraConfig = ''
-      [Service]
-      ProtectProc=default
-      ProtectControlGroups=no
-      ProtectKernelTunables=no
+    system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
+      #!${pkgs.runtimeShell}
+      ln -fs "$1/init" /sbin/init
     '';
 
+    # Add the overrides from lxd distrobuilder
+    # https://github.com/lxc/distrobuilder/blob/05978d0d5a72718154f1525c7d043e090ba7c3e0/distrobuilder/main.go#L630
+    systemd.packages = [
+      (pkgs.writeTextFile {
+        name = "systemd-lxc-service-overrides";
+        destination = "/etc/systemd/system/service.d/zzz-lxc-service.conf";
+        text = ''
+          [Service]
+          ProcSubset=all
+          ProtectProc=default
+          ProtectControlGroups=no
+          ProtectKernelTunables=no
+          NoNewPrivileges=no
+          LoadCredential=
+        '' + optionalString cfg.privilegedContainer ''
+          # Additional settings for privileged containers
+          ProtectHome=no
+          ProtectSystem=no
+          PrivateDevices=no
+          PrivateTmp=no
+          ProtectKernelLogs=no
+          ProtectKernelModules=no
+          ReadWritePaths=
+        '';
+      })
+    ];
+
     # Allow the user to login as root without password.
     users.users.root.initialHashedPassword = mkOverride 150 "";
 
@@ -170,5 +210,11 @@ in
     # Containers should be light-weight, so start sshd on demand.
     services.openssh.enable = mkDefault true;
     services.openssh.startWhenNeeded = mkDefault true;
+
+    # As this is intended as a standalone image, undo some of the minimal profile stuff
+    environment.noXlibs = false;
+    documentation.enable = true;
+    documentation.nixos.enable = true;
+    services.logrotate.enable = true;
   };
 }
diff --git a/nixpkgs/nixos/modules/virtualisation/lxc.nix b/nixpkgs/nixos/modules/virtualisation/lxc.nix
index d7bbc3f8414c..5bd64a5f9a56 100644
--- a/nixpkgs/nixos/modules/virtualisation/lxc.nix
+++ b/nixpkgs/nixos/modules/virtualisation/lxc.nix
@@ -53,10 +53,9 @@ in
         type = types.lines;
         default = "";
         description =
-          ''
+          lib.mdDoc ''
             This is the config file for managing unprivileged user network
-            administration access in LXC. See <citerefentry><refentrytitle>lxc-usernet</refentrytitle><manvolnum>5</manvolnum>
-            </citerefentry>.
+            administration access in LXC. See {manpage}`lxc-usernet(5)`.
           '';
       };
   };
diff --git a/nixpkgs/nixos/modules/virtualisation/lxcfs.nix b/nixpkgs/nixos/modules/virtualisation/lxcfs.nix
index b2457403463a..fb0ba49f7304 100644
--- a/nixpkgs/nixos/modules/virtualisation/lxcfs.nix
+++ b/nixpkgs/nixos/modules/virtualisation/lxcfs.nix
@@ -15,13 +15,13 @@ in {
       mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This enables LXCFS, a FUSE filesystem for LXC.
           To use lxcfs in include the following configuration in your
           container configuration:
-          <code>
-            virtualisation.lxc.defaultConfig = "lxc.include = ''${pkgs.lxcfs}/share/lxc/config/common.conf.d/00-lxcfs.conf";
-          </code>
+          ```
+          virtualisation.lxc.defaultConfig = "lxc.include = ''${pkgs.lxcfs}/share/lxc/config/common.conf.d/00-lxcfs.conf";
+          ```
         '';
       };
   };
diff --git a/nixpkgs/nixos/modules/virtualisation/lxd.nix b/nixpkgs/nixos/modules/virtualisation/lxd.nix
index f1eabee5ffaf..c06716e5eb60 100644
--- a/nixpkgs/nixos/modules/virtualisation/lxd.nix
+++ b/nixpkgs/nixos/modules/virtualisation/lxd.nix
@@ -18,17 +18,17 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option enables lxd, a daemon that manages
           containers. Users in the "lxd" group can interact with
           the daemon (e.g. to start or stop containers) using the
-          <command>lxc</command> command line tool, among others.
+          {command}`lxc` command line tool, among others.
 
           Most of the time, you'll also want to start lxcfs, so
           that containers can "see" the limits:
-          <code>
-            virtualisation.lxc.lxcfs.enable = true;
-          </code>
+          ```
+          virtualisation.lxc.lxcfs.enable = true;
+          ```
         '';
       };
 
@@ -129,11 +129,19 @@ in {
       description = "LXD Container Management Daemon";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "network-online.target" "lxcfs.service" ];
-      requires = [ "network-online.target" "lxd.socket"  "lxcfs.service" ];
+      after = [
+        "network-online.target"
+        (mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
+      ];
+      requires = [
+        "network-online.target"
+        "lxd.socket"
+        (mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
+      ];
       documentation = [ "man:lxd(1)" ];
 
-      path = optional cfg.zfsSupport config.boot.zfs.package;
+      path = [ pkgs.util-linux ]
+        ++ optional cfg.zfsSupport config.boot.zfs.package;
 
       serviceConfig = {
         ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd";
diff --git a/nixpkgs/nixos/modules/virtualisation/multipass.nix b/nixpkgs/nixos/modules/virtualisation/multipass.nix
new file mode 100644
index 000000000000..b331b3be7ea5
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/multipass.nix
@@ -0,0 +1,61 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.virtualisation.multipass;
+in
+{
+  options = {
+    virtualisation.multipass = {
+      enable = lib.mkEnableOption (lib.mdDoc ''
+        Multipass, a simple manager for virtualised Ubuntu instances.
+      '');
+
+      logLevel = lib.mkOption {
+        type = lib.types.enum [ "error" "warning" "info" "debug" "trace" ];
+        default = "debug";
+        description = lib.mdDoc ''
+          The logging verbosity of the multipassd binary.
+        '';
+      };
+
+      package = lib.mkPackageOptionMD pkgs "multipass" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.multipass = {
+      description = "Multipass orchestrates virtual Ubuntu instances.";
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      environment = {
+        "XDG_DATA_HOME" = "/var/lib/multipass/data";
+        "XDG_CACHE_HOME" = "/var/lib/multipass/cache";
+        "XDG_CONFIG_HOME" = "/var/lib/multipass/config";
+      };
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/multipassd --logger platform --verbosity ${cfg.logLevel}";
+        SyslogIdentifier = "multipassd";
+        Restart = "on-failure";
+        TimeoutStopSec = 300;
+        Type = "simple";
+
+        WorkingDirectory = "/var/lib/multipass";
+
+        StateDirectory = "multipass";
+        StateDirectoryMode = "0750";
+        CacheDirectory = "multipass";
+        CacheDirectoryMode = "0750";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix b/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix
index 1285563c6463..c3949564d4bd 100644
--- a/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }@host:
 
 with lib;
 
@@ -9,6 +9,10 @@ let
   configurationDirectory = "/etc/${configurationDirectoryName}";
   stateDirectory = "/var/lib/${configurationPrefix}containers";
 
+  nixos-container = pkgs.nixos-container.override {
+    inherit stateDirectory configurationDirectory;
+  };
+
   # The container's init script, a small wrapper around the regular
   # NixOS stage-2 init script.
   containerInit = (cfg:
@@ -138,6 +142,8 @@ let
         fi
       ''}
 
+      export SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=1
+
       # Run systemd-nspawn without startup notification (we'll
       # wait for the container systemd to signal readiness)
       # Kill signal handling means systemd-nspawn will pass a system-halt signal
@@ -164,11 +170,11 @@ let
         --setenv HOST_PORT="$HOST_PORT" \
         --setenv PATH="$PATH" \
         ${optionalString cfg.ephemeral "--ephemeral"} \
-        ${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then
-          ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"'' else ""
+        ${optionalString (cfg.additionalCapabilities != null && cfg.additionalCapabilities != [])
+          ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"''
         } \
-        ${if cfg.tmpfs != null && cfg.tmpfs != [] then
-          ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else ""
+        ${optionalString (cfg.tmpfs != null && cfg.tmpfs != [])
+          ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}''
         } \
         ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
     '';
@@ -248,7 +254,7 @@ let
     ExecReload = pkgs.writeScript "reload-container"
       ''
         #! ${pkgs.runtimeShell} -e
-        ${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \
+        ${nixos-container}/bin/nixos-container run "$INSTANCE" -- \
           bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test"
       '';
 
@@ -284,7 +290,6 @@ let
     DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
   };
 
-  inherit (config.nixpkgs) localSystem;
   kernelVersion = config.boot.kernelPackages.kernel.version;
 
   bindMountOpts = { name, ... }: {
@@ -480,10 +485,13 @@ in
                 merge = loc: defs: (import "${toString config.nixpkgs}/nixos/lib/eval-config.nix" {
                   modules =
                     let
-                      extraConfig = {
+                      extraConfig = { options, ... }: {
                         _file = "module at ${__curPos.file}:${toString __curPos.line}";
                         config = {
-                          nixpkgs = { inherit localSystem; };
+                          nixpkgs = if options.nixpkgs?hostPlatform && host.options.nixpkgs.hostPlatform.isDefined
+                                    then { inherit (host.config.nixpkgs) hostPlatform; }
+                                    else { inherit (host.config.nixpkgs) localSystem; }
+                          ;
                           boot.isContainer = true;
                           networking.hostName = mkDefault name;
                           networking.useDHCP = false;
@@ -506,6 +514,11 @@ in
                       };
                     in [ extraConfig ] ++ (map (x: x.value) defs);
                   prefix = [ "containers" name ];
+                  inherit (config) specialArgs;
+
+                  # The system is inherited from the host above.
+                  # Set it to null, to remove the "legacy" entrypoint's non-hermetic default.
+                  system = null;
                 }).config;
               };
             };
@@ -536,21 +549,31 @@ in
               type = types.path;
               default = pkgs.path;
               defaultText = literalExpression "pkgs.path";
-              description = ''
+              description = lib.mdDoc ''
                 A path to the nixpkgs that provide the modules, pkgs and lib for evaluating the container.
 
-                To only change the <literal>pkgs</literal> argument used inside the container modules,
-                set the <literal>nixpkgs.*</literal> options in the container <option>config</option>.
-                Setting <literal>config.nixpkgs.pkgs = pkgs</literal> speeds up the container evaluation
-                by reusing the system pkgs, but the <literal>nixpkgs.config</literal> option in the
+                To only change the `pkgs` argument used inside the container modules,
+                set the `nixpkgs.*` options in the container {option}`config`.
+                Setting `config.nixpkgs.pkgs = pkgs` speeds up the container evaluation
+                by reusing the system pkgs, but the `nixpkgs.config` option in the
                 container config is ignored in this case.
               '';
             };
 
+            specialArgs = mkOption {
+              type = types.attrsOf types.unspecified;
+              default = {};
+              description = lib.mdDoc ''
+                A set of special arguments to be passed to NixOS modules.
+                This will be merged into the `specialArgs` used to evaluate
+                the NixOS configurations.
+              '';
+            };
+
             ephemeral = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Runs container in ephemeral mode with the empty root filesystem at boot.
                 This way container will be bootstrapped from scratch on each boot
                 and will be cleaned up on shutdown leaving no traces behind.
@@ -558,8 +581,8 @@ in
 
                 Note that this option might require to do some adjustments to the container configuration,
                 e.g. you might want to set
-                <varname>systemd.network.networks.$interface.dhcpV4Config.ClientIdentifier</varname> to "mac"
-                if you use <varname>macvlans</varname> option.
+                {var}`systemd.network.networks.$interface.dhcpV4Config.ClientIdentifier` to "mac"
+                if you use {var}`macvlans` option.
                 This way dhcp client identifier will be stable between the container restarts.
 
                 Note that the container journal will not be linked to the host if this option is enabled.
@@ -720,7 +743,7 @@ in
               { config =
                   { config, pkgs, ... }:
                   { services.postgresql.enable = true;
-                    services.postgresql.package = pkgs.postgresql_10;
+                    services.postgresql.package = pkgs.postgresql_14;
 
                     system.stateVersion = "21.05";
                   };
@@ -864,9 +887,7 @@ in
     '';
 
     environment.systemPackages = [
-      (pkgs.nixos-container.override {
-        inherit stateDirectory configurationDirectory;
-      })
+      nixos-container
     ];
 
     boot.kernelModules = [
diff --git a/nixpkgs/nixos/modules/virtualisation/oci-containers.nix b/nixpkgs/nixos/modules/virtualisation/oci-containers.nix
index 81cdf1dd72b4..a9f4ab77f866 100644
--- a/nixpkgs/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixpkgs/nixos/modules/virtualisation/oci-containers.nix
@@ -227,9 +227,13 @@ let
 
   mkService = name: container: let
     dependsOn = map (x: "${cfg.backend}-${x}.service") container.dependsOn;
+    escapedName = escapeShellArg name;
   in {
     wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
-    after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ] ++ dependsOn;
+    after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ]
+            # if imageFile is not set, the service needs the network to download the image from the registry
+            ++ lib.optionals (container.imageFile == null) [ "network-online.target" ]
+            ++ dependsOn;
     requires = dependsOn;
     environment = proxy_env;
 
@@ -250,16 +254,25 @@ let
       ${optionalString (container.imageFile != null) ''
         ${cfg.backend} load -i ${container.imageFile}
         ''}
+      ${optionalString (cfg.backend == "podman") ''
+        rm -f /run/podman-${escapedName}.ctr-id
+        ''}
       '';
 
     script = concatStringsSep " \\\n  " ([
       "exec ${cfg.backend} run"
       "--rm"
-      "--name=${escapeShellArg name}"
+      "--name=${escapedName}"
       "--log-driver=${container.log-driver}"
     ] ++ optional (container.entrypoint != null)
       "--entrypoint=${escapeShellArg container.entrypoint}"
-      ++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
+      ++ lib.optionals (cfg.backend == "podman") [
+        "--cidfile=/run/podman-${escapedName}.ctr-id"
+        "--cgroups=no-conmon"
+        "--sdnotify=conmon"
+        "-d"
+        "--replace"
+      ] ++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
       ++ map (f: "--env-file ${escapeShellArg f}") container.environmentFiles
       ++ map (p: "-p ${escapeShellArg p}") container.ports
       ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
@@ -270,8 +283,12 @@ let
       ++ map escapeShellArg container.cmd
     );
 
-    preStop = "[ $SERVICE_RESULT = success ] || ${cfg.backend} stop ${name}";
-    postStop = "${cfg.backend} rm -f ${name} || true";
+    preStop = if cfg.backend == "podman"
+      then "[ $SERVICE_RESULT = success ] || podman stop --ignore --cidfile=/run/podman-${escapedName}.ctr-id"
+      else "[ $SERVICE_RESULT = success ] || ${cfg.backend} stop ${name}";
+    postStop =  if cfg.backend == "podman"
+      then "podman rm -f --ignore --cidfile=/run/podman-${escapedName}.ctr-id"
+      else "${cfg.backend} rm -f ${name} || true";
 
     serviceConfig = {
       ### There is no generalized way of supporting `reload` for docker
@@ -293,6 +310,10 @@ let
       TimeoutStartSec = 0;
       TimeoutStopSec = 120;
       Restart = "always";
+    } // optionalAttrs (cfg.backend == "podman") {
+      Environment="PODMAN_SYSTEMD_UNIT=podman-${name}.service";
+      Type="notify";
+      NotifyAccess="all";
     };
   };
 
diff --git a/nixpkgs/nixos/modules/virtualisation/openstack-config.nix b/nixpkgs/nixos/modules/virtualisation/openstack-config.nix
index af4f57466109..0ef7a3b50106 100644
--- a/nixpkgs/nixos/modules/virtualisation/openstack-config.nix
+++ b/nixpkgs/nixos/modules/virtualisation/openstack-config.nix
@@ -59,8 +59,8 @@ in
     # Allow root logins
     services.openssh = {
       enable = true;
-      permitRootLogin = "prohibit-password";
-      passwordAuthentication = mkDefault false;
+      settings.PermitRootLogin = "prohibit-password";
+      settings.PasswordAuthentication = mkDefault false;
     };
 
     users.users.root.initialPassword = "foobar";
diff --git a/nixpkgs/nixos/modules/virtualisation/openstack-options.nix b/nixpkgs/nixos/modules/virtualisation/openstack-options.nix
index eded418c9c91..52f45de92ecb 100644
--- a/nixpkgs/nixos/modules/virtualisation/openstack-options.nix
+++ b/nixpkgs/nixos/modules/virtualisation/openstack-options.nix
@@ -9,13 +9,13 @@ in
         enable = lib.mkOption {
           default = false;
           internal = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether the OpenStack instance uses a ZFS root.
           '';
         };
 
         datasets = lib.mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Datasets to create under the `tank` and `boot` zpools.
 
             **NOTE:** This option is used only at image creation time, and
@@ -29,13 +29,13 @@ in
             options = {
               mount = lib.mkOption {
                 description = lib.mdDoc "Where to mount this dataset.";
-                type = types.nullOr types.string;
+                type = types.nullOr types.str;
                 default = null;
               };
 
               properties = lib.mkOption {
                 description = lib.mdDoc "Properties to set on this dataset.";
-                type = types.attrsOf types.string;
+                type = types.attrsOf types.str;
                 default = { };
               };
             };
@@ -47,7 +47,7 @@ in
         default = pkgs.stdenv.hostPlatform.isAarch64;
         defaultText = literalExpression "pkgs.stdenv.hostPlatform.isAarch64";
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the instance is using EFI.
         '';
       };
diff --git a/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix b/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix
index d3affe2b8f2c..dba8ce02b724 100644
--- a/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix
+++ b/nixpkgs/nixos/modules/virtualisation/parallels-guest.nix
@@ -23,7 +23,7 @@ in
       autoMountShares = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Control prlfsmountd service. When this service is running, shares can not be manually
           mounted through `mount -t prl_fs ...` as this service will remount and trample any set options.
           Recommended to enable for simple file sharing, but extended share use such as for code should
@@ -87,7 +87,6 @@ in
       bindsTo = [ "cups.service" ];
       path = [ prl-tools ];
       serviceConfig = {
-        Type = "forking";
         ExecStart = "${prl-tools}/bin/prlshprint";
         WorkingDirectory = "${prl-tools}/bin";
       };
diff --git a/nixpkgs/nixos/modules/virtualisation/podman/default.nix b/nixpkgs/nixos/modules/virtualisation/podman/default.nix
index ccf30a0ff663..c3fae4bac41b 100644
--- a/nixpkgs/nixos/modules/virtualisation/podman/default.nix
+++ b/nixpkgs/nixos/modules/virtualisation/podman/default.nix
@@ -1,13 +1,14 @@
 { config, lib, pkgs, ... }:
 let
   cfg = config.virtualisation.podman;
-  toml = pkgs.formats.toml { };
   json = pkgs.formats.json { };
 
   inherit (lib) mkOption types;
 
   podmanPackage = (pkgs.podman.override {
     extraPackages = cfg.extraPackages
+      # setuid shadow
+      ++ [ "/run/wrappers" ]
       ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
   });
 
@@ -27,26 +28,14 @@ let
     done
   '';
 
-  net-conflist = pkgs.runCommand "87-podman-bridge.conflist"
-    {
-      nativeBuildInputs = [ pkgs.jq ];
-      extraPlugins = builtins.toJSON cfg.defaultNetwork.extraPlugins;
-      jqScript = ''
-        . + { "plugins": (.plugins + $extraPlugins) }
-      '';
-    } ''
-    jq <${cfg.package}/etc/cni/net.d/87-podman-bridge.conflist \
-      --argjson extraPlugins "$extraPlugins" \
-      "$jqScript" \
-      >$out
-  '';
-
 in
 {
   imports = [
-    ./dnsname.nix
+    (lib.mkRemovedOptionModule [ "virtualisation" "podman" "defaultNetwork" "dnsname" ]
+      "Use virtualisation.podman.defaultNetwork.settings.dns_enabled instead.")
+    (lib.mkRemovedOptionModule [ "virtualisation" "podman" "defaultNetwork" "extraPlugins" ]
+      "Netavark isn't compatible with CNI plugins.")
     ./network-socket.nix
-    (lib.mkRenamedOptionModule [ "virtualisation" "podman" "libpod" ] [ "virtualisation" "containers" "containersConf" ])
   ];
 
   meta = {
@@ -110,35 +99,82 @@ in
       '';
     };
 
+    autoPrune = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to periodically prune Podman resources. If enabled, a
+          systemd timer will run `podman system prune -f`
+          as specified by the `dates` option.
+        '';
+      };
+
+      flags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--all" ];
+        description = lib.mdDoc ''
+          Any additional flags passed to {command}`podman system prune`.
+        '';
+      };
+
+      dates = mkOption {
+        default = "weekly";
+        type = types.str;
+        description = lib.mdDoc ''
+          Specification (in the format described by
+          {manpage}`systemd.time(7)`) of the time at
+          which the prune will occur.
+        '';
+      };
+    };
+
     package = lib.mkOption {
       type = types.package;
       default = podmanPackage;
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         The final Podman package (including extra packages).
       '';
     };
 
-    defaultNetwork.extraPlugins = lib.mkOption {
-      type = types.listOf json.type;
-      default = [ ];
+    defaultNetwork.settings = lib.mkOption {
+      type = json.type;
+      default = { };
+      example = lib.literalExpression "{ dns_enabled = true; }";
       description = lib.mdDoc ''
-        Extra CNI plugin configurations to add to podman's default network.
+        Settings for podman's default network.
       '';
     };
 
   };
 
-  config = lib.mkIf cfg.enable (lib.mkMerge [
+  config = lib.mkIf cfg.enable
     {
       environment.systemPackages = [ cfg.package ]
         ++ lib.optional cfg.dockerCompat dockerCompat;
 
-      environment.etc."cni/net.d/87-podman-bridge.conflist".source = net-conflist;
+      # https://github.com/containers/podman/blob/097cc6eb6dd8e598c0e8676d21267b4edb11e144/docs/tutorials/basic_networking.md#default-network
+      environment.etc."containers/networks/podman.json" = lib.mkIf (cfg.defaultNetwork.settings != { }) {
+        source = json.generate "podman.json" ({
+          dns_enabled = false;
+          driver = "bridge";
+          id = "0000000000000000000000000000000000000000000000000000000000000000";
+          internal = false;
+          ipam_options = { driver = "host-local"; };
+          ipv6_enabled = false;
+          name = "podman";
+          network_interface = "podman0";
+          subnets = [{ gateway = "10.88.0.1"; subnet = "10.88.0.0/16"; }];
+        } // cfg.defaultNetwork.settings);
+      };
 
       virtualisation.containers = {
         enable = true; # Enable common /etc/containers configuration
-        containersConf.settings = lib.optionalAttrs cfg.enableNvidia {
+        containersConf.settings = {
+          network.network_backend = "netavark";
+        } // lib.optionalAttrs cfg.enableNvidia {
           engine = {
             conmon_env_vars = [ "PATH=${lib.makeBinPath [ pkgs.nvidia-podman ]}" ];
             runtimes.nvidia = [ "${pkgs.nvidia-podman}/bin/nvidia-container-runtime" ];
@@ -148,17 +184,26 @@ in
 
       systemd.packages = [ cfg.package ];
 
-      systemd.services.podman.serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
+      systemd.services.podman-prune = {
+        description = "Prune podman resources";
+
+        restartIfChanged = false;
+        unitConfig.X-StopOnRemoval = false;
+
+        serviceConfig.Type = "oneshot";
+
+        script = ''
+          ${cfg.package}/bin/podman system prune -f ${toString cfg.autoPrune.flags}
+        '';
+
+        startAt = lib.optional cfg.autoPrune.enable cfg.autoPrune.dates;
+        after = [ "podman.service" ];
+        requires = [ "podman.service" ];
       };
 
       systemd.sockets.podman.wantedBy = [ "sockets.target" ];
       systemd.sockets.podman.socketConfig.SocketGroup = "podman";
 
-      systemd.user.services.podman.serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
-      };
-
       systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
 
       systemd.tmpfiles.packages = [
@@ -191,6 +236,5 @@ in
           '';
         }
       ];
-    }
-  ]);
+    };
 }
diff --git a/nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix b/nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix
deleted file mode 100644
index 3e7d35ae1e44..000000000000
--- a/nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix
+++ /dev/null
@@ -1,36 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
-  inherit (lib)
-    mkOption
-    mkIf
-    types
-    ;
-
-  cfg = config.virtualisation.podman;
-
-in
-{
-  options = {
-    virtualisation.podman = {
-
-      defaultNetwork.dnsname.enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable DNS resolution in the default podman network.
-        '';
-      };
-
-    };
-  };
-
-  config = {
-    virtualisation.containers.containersConf.cniPlugins = mkIf cfg.defaultNetwork.dnsname.enable [ pkgs.dnsname-cni ];
-    virtualisation.podman.defaultNetwork.extraPlugins =
-      lib.optional cfg.defaultNetwork.dnsname.enable {
-        type = "dnsname";
-        domainName = "dns.podman";
-        capabilities.aliases = true;
-      };
-  };
-}
diff --git a/nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix b/nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix
index 523cbd00715b..a10597175ab9 100644
--- a/nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix
+++ b/nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix
@@ -17,16 +17,16 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Make the Podman and Docker compatibility API available over the network
         with TLS client certificate authentication.
 
         This allows Docker clients to connect with the equivalents of the Docker
-        CLI <literal>-H</literal> and <literal>--tls*</literal> family of options.
+        CLI `-H` and `--tls*` family of options.
 
         For certificate setup, see https://docs.docker.com/engine/security/protect-access/
 
-        This option is independent of <xref linkend="opt-virtualisation.podman.dockerSocket.enable"/>.
+        This option is independent of [](#opt-virtualisation.podman.dockerSocket.enable).
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/virtualisation/proxmox-image.nix b/nixpkgs/nixos/modules/virtualisation/proxmox-image.nix
index e07d1d1eb3fc..b5d4ecd02683 100644
--- a/nixpkgs/nixos/modules/virtualisation/proxmox-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/proxmox-image.nix
@@ -10,7 +10,7 @@ with lib;
         type = types.str;
         default = "";
         example = "order=scsi0;net0";
-        description = ''
+        description = lib.mdDoc ''
           Default boot device. PVE will try all devices in its default order if this value is empty.
         '';
       };
@@ -18,54 +18,61 @@ with lib;
         type = types.str;
         default = "virtio-scsi-pci";
         example = "lsi";
-        description = ''
+        description = lib.mdDoc ''
           SCSI controller type. Must be one of the supported values given in
-          <link xlink:href="https://pve.proxmox.com/wiki/Qemu/KVM_Virtual_Machines"/>
+          <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.
+        description = lib.mdDoc ''
+          Configuration for the default virtio disk. It can be used as a cue for PVE to autodetect the target storage.
           This parameter is required by PVE even if it isn't used.
         '';
       };
       ostype = mkOption {
         type = types.str;
         default = "l26";
-        description = ''
+        description = lib.mdDoc ''
           Guest OS type
         '';
       };
       cores = mkOption {
         type = types.ints.positive;
         default = 1;
-        description = ''
+        description = lib.mdDoc ''
           Guest core count
         '';
       };
       memory = mkOption {
         type = types.ints.positive;
         default = 1024;
-        description = ''
+        description = lib.mdDoc ''
           Guest memory in MB
         '';
       };
+      bios = mkOption {
+        type = types.enum [ "seabios" "ovmf" ];
+        default = "seabios";
+        description = ''
+          Select BIOS implementation (seabios = Legacy BIOS, ovmf = UEFI).
+        '';
+      };
 
       # optional configs
       name = mkOption {
         type = types.str;
         default = "nixos-${config.system.nixos.label}";
-        description = ''
+        description = lib.mdDoc ''
           VM name
         '';
       };
       net0 = mkOption {
         type = types.commas;
         default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1";
-        description = ''
+        description = lib.mdDoc ''
           Configuration for the default interface. When restoring from VMA, check the
           "unique" box to ensure device mac is randomized.
         '';
@@ -74,7 +81,7 @@ with lib;
         type = types.str;
         default = "socket";
         example = "/dev/ttyS0";
-        description = ''
+        description = lib.mdDoc ''
           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).
         '';
@@ -83,7 +90,7 @@ with lib;
         type = types.bool;
         apply = x: if x then "1" else "0";
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Expect guest to have qemu agent running
         '';
       };
@@ -95,15 +102,26 @@ with lib;
         cpu = "host";
         onboot = 1;
       }'';
-      description = ''
+      description = lib.mdDoc ''
         Additional options appended to qemu-server.conf
       '';
     };
+    partitionTableType = mkOption {
+      type = types.enum [ "efi" "hybrid" "legacy" "legacy+gpt" ];
+      description = ''
+        Partition table type to use. See make-disk-image.nix partitionTableType for details.
+        Defaults to 'legacy' for 'proxmox.qemuConf.bios="seabios"' (default), other bios values defaults to 'efi'.
+        Use 'hybrid' to build grub-based hybrid bios+efi images.
+      '';
+      default = if config.proxmox.qemuConf.bios == "seabios" then "legacy" else "efi";
+      defaultText = lib.literalExpression ''if config.proxmox.qemuConf.bios == "seabios" then "legacy" else "efi"'';
+      example = "hybrid";
+    };
     filenameSuffix = mkOption {
       type = types.str;
       default = config.proxmox.qemuConf.name;
       example = "999-nixos_template";
-      description = ''
+      description = lib.mdDoc ''
         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
@@ -117,35 +135,92 @@ with lib;
     cfgLine = name: value: ''
       ${name}: ${builtins.toString value}
     '';
+    virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0);
     cfgFile = fileName: properties: pkgs.writeTextDir fileName ''
       # generated by NixOS
       ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)}
-      #qmdump#map:virtio0:drive-virtio0:local-lvm:raw:
+      #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw:
     '';
+    inherit (cfg) partitionTableType;
+    supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid";
+    supportBios = partitionTableType == "legacy" || partitionTableType == "hybrid" || partitionTableType == "legacy+gpt";
+    hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid";
+    hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt";
   in {
+    assertions = [
+      {
+        assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf";
+        message = "systemd-boot requires 'ovmf' bios";
+      }
+      {
+        assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf";
+        message = "'efi' disk partitioning requires 'ovmf' bios";
+      }
+      {
+        assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios";
+        message = "'legacy' disk partitioning requires 'seabios' bios";
+      }
+      {
+        assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios";
+        message = "'legacy+gpt' disk partitioning requires 'seabios' bios";
+      }
+    ];
     system.build.VMA = import ../../lib/make-disk-image.nix {
       name = "proxmox-${cfg.filenameSuffix}";
+      inherit partitionTableType;
       postVM = let
         # Build qemu with PVE's patch that adds support for the VMA format
-        vma = pkgs.qemu_kvm.overrideAttrs ( super: rec {
+        vma = (pkgs.qemu_kvm.override {
+          alsaSupport = false;
+          pulseSupport = false;
+          sdlSupport = false;
+          jackSupport = false;
+          gtkSupport = false;
+          vncSupport = false;
+          smartcardSupport = false;
+          spiceSupport = false;
+          ncursesSupport = false;
+          libiscsiSupport = false;
+          tpmSupport = false;
+          numaSupport = false;
+          seccompSupport = false;
+          guestAgentSupport = false;
+        }).overrideAttrs ( super: rec {
 
-          # proxmox's VMA patch doesn't work with qemu 7.0 yet
-          version = "6.2.0";
+          version = "7.2.1";
           src = pkgs.fetchurl {
             url= "https://download.qemu.org/qemu-${version}.tar.xz";
-            hash = "sha256-aOFdjkWsVjJuC5pK+otJo9/oq6NIgiHQmMhGmLymW0U=";
+            sha256 = "sha256-jIVpms+dekOl/immTN1WNwsMLRrQdLr3CYqCTReq1zs=";
           };
+          patches = [
+            # Proxmox' VMA tool is published as a particular patch upon QEMU
+            (pkgs.fetchpatch {
+              url =
+                let
+                  rev = "abb04bb6272c1202ca9face0827917552b9d06f6";
+                  path = "debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch";
+                in "https://git.proxmox.com/?p=pve-qemu.git;a=blob_plain;hb=${rev};f=${path}";
+              hash = "sha256-3d0HHdvaExCry6zcULnziYnWIAnn24vECkI4sjj2BMg=";
+            })
 
-          patches = let
-            rev = "b37b17c286da3d32945fbee8ee4fd97a418a50db";
-            path = "debian/patches/pve/0026-PVE-Backup-add-vma-backup-format-code.patch";
-            vma-patch = pkgs.fetchpatch {
-              url = "https://git.proxmox.com/?p=pve-qemu.git;a=blob_plain;h=${rev};f=${path}";
-              hash = "sha256-siuDWDUnM9Zq0/L2Faww3ELAOUHhVIHu5RAQn6L4Atc=";
-            };
-          in [ vma-patch ];
+            # Proxmox' VMA tool uses O_DIRECT which fails on tmpfs
+            # Filed to upstream issue tracker: https://bugzilla.proxmox.com/show_bug.cgi?id=4710
+            (pkgs.writeText "inline.patch" ''
+                --- a/vma-writer.c   2023-05-01 15:11:13.361341177 +0200
+                +++ b/vma-writer.c   2023-05-01 15:10:51.785293129 +0200
+                @@ -306,7 +306,7 @@
+                             /* try to use O_NONBLOCK */
+                             fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
+                         } else  {
+                -            oflags = O_NONBLOCK|O_DIRECT|O_WRONLY|O_EXCL;
+                +            oflags = O_NONBLOCK|O_WRONLY|O_EXCL;
+                             vmaw->fd = qemu_create(filename, oflags, 0644, errp);
+                         }
+            '')
+          ];
 
           buildInputs = super.buildInputs ++ [ pkgs.libuuid ];
+          nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ];
 
         });
       in
@@ -155,6 +230,9 @@ with lib;
         rm $diskImage
         ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma"
         mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/
+
+        mkdir -p $out/nix-support
+        echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" >> $out/nix-support/hydra-build-products
       '';
       format = "raw";
       inherit config lib pkgs;
@@ -163,7 +241,18 @@ with lib;
     boot = {
       growPartition = true;
       kernelParams = [ "console=ttyS0" ];
-      loader.grub.device = lib.mkDefault "/dev/vda";
+      loader.grub = {
+        device = lib.mkDefault (if (hasNoFsPartition || supportBios) then
+          # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"),
+          # which will be used the bootloader, do not set it as loader.grub.device.
+          # GRUB installation fails, unless the whole disk is selected.
+          "/dev/vda"
+        else
+          "nodev");
+        efiSupport = lib.mkDefault supportEfi;
+        efiInstallAsRemovable = lib.mkDefault supportEfi;
+      };
+
       loader.timeout = 0;
       initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ];
     };
@@ -173,6 +262,10 @@ with lib;
       autoResize = true;
       fsType = "ext4";
     };
+    fileSystems."/boot" = lib.mkIf hasBootPartition {
+      device = "/dev/disk/by-label/ESP";
+      fsType = "vfat";
+    };
 
     services.qemuGuest.enable = lib.mkDefault true;
   };
diff --git a/nixpkgs/nixos/modules/virtualisation/proxmox-lxc.nix b/nixpkgs/nixos/modules/virtualisation/proxmox-lxc.nix
index 9b9f99e5b817..3d966d725a9a 100644
--- a/nixpkgs/nixos/modules/virtualisation/proxmox-lxc.nix
+++ b/nixpkgs/nixos/modules/virtualisation/proxmox-lxc.nix
@@ -7,14 +7,14 @@ with lib;
     privileged = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable privileged mounts
       '';
     };
     manageNetwork = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to manage network interfaces through nix options
         When false, systemd-networkd is enabled to accept network
         configuration from proxmox.
@@ -23,7 +23,7 @@ with lib;
     manageHostName = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to manage hostname through nix options
         When false, the hostname is picked up from /etc/hostname
         populated by proxmox.
diff --git a/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix b/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
index 6bfae74f6366..5cbc648b7a9c 100644
--- a/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
@@ -1,11 +1,8 @@
 # This module creates a virtual machine from the NixOS configuration.
 # Building the `config.system.build.vm' attribute gives you a command
 # that starts a KVM/QEMU VM running the NixOS configuration defined in
-# `config'.  The Nix store is shared read-only with the host, which
-# makes (re)building VMs very efficient.  However, it also means you
-# can't reconfigure the guest inside the guest - you need to rebuild
-# the VM in the host.  On the other hand, the root filesystem is a
-# read/writable disk image persistent across VM reboots.
+# `config'. By default, the Nix store is shared read-only with the
+# host, which makes (re)building VMs very efficient.
 
 { config, lib, pkgs, options, ... }:
 
@@ -55,6 +52,11 @@ let
 
   };
 
+  selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }:
+  if useDefaultFilesystems then
+    if useEFIBoot then "efi" else "legacy"
+  else "none";
+
   driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
     let
       drvId = "drive${toString idx}";
@@ -98,25 +100,35 @@ let
   addDeviceNames =
     imap1 (idx: drive: drive // { device = driveDeviceName idx; });
 
-  efiPrefix =
-    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";
-  efiVarsDefault = "${efiPrefix}_VARS.fd";
-
   # Shell script to start the VM.
   startVM =
     ''
-      #! ${pkgs.runtimeShell}
+      #! ${cfg.host.pkgs.runtimeShell}
 
-      set -e
+      export PATH=${makeBinPath [ cfg.host.pkgs.coreutils ]}''${PATH:+:}$PATH
 
-      NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}")
+      set -e
 
-      if ! test -e "$NIX_DISK_IMAGE"; then
-          ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
-            ${toString config.virtualisation.diskSize}M
+      NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
+
+      if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
+          echo "Disk image do not exist, creating the virtualisation disk image..."
+          # If we are using a bootloader and default filesystems layout.
+          # We have to reuse the system image layout as a backing image format (CoW)
+          # So we can write on the top of it.
+
+          # If we are not using the default FS layout, potentially, we are interested into
+          # performing operations in postDeviceCommands or at early boot on the raw device.
+          # We can still boot through QEMU direct kernel boot feature.
+
+          # CoW prevent size to be attributed to an image.
+          # FIXME: raise this issue to upstream.
+          ${qemu}/bin/qemu-img create \
+          ${concatStringsSep " \\\n" ([ "-f qcow2" ]
+          ++ optional (cfg.useBootLoader && cfg.useDefaultFilesystems) "-F qcow2 -b ${systemImage}/nixos.qcow2"
+          ++ optional (!(cfg.useBootLoader && cfg.useDefaultFilesystems)) "-o size=${toString config.virtualisation.diskSize}M"
+          ++ [ ''"$NIX_DISK_IMAGE"'' ])}
+          echo "Virtualisation disk image created."
       fi
 
       # Create a directory for storing temporary data of the running VM.
@@ -154,22 +166,26 @@ let
       # Create a directory for exchanging data with the VM.
       mkdir -p "$TMPDIR/xchg"
 
-      ${lib.optionalString cfg.useBootLoader
+      ${lib.optionalString cfg.useEFIBoot
       ''
-        # Create a writable copy/snapshot of the boot disk.
-        # A writable boot disk can be booted from automatically.
-        ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img"
-
-        NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}")
-
-        ${lib.optionalString cfg.useEFIBoot
-        ''
-          # VM needs writable EFI vars
-          if ! test -e "$NIX_EFI_VARS"; then
-            cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS"
-            chmod 0644 "$NIX_EFI_VARS"
-          fi
-        ''}
+        # Expose EFI variables, it's useful even when we are not using a bootloader (!).
+        # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence
+        # no guard against `useBootLoader`.  Examples:
+        # - testing PXE boot or other EFI applications
+        # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows
+        NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}")
+        # VM needs writable EFI vars
+        if ! test -e "$NIX_EFI_VARS"; then
+        ${if cfg.useBootLoader then
+            # We still need the EFI var from the make-disk-image derivation
+            # because our "switch-to-configuration" process might
+            # write into it and we want to keep this data.
+            ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"''
+            else
+            ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"''
+          }
+          chmod 0644 "$NIX_EFI_VARS"
+        fi
       ''}
 
       cd "$TMPDIR"
@@ -202,95 +218,29 @@ let
 
   regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; };
 
-
-  # Generate a hard disk image containing a /boot partition and GRUB
-  # in the MBR.  Used when the `useBootLoader' option is set.
-  # Uses `runInLinuxVM` to create the image in a throwaway VM.
-  # See note [Disk layout with `useBootLoader`].
-  # FIXME: use nixos/lib/make-disk-image.nix.
-  bootDisk =
-    pkgs.vmTools.runInLinuxVM (
-      pkgs.runCommand "nixos-boot-disk"
-        { preVM =
-            ''
-              mkdir $out
-              diskImage=$out/disk.img
-              ${qemu}/bin/qemu-img create -f qcow2 $diskImage "60M"
-              ${if cfg.useEFIBoot then ''
-                efiVars=$out/efi-vars.fd
-                cp ${efiVarsDefault} $efiVars
-                chmod 0644 $efiVars
-              '' else ""}
-            '';
-          buildInputs = [ pkgs.util-linux ];
-          QEMU_OPTS = "-nographic -serial stdio -monitor none"
-                      + lib.optionalString cfg.useEFIBoot (
-                        " -drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
-                      + " -drive if=pflash,format=raw,unit=1,file=$efiVars");
-        }
-        ''
-          # Create a /boot EFI partition with 60M and arbitrary but fixed GUIDs for reproducibility
-          ${pkgs.gptfdisk}/bin/sgdisk \
-            --set-alignment=1 --new=1:34:2047 --change-name=1:BIOSBootPartition --typecode=1:ef02 \
-            --set-alignment=512 --largest-new=2 --change-name=2:EFISystem --typecode=2:ef00 \
-            --attributes=1:set:1 \
-            --attributes=2:set:2 \
-            --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C1 \
-            --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
-            --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
-            --hybrid 2 \
-            --recompute-chs /dev/vda
-
-          ${optionalString (config.boot.loader.grub.device != "/dev/vda")
-            # In this throwaway VM, we only have the /dev/vda disk, but the
-            # actual VM described by `config` (used by `switch-to-configuration`
-            # below) may set `boot.loader.grub.device` to a different device
-            # that's nonexistent in the throwaway VM.
-            # Create a symlink for that device, so that the `grub-install`
-            # by `switch-to-configuration` will hit /dev/vda anyway.
-            ''
-              ln -s /dev/vda ${config.boot.loader.grub.device}
-            ''
-          }
-
-          ${pkgs.dosfstools}/bin/mkfs.fat -F16 /dev/vda2
-          export MTOOLS_SKIP_CHECK=1
-          ${pkgs.mtools}/bin/mlabel -i /dev/vda2 ::boot
-
-          # Mount /boot; load necessary modules first.
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/nls/nls_cp437.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/nls/nls_iso8859-1.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/fat/fat.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/fat/vfat.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/efivarfs/efivarfs.ko.xz || true
-          mkdir /boot
-          mount /dev/vda2 /boot
-
-          ${optionalString config.boot.loader.efi.canTouchEfiVariables ''
-            mount -t efivarfs efivarfs /sys/firmware/efi/efivars
-          ''}
-
-          # This is needed for GRUB 0.97, which doesn't know about virtio devices.
-          mkdir /boot/grub
-          echo '(hd0) /dev/vda' > /boot/grub/device.map
-
-          # This is needed for systemd-boot to find ESP, and udev is not available here to create this
-          mkdir -p /dev/block
-          ln -s /dev/vda2 /dev/block/254:2
-
-          # Set up system profile (normally done by nixos-rebuild / nix-env --set)
-          mkdir -p /nix/var/nix/profiles
-          ln -s ${config.system.build.toplevel} /nix/var/nix/profiles/system-1-link
-          ln -s /nix/var/nix/profiles/system-1-link /nix/var/nix/profiles/system
-
-          # Install bootloader
-          touch /etc/NIXOS
-          export NIXOS_INSTALL_BOOTLOADER=1
-          ${config.system.build.toplevel}/bin/switch-to-configuration boot
-
-          umount /boot
-        '' # */
-    );
+  # System image is akin to a complete NixOS install with
+  # a boot partition and root partition.
+  systemImage = import ../../lib/make-disk-image.nix {
+    inherit pkgs config lib;
+    additionalPaths = [ regInfo ];
+    format = "qcow2";
+    onlyNixStore = false;
+    partitionTableType = selectPartitionTableLayout { inherit (cfg) useDefaultFilesystems useEFIBoot; };
+    # Bootloader should be installed on the system image only if we are booting through bootloaders.
+    # Though, if a user is not using our default filesystems, it is possible to not have any ESP
+    # or a strange partition table that's incompatible with GRUB configuration.
+    # As a consequence, this may lead to disk image creation failures.
+    # To avoid this, we prefer to let the user find out about how to install the bootloader on its ESP/disk.
+    # Usually, this can be through building your own disk image.
+    # TODO: If a user is interested into a more fine grained heuristic for `installBootLoader`
+    # by examining the actual contents of `cfg.fileSystems`, please send a PR.
+    installBootLoader = cfg.useBootLoader && cfg.useDefaultFilesystems;
+    touchEFIVars = cfg.useEFIBoot;
+    diskSize = "auto";
+    additionalSpace = "0M";
+    copyChannel = false;
+    OVMF = cfg.efi.OVMF;
+  };
 
   storeImage = import ../../lib/make-disk-image.nix {
     inherit pkgs config lib;
@@ -299,17 +249,43 @@ let
     onlyNixStore = true;
     partitionTableType = "none";
     installBootLoader = false;
+    touchEFIVars = false;
     diskSize = "auto";
     additionalSpace = "0M";
     copyChannel = false;
   };
 
+  bootConfiguration =
+    if cfg.useDefaultFilesystems
+    then
+      if cfg.useBootLoader
+      then
+        if cfg.useEFIBoot then "efi_bootloading_with_default_fs"
+        else "legacy_bootloading_with_default_fs"
+      else
+        if cfg.directBoot.enable then "direct_boot_with_default_fs"
+        else "custom"
+    else
+      "custom";
+  suggestedRootDevice = {
+    "efi_bootloading_with_default_fs" = "${cfg.bootLoaderDevice}2";
+    "legacy_bootloading_with_default_fs" = "${cfg.bootLoaderDevice}1";
+    "direct_boot_with_default_fs" = cfg.bootLoaderDevice;
+    # This will enforce a NixOS module type checking error
+    # to ask explicitly the user to set a rootDevice.
+    # As it will look like `rootDevice = lib.mkDefault null;` after
+    # all "computations".
+    "custom" = null;
+  }.${bootConfiguration};
 in
 
 {
   imports = [
     ../profiles/qemu-guest.nix
     (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ])
+    (mkRemovedOptionModule [ "virtualisation" "bootDevice" ] "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues.")
+    (mkRemovedOptionModule [ "virtualisation" "efiVars" ] "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue")
+    (mkRemovedOptionModule [ "virtualisation" "persistBootDevice" ] "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`")
   ];
 
   options = {
@@ -350,7 +326,7 @@ in
 
     virtualisation.diskImage =
       mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         default = "./${config.system.name}.qcow2";
         defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
         description =
@@ -358,16 +334,53 @@ in
             Path to the disk image containing the root filesystem.
             The image will be created on startup if it does not
             exist.
+
+            If null, a tmpfs will be used as the root filesystem and
+            the VM's state will not be persistent.
           '';
       };
 
-    virtualisation.bootDevice =
+    virtualisation.bootLoaderDevice =
       mkOption {
         type = types.path;
+        default = lookupDriveDeviceName "root" cfg.qemu.drives;
+        defaultText = literalExpression ''lookupDriveDeviceName "root" cfg.qemu.drives'';
         example = "/dev/vda";
         description =
           lib.mdDoc ''
-            The disk to be used for the root filesystem.
+            The disk to be used for the boot filesystem.
+            By default, it is the same disk as the root filesystem.
+          '';
+        };
+
+    virtualisation.bootPartition =
+      mkOption {
+        type = types.nullOr types.path;
+        default = if cfg.useEFIBoot then "${cfg.bootLoaderDevice}1" else null;
+        defaultText = literalExpression ''if cfg.useEFIBoot then "''${cfg.bootLoaderDevice}1" else null'';
+        example = "/dev/vda1";
+        description =
+          lib.mdDoc ''
+            The boot partition to be used to mount /boot filesystem.
+            In legacy boots, this should be null.
+            By default, in EFI boot, it is the first partition of the boot device.
+          '';
+      };
+
+    virtualisation.rootDevice =
+      mkOption {
+        type = types.nullOr types.path;
+        example = "/dev/vda2";
+        description =
+          lib.mdDoc ''
+            The disk or partition to be used for the root filesystem.
+            By default (read the source code for more details):
+
+            - under EFI with a bootloader: 2nd partition of the boot disk
+            - in legacy boot with a bootloader: 1st partition of the boot disk
+            - in direct boot (i.e. without a bootloader): whole disk
+
+            In case you are not using a default boot device or a default filesystem, you have to set explicitly your root device.
           '';
       };
 
@@ -469,14 +482,13 @@ in
             type = types.enum [ "host" "guest" ];
             default = "host";
             description =
-              ''
+              lib.mdDoc ''
                 Controls the direction in which the ports are mapped:
 
-                - <literal>"host"</literal> means traffic from the host ports
-                is forwarded to the given guest port.
-
-                - <literal>"guest"</literal> means traffic from the guest ports
-                is forwarded to the given host port.
+                - `"host"` means traffic from the host ports
+                  is forwarded to the given guest port.
+                - `"guest"` means traffic from the guest ports
+                  is forwarded to the given host port.
               '';
           };
           options.proto = mkOption {
@@ -517,24 +529,41 @@ in
         ]
         '';
       description =
-        ''
+        lib.mdDoc ''
           When using the SLiRP user networking (default), this option allows to
           forward ports to/from the host/guest.
 
-          <warning><para>
-            If the NixOS firewall on the virtual machine is enabled, you also
-            have to open the guest ports to enable the traffic between host and
-            guest.
-          </para></warning>
+          ::: {.warning}
+          If the NixOS firewall on the virtual machine is enabled, you also
+          have to open the guest ports to enable the traffic between host and
+          guest.
+          :::
 
-          <note><para>Currently QEMU supports only IPv4 forwarding.</para></note>
+          ::: {.note}
+          Currently QEMU supports only IPv4 forwarding.
+          :::
         '';
     };
 
+    virtualisation.restrictNetwork =
+      mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description =
+          lib.mdDoc ''
+            If this option is enabled, the guest will be isolated, i.e. it will
+            not be able to contact the host and no guest IP packets will be
+            routed over the host to the outside. This option does not affect
+            any explicitly set forwarding rules.
+          '';
+      };
+
     virtualisation.vlans =
       mkOption {
         type = types.listOf types.ints.unsigned;
-        default = [ 1 ];
+        default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ];
+        defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]'';
         example = [ 1 2 ];
         description =
           lib.mdDoc ''
@@ -549,15 +578,47 @@ in
           '';
       };
 
+    virtualisation.interfaces = mkOption {
+      default = {};
+      example = {
+        enp1s0.vlan = 1;
+      };
+      description = lib.mdDoc ''
+        Network interfaces to add to the VM.
+      '';
+      type = with types; attrsOf (submodule {
+        options = {
+          vlan = mkOption {
+            type = types.ints.unsigned;
+            description = lib.mdDoc ''
+              VLAN to which the network interface is connected.
+            '';
+          };
+
+          assignIP = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc ''
+              Automatically assign an IP address to the network interface using the same scheme as
+              virtualisation.vlans.
+            '';
+          };
+        };
+      });
+    };
+
     virtualisation.writableStore =
       mkOption {
         type = types.bool;
-        default = true; # FIXME
+        default = cfg.mountHostNixStore;
+        defaultText = literalExpression "cfg.mountHostNixStore";
         description =
           lib.mdDoc ''
             If enabled, the Nix store in the VM is made writable by
             layering an overlay filesystem on top of the host's Nix
             store.
+
+            By default, this is enabled if you mount a host Nix store.
           '';
       };
 
@@ -577,15 +638,29 @@ in
         type = types.str;
         default = "";
         internal = true;
-        description = "Primary IP address used in /etc/hosts.";
+        description = lib.mdDoc "Primary IP address used in /etc/hosts.";
       };
 
+    virtualisation.host.pkgs = mkOption {
+      type = options.nixpkgs.pkgs.type;
+      default = pkgs;
+      defaultText = literalExpression "pkgs";
+      example = literalExpression ''
+        import pkgs.path { system = "x86_64-darwin"; }
+      '';
+      description = lib.mdDoc ''
+        pkgs set to use for the host-specific packages of the vm runner.
+        Changing this to e.g. a Darwin package set allows running NixOS VMs on Darwin.
+      '';
+    };
+
     virtualisation.qemu = {
       package =
         mkOption {
           type = types.package;
-          default = pkgs.qemu_kvm;
-          example = "pkgs.qemu_test";
+          default = cfg.host.pkgs.qemu_kvm;
+          defaultText = literalExpression "config.virtualisation.host.pkgs.qemu_kvm";
+          example = literalExpression "pkgs.qemu_test";
           description = lib.mdDoc "QEMU package to use.";
         };
 
@@ -677,21 +752,69 @@ in
           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.
+
+          As an alternative, you can use a bootloader which will provide you
+          with a full NixOS system image containing a Nix store and
+          avoid mounting the host nix store through
+          {option}`virtualisation.mountHostNixStore`.
+        '';
+      };
+
+    virtualisation.mountHostNixStore =
+      mkOption {
+        type = types.bool;
+        default = !cfg.useNixStoreImage && !cfg.useBootLoader;
+        defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
+        description = lib.mdDoc ''
+          Mount the host Nix store as a 9p mount.
         '';
       };
 
+    virtualisation.directBoot = {
+      enable =
+        mkOption {
+          type = types.bool;
+          default = !cfg.useBootLoader;
+          defaultText = "!cfg.useBootLoader";
+          description =
+            lib.mdDoc ''
+              If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. Other relevant parameters such as the initrd are also passed to QEMU.
+
+              If you want to test netboot, consider disabling this option.
+
+              This will not boot / reboot correctly into a system that has switched to a different configuration on disk.
+
+              This is enabled by default if you don't enable bootloaders, but you can still enable a bootloader if you need.
+              Read more about this feature: <https://qemu-project.gitlab.io/qemu/system/linuxboot.html>.
+            '';
+        };
+      initrd =
+        mkOption {
+          type = types.str;
+          default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
+          defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}";
+          description =
+            lib.mdDoc ''
+              In direct boot situations, you may want to influence the initrd to load
+              to use your own customized payload.
+
+              This is useful if you want to test the netboot image without
+              testing the firmware or the loading part.
+            '';
+        };
+    };
+
     virtualisation.useBootLoader =
       mkOption {
         type = types.bool;
         default = false;
         description =
           lib.mdDoc ''
-            If enabled, the virtual machine will be booted using the
-            regular boot loader (i.e., GRUB 1 or 2).  This allows
-            testing of the boot loader.  If
-            disabled (the default), the VM directly boots the NixOS
-            kernel and initial ramdisk, bypassing the boot loader
-            altogether.
+            Use a boot loader to boot the system.
+            This allows, among other things, testing the boot loader.
+
+            If disabled, the kernel and initrd are directly booted,
+            forgoing any bootloader.
           '';
       };
 
@@ -705,8 +828,43 @@ in
             manager.
             useEFIBoot is ignored if useBootLoader == false.
           '';
+        };
+
+    virtualisation.efi = {
+      OVMF = mkOption {
+        type = types.package;
+        default = (pkgs.OVMF.override {
+          secureBoot = cfg.useSecureBoot;
+        }).fd;
+        defaultText = ''(pkgs.OVMF.override {
+          secureBoot = cfg.useSecureBoot;
+        }).fd'';
+        description =
+        lib.mdDoc "OVMF firmware package, defaults to OVMF configured with secure boot if needed.";
       };
 
+      firmware = mkOption {
+        type = types.path;
+        default = cfg.efi.OVMF.firmware;
+        defaultText = literalExpression "cfg.efi.OVMF.firmware";
+        description =
+          lib.mdDoc ''
+            Firmware binary for EFI implementation, defaults to OVMF.
+          '';
+      };
+
+      variables = mkOption {
+        type = types.path;
+        default = cfg.efi.OVMF.variables;
+        defaultText = literalExpression "cfg.efi.OVMF.variables";
+        description =
+          lib.mdDoc ''
+            Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware.
+            Defaults to OVMF.
+          '';
+      };
+    };
+
     virtualisation.useDefaultFilesystems =
       mkOption {
         type = types.bool;
@@ -722,27 +880,26 @@ in
           '';
       };
 
-    virtualisation.efiVars =
+    virtualisation.useSecureBoot =
       mkOption {
-        type = types.str;
-        default = "./${config.system.name}-efi-vars.fd";
-        defaultText = literalExpression ''"./''${config.system.name}-efi-vars.fd"'';
+        type = types.bool;
+        default = false;
         description =
           lib.mdDoc ''
-            Path to nvram image containing UEFI variables.  The will be created
-            on startup if it does not exist.
+            Enable Secure Boot support in the EFI firmware.
           '';
       };
 
+
     virtualisation.bios =
       mkOption {
         type = types.nullOr types.package;
         default = null;
         description =
-          ''
-            An alternate BIOS (such as <package>qboot</package>) with which to start the VM.
-            Should contain a file named <literal>bios.bin</literal>.
-            If <literal>null</literal>, QEMU's builtin SeaBIOS will be used.
+          lib.mdDoc ''
+            An alternate BIOS (such as `qboot`) with which to start the VM.
+            Should contain a file named `bios.bin`.
+            If `null`, QEMU's builtin SeaBIOS will be used.
           '';
       };
 
@@ -767,13 +924,31 @@ in
                   The address must be in the default VLAN (10.0.2.0/24).
               '';
           }
-        ]));
+        ])) ++ [
+          { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047;
+            message = ''
+              virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max.
+            '';
+          }
+          { assertion = cfg.directBoot.initrd != options.virtualisation.directBoot.initrd.default -> cfg.directBoot.enable;
+            message =
+              ''
+                You changed the default of `virtualisation.directBoot.initrd` but you are not
+                using QEMU direct boot. This initrd will not be used in your current
+                boot configuration.
+
+                Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot.
+
+                If you have a more advanced usecase, please open an issue or a pull request.
+              '';
+          }
+        ];
 
     warnings =
       optional (
         cfg.writableStore &&
         cfg.useNixStoreImage &&
-        opt.writableStore.highestPrio > lib.modules.defaultPriority)
+        opt.writableStore.highestPrio > lib.modules.defaultOverridePriority)
         ''
           You have enabled ${opt.useNixStoreImage} = true,
           without setting ${opt.writableStore} = false.
@@ -787,33 +962,25 @@ in
           Otherwise, we recommend
 
             ${opt.writableStore} = false;
+            ''
+      ++ optional (cfg.directBoot.enable && cfg.useBootLoader)
+        ''
+          You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering
+          `useBootLoader` useless. You might want to disable one of those options.
         '';
 
-    # Note [Disk layout with `useBootLoader`]
-    #
-    # If `useBootLoader = true`, we configure 2 drives:
-    # `/dev/?da` for the root disk, and `/dev/?db` for the boot disk
-    # which has the `/boot` partition and the boot loader.
-    # Concretely:
-    #
-    # * The second drive's image `disk.img` is created in `bootDisk = ...`
-    #   using a throwaway VM. Note that there the disk is always `/dev/vda`,
-    #   even though in the final VM it will be at `/dev/*b`.
-    # * The disks are attached in `virtualisation.qemu.drives`.
-    #   Their order makes them appear as devices `a`, `b`, etc.
-    # * `fileSystems."/boot"` is adjusted to be on device `b`.
-
-    # If `useBootLoader`, GRUB goes to the second disk, see
-    # note [Disk layout with `useBootLoader`].
-    boot.loader.grub.device = mkVMOverride (
-      if cfg.useBootLoader
-        then driveDeviceName 2 # second disk
-        else cfg.bootDevice
-    );
+    # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install
+    # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs.
+    # Otherwise, we set the proper bootloader device for this.
+    # FIXME: make a sense of this mess wrt to multiple ESP present in the system, probably use boot.efiSysMountpoint?
+    boot.loader.grub.device = mkVMOverride (if cfg.useEFIBoot then "nodev" else cfg.bootLoaderDevice);
     boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
+    virtualisation.rootDevice = mkDefault suggestedRootDevice;
 
     boot.initrd.kernelModules = optionals (cfg.useNixStoreImage && !cfg.writableStore) [ "erofs" ];
 
+    boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false);
+
     boot.initrd.extraUtilsCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
       ''
         # We need mke2fs in the initrd.
@@ -824,9 +991,10 @@ in
       ''
         # If the disk image appears to be empty, run mke2fs to
         # initialise.
-        FSTYPE=$(blkid -o value -s TYPE ${cfg.bootDevice} || true)
-        if test -z "$FSTYPE"; then
-            mke2fs -t ext4 ${cfg.bootDevice}
+        FSTYPE=$(blkid -o value -s TYPE ${cfg.rootDevice} || true)
+        PARTTYPE=$(blkid -o value -s PTTYPE ${cfg.rootDevice} || true)
+        if test -z "$FSTYPE" -a -z "$PARTTYPE"; then
+            mke2fs -t ext4 ${cfg.rootDevice}
         fi
       '';
 
@@ -843,7 +1011,7 @@ in
 
         ${optionalString cfg.writableStore ''
           echo "mounting overlay filesystem on /nix/store..."
-          mkdir -p 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
+          mkdir -p -m 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
           mount -t overlay overlay $targetRoot/nix/store \
             -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail
         ''}
@@ -872,12 +1040,10 @@ in
       optional cfg.writableStore "overlay"
       ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx";
 
-    virtualisation.bootDevice = mkDefault (driveDeviceName 1);
-
     virtualisation.additionalPaths = [ config.system.build.toplevel ];
 
     virtualisation.sharedDirectories = {
-      nix-store = mkIf (!cfg.useNixStoreImage) {
+      nix-store = mkIf cfg.mountHostNixStore {
         source = builtins.storeDir;
         target = "/nix/store";
       };
@@ -901,13 +1067,13 @@ in
               else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" +
                    "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
           );
+        restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,";
       in
       [
         "-net nic,netdev=user.0,model=virtio"
-        "-netdev user,id=user.0,${forwardingOptions}\"$QEMU_NET_OPTS\""
+        "-netdev user,id=user.0,${forwardingOptions}${restrictNetworkOption}\"$QEMU_NET_OPTS\""
       ];
 
-    # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
       (mkIf cfg.qemu.virtioKeyboard [
         "-device virtio-keyboard"
@@ -922,14 +1088,14 @@ in
         alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9));
         # Replace all non-alphanumeric characters with underscores
         sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s);
-      in mkIf (!cfg.useBootLoader) [
+      in mkIf cfg.directBoot.enable [
         "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}"
-        "-initrd ${config.system.build.toplevel}/initrd"
+        "-initrd ${cfg.directBoot.initrd}"
         ''-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=on,file=${efiFirmware}"
-        "-drive if=pflash,format=raw,unit=1,file=$NIX_EFI_VARS"
+        "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}"
+        "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS"
       ])
       (mkIf (cfg.bios != null) [
         "-bios ${cfg.bios}/bios.bin"
@@ -940,48 +1106,38 @@ in
     ];
 
     virtualisation.qemu.drives = mkMerge [
-      [{
+      (mkIf (cfg.diskImage != null) [{
         name = "root";
         file = ''"$NIX_DISK_IMAGE"'';
         driveExtraOpts.cache = "writeback";
         driveExtraOpts.werror = "report";
-      }]
+        deviceExtraOpts.bootindex = "1";
+      }])
       (mkIf cfg.useNixStoreImage [{
         name = "nix-store";
         file = ''"$TMPDIR"/store.img'';
-        deviceExtraOpts.bootindex = if cfg.useBootLoader then "3" else "2";
+        deviceExtraOpts.bootindex = "2";
         driveExtraOpts.format = if cfg.writableStore then "qcow2" else "raw";
       }])
-      (mkIf cfg.useBootLoader [
-        # The order of this list determines the device names, see
-        # note [Disk layout with `useBootLoader`].
-        {
-          name = "boot";
-          file = ''"$TMPDIR"/disk.img'';
-          driveExtraOpts.media = "disk";
-          deviceExtraOpts.bootindex = "1";
-        }
-      ])
       (imap0 (idx: _: {
         file = "$(pwd)/empty${toString idx}.qcow2";
         driveExtraOpts.werror = "report";
       }) cfg.emptyDiskImages)
     ];
 
-    # Mount the host filesystem via 9P, and bind-mount the Nix store
-    # of the host into our own filesystem.  We use mkVMOverride to
-    # allow this module to be applied to "normal" NixOS system
-    # configuration, where the regular value for the `fileSystems'
-    # attribute should be disregarded for the purpose of building a VM
-    # test image (since those filesystems don't exist in the VM).
-    fileSystems =
-    let
+    # Use mkVMOverride to enable building test VMs (e.g. via `nixos-rebuild
+    # build-vm`) of a system configuration, where the regular value for the
+    # `fileSystems' attribute should be disregarded (since those filesystems
+    # don't necessarily exist in the VM).
+    fileSystems = mkVMOverride cfg.fileSystems;
+
+    virtualisation.fileSystems = let
       mkSharedDir = tag: share:
         {
           name =
             if tag == "nix-store" && cfg.writableStore
-              then "/nix/.ro-store"
-              else share.target;
+            then "/nix/.ro-store"
+            else share.target;
           value.device = tag;
           value.fsType = "9p";
           value.neededForBoot = true;
@@ -989,44 +1145,41 @@ in
             [ "trans=virtio" "version=9p2000.L"  "msize=${toString cfg.msize}" ]
             ++ lib.optional (tag == "nix-store") "cache=loose";
         };
-    in
-      mkVMOverride (cfg.fileSystems //
-      optionalAttrs cfg.useDefaultFilesystems {
-        "/".device = cfg.bootDevice;
-        "/".fsType = "ext4";
-        "/".autoFormat = true;
-      } //
-      optionalAttrs config.boot.tmpOnTmpfs {
-        "/tmp" = {
+    in lib.mkMerge [
+      (lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
+      {
+        "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then {
+          device = "tmpfs";
+          fsType = "tmpfs";
+        } else {
+          device = cfg.rootDevice;
+          fsType = "ext4";
+          autoFormat = true;
+        });
+        "/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
           device = "tmpfs";
           fsType = "tmpfs";
           neededForBoot = true;
           # Sync with systemd's tmp.mount;
-          options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
+          options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ];
         };
-      } //
-      optionalAttrs cfg.useNixStoreImage {
-        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = {
+        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage {
           device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
           neededForBoot = true;
           options = [ "ro" ];
         };
-      } //
-      optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) {
-        "/nix/.rw-store" = {
+        "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
           fsType = "tmpfs";
           options = [ "mode=0755" ];
           neededForBoot = true;
         };
-      } //
-      optionalAttrs cfg.useBootLoader {
-        # see note [Disk layout with `useBootLoader`]
-        "/boot" = {
-          device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
+        "/boot" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
+          device = cfg.bootPartition; # 1 for e.g. `vda1`, as created in `systemImage`
           fsType = "vfat";
           noCheck = true; # fsck fails on a r/o filesystem
         };
-      } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
+      }
+    ];
 
     boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
       mounts = [{
@@ -1034,18 +1187,20 @@ in
         what = "overlay";
         type = "overlay";
         options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work";
-        wantedBy = ["local-fs.target"];
-        before = ["local-fs.target"];
-        requires = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
-        after = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
-        unitConfig.IgnoreOnIsolate = true;
+        wantedBy = ["initrd-fs.target"];
+        before = ["initrd-fs.target"];
+        requires = ["rw-store.service"];
+        after = ["rw-store.service"];
+        unitConfig.RequiresMountsFor = "/sysroot/nix/.ro-store";
       }];
       services.rw-store = {
-        after = ["sysroot-nix-.rw\\x2dstore.mount"];
-        unitConfig.DefaultDependencies = false;
+        unitConfig = {
+          DefaultDependencies = false;
+          RequiresMountsFor = "/sysroot/nix/.rw-store";
+        };
         serviceConfig = {
           Type = "oneshot";
-          ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
+          ExecStart = "/bin/mkdir -p -m 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
         };
       };
     };
@@ -1058,14 +1213,14 @@ in
 
     services.qemuGuest.enable = cfg.qemu.guestAgent.enable;
 
-    system.build.vm = pkgs.runCommand "nixos-vm" {
+    system.build.vm = cfg.host.pkgs.runCommand "nixos-vm" {
       preferLocalBuild = true;
       meta.mainProgram = "run-${config.system.name}-vm";
     }
       ''
         mkdir -p $out/bin
         ln -s ${config.system.build.toplevel} $out/system
-        ln -s ${pkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm
+        ln -s ${cfg.host.pkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm
       '';
 
     # When building a regular system configuration, override whatever
diff --git a/nixpkgs/nixos/modules/virtualisation/rosetta.nix b/nixpkgs/nixos/modules/virtualisation/rosetta.nix
new file mode 100644
index 000000000000..ee811b571b8f
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/rosetta.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.virtualisation.rosetta;
+  inherit (lib) types;
+in
+{
+  options = {
+    virtualisation.rosetta.enable = lib.mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to enable [Rosetta](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) support.
+
+        This feature requires the system to be a virtualised guest on an Apple silicon host.
+
+        The default settings are suitable for the [UTM](https://docs.getutm.app/) virtualisation [package](https://search.nixos.org/packages?channel=unstable&show=utm&from=0&size=1&sort=relevance&type=packages&query=utm).
+        Make sure to select 'Apple Virtualization' as the virtualisation engine and then tick the 'Enable Rosetta' option.
+      '';
+    };
+
+    virtualisation.rosetta.mountPoint = lib.mkOption {
+      type = types.str;
+      default = "/run/rosetta";
+      internal = true;
+      description = lib.mdDoc ''
+        The mount point for the Rosetta runtime inside the guest system.
+
+        The proprietary runtime is exposed through a VirtioFS directory share and then mounted at this directory.
+      '';
+    };
+
+    virtualisation.rosetta.mountTag = lib.mkOption {
+      type = types.str;
+      default = "rosetta";
+      description = lib.mdDoc ''
+        The VirtioFS mount tag for the Rosetta runtime, exposed by the host's virtualisation software.
+
+        If supported, your virtualisation software should provide instructions on how register the Rosetta runtime inside Linux guests.
+        These instructions should mention the name of the mount tag used for the VirtioFS directory share that contains the Rosetta runtime.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = pkgs.stdenv.hostPlatform.isAarch64;
+        message = "Rosetta is only supported on aarch64 systems";
+      }
+    ];
+
+    fileSystems."${cfg.mountPoint}" = {
+      device = cfg.mountTag;
+      fsType = "virtiofs";
+    };
+
+
+    nix.settings = {
+      extra-platforms = [ "x86_64-linux" ];
+      extra-sandbox-paths =  [
+        "/run/binfmt"
+        cfg.mountPoint
+      ];
+    };
+    boot.binfmt.registrations.rosetta = {
+      interpreter = "${cfg.mountPoint}/rosetta";
+
+      # The required flags for binfmt are documented by Apple:
+      # https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+      fixBinary = true;
+      matchCredentials = true;
+      preserveArgvZero = false;
+
+      # Remove the shell wrapper and call the runtime directly
+      wrapInterpreterInShell = false;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
index 4e47febed1be..b1565a09682a 100644
--- a/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
@@ -18,25 +18,25 @@ in
 
 {
   options.virtualisation.virtualbox.host = {
-    enable = mkEnableOption "VirtualBox" // {
-      description = ''
+    enable = mkEnableOption (lib.mdDoc "VirtualBox") // {
+      description = lib.mdDoc ''
         Whether to enable VirtualBox.
 
-        <note><para>
-          In order to pass USB devices from the host to the guests, the user
-          needs to be in the <literal>vboxusers</literal> group.
-        </para></note>
+        ::: {.note}
+        In order to pass USB devices from the host to the guests, the user
+        needs to be in the `vboxusers` group.
+        :::
       '';
     };
 
-    enableExtensionPack = mkEnableOption "VirtualBox extension pack" // {
-      description = ''
+    enableExtensionPack = mkEnableOption (lib.mdDoc "VirtualBox extension pack") // {
+      description = lib.mdDoc ''
         Whether to install the Oracle Extension Pack for VirtualBox.
 
-        <important><para>
-          You must set <literal>nixpkgs.config.allowUnfree = true</literal> in
-          order to use this.  This requires you accept the VirtualBox PUEL.
-        </para></important>
+        ::: {.important}
+        You must set `nixpkgs.config.allowUnfree = true` in
+        order to use this.  This requires you accept the VirtualBox PUEL.
+        :::
       '';
     };
 
@@ -60,15 +60,15 @@ in
     enableHardening = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Enable hardened VirtualBox, which ensures that only the binaries in the
         system path get access to the devices exposed by the kernel modules
         instead of all users in the vboxusers group.
 
-        <important><para>
-          Disabling this can put your system's security at risk, as local users
-          in the vboxusers group can tamper with the VirtualBox device files.
-        </para></important>
+        ::: {.important}
+        Disabling this can put your system's security at risk, as local users
+        in the vboxusers group can tamper with the VirtualBox device files.
+        :::
       '';
     };
 
@@ -104,16 +104,18 @@ in
         group = "vboxusers";
         setuid = true;
       };
+      executables = [
+        "VBoxHeadless"
+        "VBoxNetAdpCtl"
+        "VBoxNetDHCP"
+        "VBoxNetNAT"
+        "VBoxVolInfo"
+      ] ++ (lib.optionals (!cfg.headless) [
+        "VBoxSDL"
+        "VirtualBoxVM"
+      ]);
     in mkIf cfg.enableHardening
-      (builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) [
-      "VBoxHeadless"
-      "VBoxNetAdpCtl"
-      "VBoxNetDHCP"
-      "VBoxNetNAT"
-      "VBoxSDL"
-      "VBoxVolInfo"
-      "VirtualBoxVM"
-    ]));
+      (builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) executables));
 
     users.groups.vboxusers.gid = config.ids.gids.vboxusers;
 
diff --git a/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix
index 0c095c01ad80..0da217fd1cb0 100644
--- a/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/virtualbox-image.nix
@@ -41,7 +41,7 @@ in {
       };
       vmName = mkOption {
         type = types.str;
-        default = "NixOS ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
+        default = "${config.system.nixos.distroName} ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
         description = lib.mdDoc ''
           The name of the VirtualBox appliance.
         '';
@@ -81,7 +81,7 @@ in {
       extraDisk = mkOption {
         description = lib.mdDoc ''
           Optional extra disk/hdd configuration.
-          The disk will be an 'ext4' partition on a separate VMDK file.
+          The disk will be an 'ext4' partition on a separate file.
         '';
         default = null;
         example = {
@@ -107,6 +107,46 @@ in {
           };
         });
       };
+      postExportCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          ${pkgs.cot}/bin/cot edit-hardware "$fn" \
+            -v vmx-14 \
+            --nics 2 \
+            --nic-types VMXNET3 \
+            --nic-names 'Nic name' \
+            --nic-networks 'Nic match' \
+            --network-descriptions 'Nic description' \
+            --scsi-subtypes VirtualSCSI
+        '';
+        description = lib.mdDoc ''
+          Extra commands to run after exporting the OVA to `$fn`.
+        '';
+      };
+      storageController = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+        example = {
+          name = "SCSI";
+          add = "scsi";
+          portcount = 16;
+          bootable = "on";
+          hostiocache = "on";
+        };
+        default = {
+          name = "SATA";
+          add = "sata";
+          portcount = 4;
+          bootable = "on";
+          hostiocache = "on";
+        };
+        description = lib.mdDoc ''
+          Parameters passed to the VirtualBox appliance. Must have at least
+          `name`.
+
+          Run `VBoxManage storagectl --help` to see more options.
+        '';
+      };
     };
   };
 
@@ -143,8 +183,8 @@ in {
           export HOME=$PWD
           export PATH=${pkgs.virtualbox}/bin:$PATH
 
-          echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
-          VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
+          echo "converting image to VirtualBox format..."
+          VBoxManage convertfromraw $diskImage disk.vdi
 
           ${optionalString (cfg.extraDisk != null) ''
             echo "creating extra disk: data-disk.raw"
@@ -156,8 +196,8 @@ in {
               mkpart primary ext4 1MiB -1
             eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs)
             mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
-            echo "creating extra disk: data-disk.vmdk"
-            VBoxManage internalcommands createrawvmdk -filename data-disk.vmdk -rawdisk $dataDiskImage
+            echo "creating extra disk: data-disk.vdi"
+            VBoxManage convertfromraw $dataDiskImage data-disk.vdi
           ''}
 
           echo "creating VirtualBox VM..."
@@ -167,18 +207,19 @@ in {
           VBoxManage modifyvm "$vmName" \
             --memory ${toString cfg.memorySize} \
             ${lib.cli.toGNUCommandLineShell { } cfg.params}
-          VBoxManage storagectl "$vmName" --name SATA --add sata --portcount 4 --bootable on --hostiocache on
-          VBoxManage storageattach "$vmName" --storagectl SATA --port 0 --device 0 --type hdd \
-            --medium disk.vmdk
+          VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController}
+          VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \
+            --medium disk.vdi
           ${optionalString (cfg.extraDisk != null) ''
-            VBoxManage storageattach "$vmName" --storagectl SATA --port 1 --device 0 --type hdd \
-            --medium data-disk.vmdk
+            VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \
+            --medium data-disk.vdi
           ''}
 
           echo "exporting VirtualBox VM..."
           mkdir -p $out
           fn="$out/${cfg.vmFileName}"
           VBoxManage export "$vmName" --output "$fn" --options manifest ${escapeShellArgs cfg.exportParams}
+          ${cfg.postExportCommands}
 
           rm -v $diskImage
 
diff --git a/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix b/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
index 61ff9da65af1..b8f0a4cf668e 100644
--- a/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
+++ b/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
@@ -13,10 +13,11 @@ in
   ];
 
   options.virtualisation.vmware.guest = {
-    enable = mkEnableOption "VMWare Guest Support";
+    enable = mkEnableOption (lib.mdDoc "VMWare Guest Support");
     headless = mkOption {
       type = types.bool;
-      default = false;
+      default = !config.services.xserver.enable;
+      defaultText = "!config.services.xserver.enable";
       description = lib.mdDoc "Whether to disable X11-related features.";
     };
   };
diff --git a/nixpkgs/nixos/modules/virtualisation/vmware-host.nix b/nixpkgs/nixos/modules/virtualisation/vmware-host.nix
index b4869f0210dc..4b2dc28aeac7 100644
--- a/nixpkgs/nixos/modules/virtualisation/vmware-host.nix
+++ b/nixpkgs/nixos/modules/virtualisation/vmware-host.nix
@@ -20,21 +20,21 @@ in
 {
   options = with lib; {
     virtualisation.vmware.host = {
-      enable = mkEnableOption "VMware" // {
-        description = ''
+      enable = mkEnableOption (lib.mdDoc "VMware") // {
+        description = lib.mdDoc ''
           This enables VMware host virtualisation for running VMs.
 
-          <important><para>
-          <literal>vmware-vmx</literal> will cause kcompactd0 due to
-          <literal>Transparent Hugepages</literal> feature in kernel.
-          Apply <literal>[ "transparent_hugepage=never" ]</literal> in
-          option <option>boot.kernelParams</option> to disable them.
-          </para></important>
-
-          <note><para>
-          If that didn't work disable <literal>TRANSPARENT_HUGEPAGE</literal>,
-          <literal>COMPACTION</literal> configs and recompile kernel.
-          </para></note>
+          ::: {.important}
+          `vmware-vmx` will cause kcompactd0 due to
+          `Transparent Hugepages` feature in kernel.
+          Apply `[ "transparent_hugepage=never" ]` in
+          option {option}`boot.kernelParams` to disable them.
+          :::
+
+          ::: {.note}
+          If that didn't work disable `TRANSPARENT_HUGEPAGE`,
+          `COMPACTION` configs and recompile kernel.
+          :::
         '';
       };
       package = mkOption {
@@ -120,7 +120,7 @@ in
     # Services
 
     systemd.services."vmware-authdlauncher" = {
-      description = "VMware Authentification Daemon";
+      description = "VMware Authentication Daemon";
       serviceConfig = {
         Type = "forking";
         ExecStart = [ "${cfg.package}/bin/vmware-authdlauncher" ];
diff --git a/nixpkgs/nixos/modules/virtualisation/vmware-image.nix b/nixpkgs/nixos/modules/virtualisation/vmware-image.nix
index f6cd12e2bb79..a38713b4d4ee 100644
--- a/nixpkgs/nixos/modules/virtualisation/vmware-image.nix
+++ b/nixpkgs/nixos/modules/virtualisation/vmware-image.nix
@@ -21,34 +21,34 @@ in {
         type = with types; either (enum [ "auto" ]) int;
         default = "auto";
         example = 2048;
-        description = ''
+        description = lib.mdDoc ''
           The size of the VMWare base image in MiB.
         '';
       };
       vmDerivationName = mkOption {
         type = types.str;
         default = "nixos-vmware-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
-        description = ''
+        description = lib.mdDoc ''
           The name of the derivation for the VMWare appliance.
         '';
       };
       vmFileName = mkOption {
         type = types.str;
         default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.vmdk";
-        description = ''
+        description = lib.mdDoc ''
           The file name of the VMWare appliance.
         '';
       };
       vmSubformat = mkOption {
         type = types.enum subformats;
         default = "monolithicSparse";
-        description = "Specifies which VMDK subformat to use.";
+        description = lib.mdDoc "Specifies which VMDK subformat to use.";
       };
       vmCompat6 = mkOption {
         type = types.bool;
         default = false;
         example = true;
-        description = "Create a VMDK version 6 image (instead of version 4).";
+        description = lib.mdDoc "Create a VMDK version 6 image (instead of version 4).";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/virtualisation/waydroid.nix b/nixpkgs/nixos/modules/virtualisation/waydroid.nix
index 84abf6065810..46e5f901015d 100644
--- a/nixpkgs/nixos/modules/virtualisation/waydroid.nix
+++ b/nixpkgs/nixos/modules/virtualisation/waydroid.nix
@@ -22,7 +22,7 @@ in
 {
 
   options.virtualisation.waydroid = {
-    enable = mkEnableOption "Waydroid";
+    enable = mkEnableOption (lib.mdDoc "Waydroid");
   };
 
   config = mkIf cfg.enable {
@@ -56,12 +56,8 @@ in
 
       wantedBy = [ "multi-user.target" ];
 
-      unitConfig = {
-        ConditionPathExists = "/var/lib/waydroid/lxc/waydroid";
-      };
-
       serviceConfig = {
-        ExecStart = "${pkgs.waydroid}/bin/waydroid container start";
+        ExecStart = "${pkgs.waydroid}/bin/waydroid -w container start";
         ExecStop = "${pkgs.waydroid}/bin/waydroid container stop";
         ExecStopPost = "${pkgs.waydroid}/bin/waydroid session stop";
       };
diff --git a/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix b/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix
index 25ccbaebc077..792edc9b397d 100644
--- a/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix
+++ b/nixpkgs/nixos/modules/virtualisation/xe-guest-utilities.nix
@@ -5,7 +5,7 @@ let
 in {
   options = {
     services.xe-guest-utilities = {
-      enable = mkEnableOption "the Xen guest utilities daemon";
+      enable = mkEnableOption (lib.mdDoc "the Xen guest utilities daemon");
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix b/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix
index 25d06e3c721d..8f361a7ac020 100644
--- a/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixpkgs/nixos/modules/virtualisation/xen-dom0.nix
@@ -139,7 +139,7 @@ in
           };
       };
 
-    virtualisation.xen.trace = mkEnableOption "Xen tracing";
+    virtualisation.xen.trace = mkEnableOption (lib.mdDoc "Xen tracing");
 
   };
 
diff --git a/nixpkgs/nixos/modules/virtualisation/xen-domU.nix b/nixpkgs/nixos/modules/virtualisation/xen-domU.nix
index c00b984c2ce0..ce5a482b1145 100644
--- a/nixpkgs/nixos/modules/virtualisation/xen-domU.nix
+++ b/nixpkgs/nixos/modules/virtualisation/xen-domU.nix
@@ -3,7 +3,6 @@
 { ... }:
 
 {
-  boot.loader.grub.version = 2;
   boot.loader.grub.device = "nodev";
 
   boot.initrd.kernelModules =
diff --git a/nixpkgs/nixos/release-combined.nix b/nixpkgs/nixos/release-combined.nix
index e8677f7e1e97..125086294d41 100644
--- a/nixpkgs/nixos/release-combined.nix
+++ b/nixpkgs/nixos/release-combined.nix
@@ -4,8 +4,8 @@
 
 { nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 56789; shortRev = "gfedcba"; }
 , stableBranch ? false
-, supportedSystems ? [ "x86_64-linux" ]
-, limitedSupportedSystems ? [ "i686-linux" "aarch64-linux" ]
+, supportedSystems ? [ "aarch64-linux" "x86_64-linux" ]
+, limitedSupportedSystems ? [ "i686-linux" ]
 }:
 
 let
@@ -50,17 +50,18 @@ in rec {
         (onFullSupported "nixos.dummy")
         (onAllSupported "nixos.iso_minimal")
         (onSystems ["x86_64-linux" "aarch64-linux"] "nixos.amazonImage")
-        (onSystems ["x86_64-linux"] "nixos.iso_plasma5")
-        (onSystems ["x86_64-linux"] "nixos.iso_gnome")
+        (onFullSupported "nixos.iso_plasma5")
+        (onFullSupported "nixos.iso_gnome")
         (onFullSupported "nixos.manual")
         (onSystems ["x86_64-linux"] "nixos.ova")
         (onSystems ["aarch64-linux"] "nixos.sd_image")
+        (onFullSupported "nixos.tests.acme")
         (onSystems ["x86_64-linux"] "nixos.tests.boot.biosCdrom")
         (onSystems ["x86_64-linux"] "nixos.tests.boot.biosUsb")
         (onFullSupported "nixos.tests.boot-stage1")
-        (onSystems ["x86_64-linux"] "nixos.tests.boot.uefiCdrom")
-        (onSystems ["x86_64-linux"] "nixos.tests.boot.uefiUsb")
-        (onSystems ["x86_64-linux"] "nixos.tests.chromium")
+        (onFullSupported "nixos.tests.boot.uefiCdrom")
+        (onFullSupported "nixos.tests.boot.uefiUsb")
+        (onFullSupported "nixos.tests.chromium")
         (onFullSupported "nixos.tests.containers-imperative")
         (onFullSupported "nixos.tests.containers-ip")
         (onSystems ["x86_64-linux"] "nixos.tests.docker")
@@ -76,6 +77,7 @@ in rec {
         (onFullSupported "nixos.tests.i3wm")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSimple")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSubvolDefault")
+        (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSubvolEscape")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSubvols")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.luksroot")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.lvm")
@@ -98,7 +100,6 @@ in rec {
         (onFullSupported "nixos.tests.login")
         (onFullSupported "nixos.tests.misc")
         (onFullSupported "nixos.tests.mutableUsers")
-        (onFullSupported "nixos.tests.nat.firewall-conntrack")
         (onFullSupported "nixos.tests.nat.firewall")
         (onFullSupported "nixos.tests.nat.standalone")
         (onFullSupported "nixos.tests.networking.scripted.bond")
@@ -129,8 +130,7 @@ in rec {
         (onFullSupported "nixos.tests.networking.networkd.virtual")
         (onFullSupported "nixos.tests.networking.networkd.vlan")
         (onFullSupported "nixos.tests.systemd-networkd-ipv6-prefix-delegation")
-        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
-        #(onFullSupported "nixos.tests.nfs3.simple")
+        (onFullSupported "nixos.tests.nfs3.simple")
         (onFullSupported "nixos.tests.nfs4.simple")
         (onSystems ["x86_64-linux"] "nixos.tests.oci-containers.podman")
         (onFullSupported "nixos.tests.openssh")
@@ -144,7 +144,8 @@ in rec {
         (onFullSupported "nixos.tests.predictable-interface-names.predictable")
         (onFullSupported "nixos.tests.predictable-interface-names.unpredictableNetworkd")
         (onFullSupported "nixos.tests.predictable-interface-names.unpredictable")
-        (onFullSupported "nixos.tests.printing")
+        (onFullSupported "nixos.tests.printing-service")
+        (onFullSupported "nixos.tests.printing-socket")
         (onFullSupported "nixos.tests.proxy")
         (onFullSupported "nixos.tests.sddm.default")
         (onFullSupported "nixos.tests.shadow")
diff --git a/nixpkgs/nixos/release-small.nix b/nixpkgs/nixos/release-small.nix
index 1d51b4e7f28f..6204dc731ad9 100644
--- a/nixpkgs/nixos/release-small.nix
+++ b/nixpkgs/nixos/release-small.nix
@@ -1,10 +1,14 @@
 # This jobset is used to generate a NixOS channel that contains a
 # small subset of Nixpkgs, mostly useful for servers that need fast
 # security updates.
-
+#
+# Individual jobs can be tested by running:
+#
+#   nix-build nixos/release-small.nix -A <jobname>
+#
 { nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 56789; shortRev = "gfedcba"; }
 , stableBranch ? false
-, supportedSystems ? [ "x86_64-linux" ] # no i686-linux
+, supportedSystems ? [ "aarch64-linux" "x86_64-linux" ] # no i686-linux
 }:
 
 let
@@ -31,6 +35,7 @@ in rec {
     inherit (nixos') channel manual options iso_minimal amazonImage dummy;
     tests = {
       inherit (nixos'.tests)
+        acme
         containers-imperative
         containers-ip
         firewall
@@ -38,8 +43,7 @@ in rec {
         login
         misc
         nat
-        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
-        #nfs3
+        nfs3
         openssh
         php
         predictable-interface-names
@@ -53,7 +57,8 @@ in rec {
       };
       boot = {
         inherit (nixos'.tests.boot)
-          biosCdrom;
+          biosCdrom
+          uefiCdrom;
       };
     };
   };
@@ -69,7 +74,7 @@ in rec {
       imagemagick
       jdk
       linux
-      mysql
+      mariadb
       nginx
       nodejs
       openssh
@@ -80,48 +85,60 @@ in rec {
       stdenv
       subversion
       tarball
-      vim;
+      vim
+      tests-stdenv-gcc-stageCompare;
   };
 
-  tested = pkgs.releaseTools.aggregate {
+  tested = let
+    onSupported = x: map (system: "${x}.${system}") supportedSystems;
+    onSystems = systems: x: map (system: "${x}.${system}")
+      (pkgs.lib.intersectLists systems supportedSystems);
+  in pkgs.releaseTools.aggregate {
     name = "nixos-${nixos.channel.version}";
     meta = {
       description = "Release-critical builds for the NixOS channel";
       maintainers = [ lib.maintainers.eelco ];
     };
-    constituents =
-      [ "nixos.channel"
-        "nixos.dummy.x86_64-linux"
-        "nixos.iso_minimal.x86_64-linux"
-        "nixos.amazonImage.x86_64-linux"
-        "nixos.manual.x86_64-linux"
-        "nixos.tests.boot.biosCdrom.x86_64-linux"
-        "nixos.tests.containers-imperative.x86_64-linux"
-        "nixos.tests.containers-ip.x86_64-linux"
-        "nixos.tests.firewall.x86_64-linux"
-        "nixos.tests.installer.lvm.x86_64-linux"
-        "nixos.tests.installer.separateBoot.x86_64-linux"
-        "nixos.tests.installer.simple.x86_64-linux"
-        "nixos.tests.ipv6.x86_64-linux"
-        "nixos.tests.login.x86_64-linux"
-        "nixos.tests.misc.x86_64-linux"
-        "nixos.tests.nat.firewall-conntrack.x86_64-linux"
-        "nixos.tests.nat.firewall.x86_64-linux"
-        "nixos.tests.nat.standalone.x86_64-linux"
-        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
-        #"nixos.tests.nfs3.simple.x86_64-linux"
-        "nixos.tests.openssh.x86_64-linux"
-        "nixos.tests.php.fpm.x86_64-linux"
-        "nixos.tests.php.pcre.x86_64-linux"
-        "nixos.tests.predictable-interface-names.predictable.x86_64-linux"
-        "nixos.tests.predictable-interface-names.predictableNetworkd.x86_64-linux"
-        "nixos.tests.predictable-interface-names.unpredictable.x86_64-linux"
-        "nixos.tests.predictable-interface-names.unpredictableNetworkd.x86_64-linux"
-        "nixos.tests.proxy.x86_64-linux"
-        "nixos.tests.simple.x86_64-linux"
-        "nixpkgs.jdk.x86_64-linux"
+    constituents = lib.flatten [
+      [
+        "nixos.channel"
         "nixpkgs.tarball"
-      ];
+      ]
+      (map (onSystems [ "x86_64-linux" ]) [
+        "nixos.tests.boot.biosCdrom"
+        "nixos.tests.installer.lvm"
+        "nixos.tests.installer.separateBoot"
+        "nixos.tests.installer.simple"
+      ])
+      (map onSupported [
+        "nixos.dummy"
+        "nixos.iso_minimal"
+        "nixos.amazonImage"
+        "nixos.manual"
+        "nixos.tests.acme"
+        "nixos.tests.boot.uefiCdrom"
+        "nixos.tests.containers-imperative"
+        "nixos.tests.containers-ip"
+        "nixos.tests.firewall"
+        "nixos.tests.ipv6"
+        "nixos.tests.login"
+        "nixos.tests.misc"
+        "nixos.tests.nat.firewall"
+        "nixos.tests.nat.standalone"
+        "nixos.tests.nfs3.simple"
+        "nixos.tests.openssh"
+        "nixos.tests.php.fpm"
+        "nixos.tests.php.pcre"
+        "nixos.tests.predictable-interface-names.predictable"
+        "nixos.tests.predictable-interface-names.predictableNetworkd"
+        "nixos.tests.predictable-interface-names.unpredictable"
+        "nixos.tests.predictable-interface-names.unpredictableNetworkd"
+        "nixos.tests.proxy"
+        "nixos.tests.simple"
+        "nixpkgs.jdk"
+        "nixpkgs.tests-stdenv-gcc-stageCompare"
+      ])
+    ];
   };
 
 }
diff --git a/nixpkgs/nixos/release.nix b/nixpkgs/nixos/release.nix
index f70b02c4292b..93ebe000fc00 100644
--- a/nixpkgs/nixos/release.nix
+++ b/nixpkgs/nixos/release.nix
@@ -22,8 +22,8 @@ let
     import ./tests/all-tests.nix {
       inherit system;
       pkgs = import ./.. { inherit system; };
-      callTest = t: {
-        ${system} = hydraJob t.test;
+      callTest = config: {
+        ${system} = hydraJob config.test;
       };
     } // {
       # for typechecking of the scripts and evaluation of
@@ -32,8 +32,8 @@ let
         import ./tests/all-tests.nix {
         inherit system;
         pkgs = import ./.. { inherit system; };
-        callTest = t: {
-          ${system} = hydraJob t.test.driver;
+        callTest = config: {
+          ${system} = hydraJob config.driver;
         };
       };
     };
@@ -144,7 +144,6 @@ in rec {
   manual = manualHTML; # TODO(@oxij): remove eventually
   manualEpub = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualEpub));
   manpages = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manpages);
-  manualGeneratedSources = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.generatedSources);
   options = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.optionsJSON)).x86_64-linux;
 
 
@@ -169,26 +168,34 @@ in rec {
     inherit system;
   });
 
-  iso_plasma5 = forMatchingSystems [ "x86_64-linux" ] (system: makeIso {
+  iso_plasma5 = forMatchingSystems supportedSystems (system: makeIso {
     module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma5.nix;
     type = "plasma5";
     inherit system;
   });
 
-  iso_gnome = forMatchingSystems [ "x86_64-linux" ] (system: makeIso {
+  iso_gnome = forMatchingSystems supportedSystems (system: makeIso {
     module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix;
     type = "gnome";
     inherit system;
   });
 
-  # A variant with a more recent (but possibly less stable) kernel
-  # that might support more hardware.
+  # A variant with a more recent (but possibly less stable) kernel that might support more hardware.
+  # This variant keeps zfs support enabled, hoping it will build and work.
   iso_minimal_new_kernel = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: makeIso {
     module = ./modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix;
     type = "minimal-new-kernel";
     inherit system;
   });
 
+  # A variant with a more recent (but possibly less stable) kernel that might support more hardware.
+  # ZFS support disabled since it is unlikely to support the latest kernel.
+  iso_minimal_new_kernel_no_zfs = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: makeIso {
+    module = ./modules/installer/cd-dvd/installation-cd-minimal-new-kernel-no-zfs.nix;
+    type = "minimal-new-kernel-no-zfs";
+    inherit system;
+  });
+
   sd_image = forMatchingSystems [ "armv6l-linux" "armv7l-linux" "aarch64-linux" ] (system: makeSdImage {
     module = {
         armv6l-linux = ./modules/installer/sd-card/sd-image-raspberrypi-installer.nix;
@@ -206,6 +213,14 @@ in rec {
     inherit system;
   });
 
+  sd_image_new_kernel_no_zfs = forMatchingSystems [ "aarch64-linux" ] (system: makeSdImage {
+    module = {
+        aarch64-linux = ./modules/installer/sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix;
+      }.${system};
+    type = "minimal-new-kernel-no-zfs";
+    inherit system;
+  });
+
   # A bootable VirtualBox virtual appliance as an OVA file (i.e. packaged OVF).
   ova = forMatchingSystems [ "x86_64-linux" ] (system:
 
@@ -390,6 +405,12 @@ in rec {
         services.xserver.desktopManager.pantheon.enable = true;
       });
 
+    deepin = makeClosure ({ ... }:
+      { services.xserver.enable = true;
+        services.xserver.displayManager.lightdm.enable = true;
+        services.xserver.desktopManager.deepin.enable = true;
+      });
+
     # Linux/Apache/PostgreSQL/PHP stack.
     lapp = makeClosure ({ pkgs, ... }:
       { services.httpd.enable = true;
diff --git a/nixpkgs/nixos/tests/3proxy.nix b/nixpkgs/nixos/tests/3proxy.nix
index 8127438fabd9..83d39de018a3 100644
--- a/nixpkgs/nixos/tests/3proxy.nix
+++ b/nixpkgs/nixos/tests/3proxy.nix
@@ -1,8 +1,6 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
+{ lib, pkgs, ... }: {
   name = "3proxy";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ misuzu ];
-  };
+  meta.maintainers = with lib.maintainers; [ misuzu ];
 
   nodes = {
     peer0 = { lib, ... }: {
@@ -92,7 +90,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       networking.firewall.allowedTCPPorts = [ 3128 9999 ];
     };
 
-    peer3 = { lib, ... }: {
+    peer3 = { lib, pkgs, ... }: {
       networking.useDHCP = false;
       networking.interfaces.eth1 = {
         ipv4.addresses = [
@@ -186,4 +184,4 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://127.0.0.1:9999"
     )
   '';
-})
+}
diff --git a/nixpkgs/nixos/tests/aaaaxy.nix b/nixpkgs/nixos/tests/aaaaxy.nix
new file mode 100644
index 000000000000..19861198c369
--- /dev/null
+++ b/nixpkgs/nixos/tests/aaaaxy.nix
@@ -0,0 +1,29 @@
+{ pkgs, lib, ... }: {
+  name = "aaaaxy";
+  meta.maintainers = with lib.maintainers; [ Luflosi ];
+
+  nodes.machine = {
+    imports = [
+      ./common/x11.nix
+    ];
+  };
+
+  # This starts the game from a known state, feeds it a prerecorded set of button presses
+  # and then checks if the final game state is identical to the expected state.
+  # This is also what AAAAXY's CI system does and serves as a good sanity check.
+  testScript = ''
+    machine.wait_for_x()
+
+    machine.succeed(
+      # benchmark.dem needs to be in a mutable directory,
+      # so we can't just refer to the file in the Nix store directly
+      "mkdir -p '/tmp/aaaaxy/assets/demos/'",
+      "ln -s '${pkgs.aaaaxy.testing_infra}/assets/demos/benchmark.dem' '/tmp/aaaaxy/assets/demos/'",
+      """
+        '${pkgs.aaaaxy.testing_infra}/scripts/regression-test-demo.sh' \
+        'aaaaxy' 'on track for Any%, All Paths, No Teleports and No Coil' \
+        '${pkgs.aaaaxy}/bin/aaaaxy' '/tmp/aaaaxy/assets/demos/benchmark.dem'
+      """,
+    )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/acme-dns.nix b/nixpkgs/nixos/tests/acme-dns.nix
new file mode 100644
index 000000000000..92d9498fe714
--- /dev/null
+++ b/nixpkgs/nixos/tests/acme-dns.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "acme-dns";
+
+  nodes.machine = { pkgs, ... }: {
+    services.acme-dns = {
+      enable = true;
+      settings = {
+        general = rec {
+          domain = "acme-dns.home.arpa";
+          nsname = domain;
+          nsadmin = "admin.home.arpa";
+          records = [
+            "${domain}. A 127.0.0.1"
+            "${domain}. AAAA ::1"
+            "${domain}. NS ${domain}."
+          ];
+        };
+        logconfig.loglevel = "debug";
+      };
+    };
+    environment.systemPackages = with pkgs; [ curl bind ];
+  };
+
+  testScript = ''
+    import json
+
+    machine.wait_for_unit("acme-dns.service")
+    machine.wait_for_open_port(53) # dns
+    machine.wait_for_open_port(8080) # http api
+
+    result = machine.succeed("curl --fail -X POST http://localhost:8080/register")
+    print(result)
+
+    registration = json.loads(result)
+
+    machine.succeed(f'dig -t TXT @localhost {registration["fulldomain"]} | grep "SOA" | grep "admin.home.arpa"')
+
+    # acme-dns exspects a TXT value string length of exactly 43 chars
+    txt = "___dummy_validation_token_for_txt_record___"
+
+    machine.succeed(
+      "curl --fail -X POST http://localhost:8080/update "
+      + f' -H "X-Api-User: {registration["username"]}"'
+      + f' -H "X-Api-Key: {registration["password"]}"'
+      + f' -d \'{{"subdomain":"{registration["subdomain"]}", "txt":"{txt}"}}\'''
+    )
+
+    assert txt in machine.succeed(f'dig -t TXT +short @localhost {registration["fulldomain"]}')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/acme.nix b/nixpkgs/nixos/tests/acme.nix
index c07f99c5db3a..4d220b9747aa 100644
--- a/nixpkgs/nixos/tests/acme.nix
+++ b/nixpkgs/nixos/tests/acme.nix
@@ -1,7 +1,7 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: let
+{ pkgs, lib, ... }: let
   commonConfig = ./common/acme/client;
 
-  dnsServerIP = nodes: nodes.dnsserver.config.networking.primaryIPAddress;
+  dnsServerIP = nodes: nodes.dnsserver.networking.primaryIPAddress;
 
   dnsScript = nodes: let
     dnsAddress = dnsServerIP nodes;
@@ -41,6 +41,16 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let
     inherit documentRoot;
   };
 
+  simpleConfig = {
+    security.acme = {
+      certs."http.example.test" = {
+        listenHTTP = ":80";
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+
   # Base specialisation config for testing general ACME features
   webserverBasicConfig = {
     services.nginx.enable = true;
@@ -134,7 +144,11 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let
 
 in {
   name = "acme";
-  meta.maintainers = lib.teams.acme.members;
+  meta = {
+    maintainers = lib.teams.acme.members;
+    # Hard timeout in seconds. Average run time is about 7 minutes.
+    timeout = 1800;
+  };
 
   nodes = {
     # The fake ACME server which will respond to client requests
@@ -153,7 +167,7 @@ in {
         description = "Pebble ACME challenge test server";
         wantedBy = [ "network.target" ];
         serviceConfig = {
-          ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.config.networking.primaryIPAddress}'";
+          ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.networking.primaryIPAddress}'";
           # Required to bind on privileged ports.
           AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
         };
@@ -173,9 +187,29 @@ in {
       services.nginx.logError = "stderr info";
 
       specialisation = {
+        # Tests HTTP-01 verification using Lego's built-in web server
+        http01lego.configuration = simpleConfig;
+
+        renew.configuration = lib.mkMerge [
+          simpleConfig
+          {
+            # Pebble provides 5 year long certs,
+            # needs to be higher than that to test renewal
+            security.acme.certs."http.example.test".validMinDays = 9999;
+          }
+        ];
+
+        # Tests that account creds can be safely changed.
+        accountchange.configuration = lib.mkMerge [
+          simpleConfig
+          {
+            security.acme.certs."http.example.test".email = "admin@example.test";
+          }
+        ];
+
         # First derivation used to test general ACME features
         general.configuration = { ... }: let
-          caDomain = nodes.acme.config.test-support.acme.caDomain;
+          caDomain = nodes.acme.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 = ''
@@ -247,7 +281,7 @@ in {
           };
         };
 
-      # Test compatiblity with Caddy
+      # Test compatibility with Caddy
       # It only supports useACMEHost, hence not using mkServerConfigs
       } // (let
         baseCaddyConfig = { nodes, config, ... }: {
@@ -316,7 +350,7 @@ in {
 
   testScript = { nodes, ... }:
     let
-      caDomain = nodes.acme.config.test-support.acme.caDomain;
+      caDomain = nodes.acme.test-support.acme.caDomain;
       newServerSystem = nodes.webserver.config.system.build.toplevel;
       switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
     in
@@ -327,6 +361,30 @@ in {
       import time
 
 
+      TOTAL_RETRIES = 20
+
+
+      class BackoffTracker(object):
+          delay = 1
+          increment = 1
+
+          def handle_fail(self, retries, message) -> int:
+              assert retries < TOTAL_RETRIES, message
+
+              print(f"Retrying in {self.delay}s, {retries + 1}/{TOTAL_RETRIES}")
+              time.sleep(self.delay)
+
+              # Only increment after the first try
+              if retries == 0:
+                  self.delay += self.increment
+                  self.increment *= 2
+
+              return retries + 1
+
+
+      backoff = BackoffTracker()
+
+
       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
@@ -349,7 +407,7 @@ in {
       # Ensures the issuer of our cert matches the chain
       # and matches the issuer we expect it to be.
       # It's a good validation to ensure the cert.pem and fullchain.pem
-      # are not still selfsigned afer verification
+      # are not still selfsigned after verification
       def check_issuer(node, cert_name, issuer):
           for fname in ("cert.pem", "fullchain.pem"):
               actual_issuer = node.succeed(
@@ -374,9 +432,7 @@ in {
           assert False
 
 
-      def check_connection(node, domain, retries=3):
-          assert retries >= 0, f"Failed to connect to https://{domain}"
-
+      def check_connection(node, domain, retries=0):
           result = node.succeed(
               "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt"
               f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1"
@@ -384,13 +440,11 @@ in {
 
           for line in result.lower().split("\n"):
               if "verification" in line and "error" in line:
-                  time.sleep(3)
-                  return check_connection(node, domain, retries - 1)
+                  retries = backoff.handle_fail(retries, f"Failed to connect to https://{domain}")
+                  return check_connection(node, domain, retries)
 
 
-      def check_connection_key_bits(node, domain, bits, retries=3):
-          assert retries >= 0, f"Did not find expected number of bits ({bits}) in key"
-
+      def check_connection_key_bits(node, domain, bits, retries=0):
           result = node.succeed(
               "openssl s_client -CAfile /tmp/ca.crt"
               f" -servername {domain} -connect {domain}:443 < /dev/null"
@@ -399,13 +453,11 @@ in {
           print("Key type:", result)
 
           if bits not in result:
-              time.sleep(3)
-              return check_connection_key_bits(node, domain, bits, retries - 1)
-
+              retries = backoff.handle_fail(retries, f"Did not find expected number of bits ({bits}) in key")
+              return check_connection_key_bits(node, domain, bits, retries)
 
-      def check_stapling(node, domain, retries=3):
-          assert retries >= 0, "OCSP Stapling check failed"
 
+      def check_stapling(node, domain, retries=0):
           # Pebble doesn't provide a full OCSP responder, so just check the URL
           result = node.succeed(
               "openssl s_client -CAfile /tmp/ca.crt"
@@ -415,21 +467,19 @@ in {
           print("OCSP Responder URL:", result)
 
           if "${caDomain}:4002" not in result.lower():
-              time.sleep(3)
-              return check_stapling(node, domain, retries - 1)
-
+              retries = backoff.handle_fail(retries, "OCSP Stapling check failed")
+              return check_stapling(node, domain, retries)
 
-      def download_ca_certs(node, retries=5):
-          assert retries >= 0, "Failed to connect to pebble to download root CA certs"
 
+      def download_ca_certs(node, retries=0):
           exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
           exit_code_2, _ = node.execute(
               "curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt"
           )
 
           if exit_code + exit_code_2 > 0:
-              time.sleep(3)
-              return download_ca_certs(node, retries - 1)
+              retries = backoff.handle_fail(retries, "Failed to connect to pebble to download root CA certs")
+              return download_ca_certs(node, retries)
 
 
       start_all()
@@ -438,7 +488,7 @@ in {
       client.wait_for_unit("default.target")
 
       client.succeed(
-          'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.config.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a'
+          'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a'
       )
 
       acme.wait_for_unit("network-online.target")
@@ -446,7 +496,35 @@ in {
 
       download_ca_certs(client)
 
-      # Perform general tests first
+      # Perform http-01 w/ lego test first
+      with subtest("Can request certificate with Lego's built in web server"):
+          switch_to(webserver, "http01lego")
+          webserver.wait_for_unit("acme-finished-http.example.test.target")
+          check_fullchain(webserver, "http.example.test")
+          check_issuer(webserver, "http.example.test", "pebble")
+
+      # Perform renewal test
+      with subtest("Can renew certificates when they expire"):
+          hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          switch_to(webserver, "renew")
+          webserver.wait_for_unit("acme-finished-http.example.test.target")
+          check_fullchain(webserver, "http.example.test")
+          check_issuer(webserver, "http.example.test", "pebble")
+          hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          assert hash != hash_after
+
+      # Perform account change test
+      with subtest("Handles email change correctly"):
+          hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          switch_to(webserver, "accountchange")
+          webserver.wait_for_unit("acme-finished-http.example.test.target")
+          check_fullchain(webserver, "http.example.test")
+          check_issuer(webserver, "http.example.test", "pebble")
+          hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          # Has to do a full run to register account, which creates new certs.
+          assert hash != hash_after
+
+      # Perform general tests
       switch_to(webserver, "general")
 
       with subtest("Can request certificate with HTTP-01 challenge"):
@@ -594,4 +672,4 @@ in {
               wait_for_server()
               check_connection_key_bits(client, test_domain, "384")
     '';
-})
+}
diff --git a/nixpkgs/nixos/tests/adguardhome.nix b/nixpkgs/nixos/tests/adguardhome.nix
index ddbe8ff9c117..ec1cc1e497b5 100644
--- a/nixpkgs/nixos/tests/adguardhome.nix
+++ b/nixpkgs/nixos/tests/adguardhome.nix
@@ -1,9 +1,14 @@
-import ./make-test-python.nix {
+{
   name = "adguardhome";
 
   nodes = {
-    minimalConf = { ... }: {
-      services.adguardhome = { enable = true; };
+    nullConf = { ... }: { services.adguardhome = { enable = true; }; };
+
+    emptyConf = { lib, ... }: {
+      services.adguardhome = {
+        enable = true;
+        settings = {};
+      };
     };
 
     declarativeConf = { ... }: {
@@ -12,6 +17,7 @@ import ./make-test-python.nix {
 
         mutableSettings = false;
         settings = {
+          schema_version = 0;
           dns = {
             bind_host = "0.0.0.0";
             bootstrap_dns = "127.0.0.1";
@@ -26,6 +32,7 @@ import ./make-test-python.nix {
 
         mutableSettings = true;
         settings = {
+          schema_version = 0;
           dns = {
             bind_host = "0.0.0.0";
             bootstrap_dns = "127.0.0.1";
@@ -36,9 +43,12 @@ import ./make-test-python.nix {
   };
 
   testScript = ''
-    with subtest("Minimal config test"):
-        minimalConf.wait_for_unit("adguardhome.service")
-        minimalConf.wait_for_open_port(3000)
+    with subtest("Minimal (settings = null) config test"):
+        nullConf.wait_for_unit("adguardhome.service")
+
+    with subtest("Default config test"):
+        emptyConf.wait_for_unit("adguardhome.service")
+        emptyConf.wait_for_open_port(3000)
 
     with subtest("Declarative config test, DNS will be reachable"):
         declarativeConf.wait_for_unit("adguardhome.service")
diff --git a/nixpkgs/nixos/tests/aesmd.nix b/nixpkgs/nixos/tests/aesmd.nix
index 9f07426be8d8..848e1c599201 100644
--- a/nixpkgs/nixos/tests/aesmd.nix
+++ b/nixpkgs/nixos/tests/aesmd.nix
@@ -1,7 +1,7 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
+{ pkgs, lib, ... }: {
   name = "aesmd";
   meta = {
-    maintainers = with lib.maintainers; [ veehaitch ];
+    maintainers = with lib.maintainers; [ trundle veehaitch ];
   };
 
   nodes.machine = { lib, ... }: {
@@ -25,38 +25,78 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
     # We don't have a real SGX machine in NixOS tests
     systemd.services.aesmd.unitConfig.AssertPathExists = lib.mkForce [ ];
+
+    specialisation = {
+      withQuoteProvider.configuration = { ... }: {
+        services.aesmd = {
+          quoteProviderLibrary = pkgs.sgx-azure-dcap-client;
+          environment = {
+            AZDCAP_DEBUG_LOG_LEVEL = "INFO";
+          };
+        };
+      };
+    };
   };
 
-  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"
-  '';
-})
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      def get_aesmd_pid():
+        status, main_pid = machine.systemctl("show --property MainPID --value aesmd.service")
+        assert status == 0, "Could not get MainPID of aesmd.service"
+        return main_pid.strip()
+
+      with subtest("aesmd.service starts"):
+        machine.wait_for_unit("aesmd.service")
+
+      main_pid = get_aesmd_pid()
+
+      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"
+
+      with subtest("aesmd.service without quote provider library has correct LD_LIBRARY_PATH"):
+        status, environment = machine.systemctl("show --property Environment --value aesmd.service")
+        assert status == 0, "Could not get Environment of aesmd.service"
+        env_by_name = dict(entry.split("=", 1) for entry in environment.split())
+        assert not env_by_name["LD_LIBRARY_PATH"], "LD_LIBRARY_PATH is not empty"
+
+      with subtest("aesmd.service with quote provider library starts"):
+        machine.succeed('${specialisations}/withQuoteProvider/bin/switch-to-configuration test')
+        machine.wait_for_unit("aesmd.service")
+
+      main_pid = get_aesmd_pid()
+
+      with subtest("aesmd.service with quote provider library has correct LD_LIBRARY_PATH"):
+        ld_library_path = machine.succeed(f"xargs -0 -L1 -a /proc/{main_pid}/environ | grep LD_LIBRARY_PATH")
+        assert ld_library_path.startswith("LD_LIBRARY_PATH=${pkgs.sgx-azure-dcap-client}/lib:"), \
+          "LD_LIBRARY_PATH is not set to the configured quote provider library"
+
+      with subtest("aesmd.service with quote provider library has set AZDCAP_DEBUG_LOG_LEVEL"):
+        azdcp_debug_log_level = machine.succeed(f"xargs -0 -L1 -a /proc/{main_pid}/environ | grep AZDCAP_DEBUG_LOG_LEVEL")
+        assert azdcp_debug_log_level == "AZDCAP_DEBUG_LOG_LEVEL=INFO\n", "AZDCAP_DEBUG_LOG_LEVEL is not set to INFO"
+    '';
+}
diff --git a/nixpkgs/nixos/tests/akkoma.nix b/nixpkgs/nixos/tests/akkoma.nix
new file mode 100644
index 000000000000..7115c0beed34
--- /dev/null
+++ b/nixpkgs/nixos/tests/akkoma.nix
@@ -0,0 +1,121 @@
+/*
+  End-to-end test for Akkoma.
+
+  Based in part on nixos/tests/pleroma.
+
+  TODO: Test federation.
+*/
+import ./make-test-python.nix ({ pkgs, package ? pkgs.akkoma, confined ? false, ... }:
+let
+  userPassword = "4LKOrGo8SgbPm1a6NclVU5Wb";
+
+  provisionUser = pkgs.writers.writeBashBin "provisionUser" ''
+    set -eu -o errtrace -o pipefail
+
+    pleroma_ctl user new jamy jamy@nixos.test --password '${userPassword}' --moderator --admin -y
+  '';
+
+  tlsCert = pkgs.runCommand "selfSignedCerts" {
+    nativeBuildInputs = with pkgs; [ openssl ];
+  } ''
+    mkdir -p $out
+    openssl req -x509 \
+      -subj '/CN=akkoma.nixos.test/' -days 49710 \
+      -addext 'subjectAltName = DNS:akkoma.nixos.test' \
+      -keyout "$out/key.pem" -newkey ed25519 \
+      -out "$out/cert.pem" -noenc
+  '';
+
+  sendToot = pkgs.writers.writeBashBin "sendToot" ''
+    set -eu -o errtrace -o pipefail
+
+    export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
+
+    echo '${userPassword}' | ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test"
+    echo "y" | ${pkgs.toot}/bin/toot post "hello world Jamy here"
+    echo "y" | ${pkgs.toot}/bin/toot timeline | grep -F -q "hello world Jamy here"
+
+    # Test file upload
+    echo "y" | ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) \
+      | grep -F -q "https://akkoma.nixos.test/media"
+  '';
+
+  checkFe = pkgs.writers.writeBashBin "checkFe" ''
+    set -eu -o errtrace -o pipefail
+
+    paths=( / /static/{config,styles}.json /pleroma/admin/ )
+
+    for path in "''${paths[@]}"; do
+      diff \
+        <(${pkgs.curl}/bin/curl -f -S -s -o /dev/null -w '%{response_code}' "https://akkoma.nixos.test$path") \
+        <(echo -n 200)
+    done
+  '';
+
+  hosts = nodes: ''
+    ${nodes.akkoma.networking.primaryIPAddress} akkoma.nixos.test
+    ${nodes.client.networking.primaryIPAddress} client.nixos.test
+  '';
+in
+{
+  name = "akkoma";
+  nodes = {
+    client = { nodes, pkgs, config, ... }: {
+      security.pki.certificateFiles = [ "${tlsCert}/cert.pem" ];
+      networking.extraHosts = hosts nodes;
+    };
+
+    akkoma = { nodes, pkgs, config, ... }: {
+      networking.extraHosts = hosts nodes;
+      networking.firewall.allowedTCPPorts = [ 443 ];
+      environment.systemPackages = with pkgs; [ provisionUser ];
+      systemd.services.akkoma.confinement.enable = confined;
+
+      services.akkoma = {
+        enable = true;
+        package = package;
+        config = {
+          ":pleroma" = {
+            ":instance" = {
+              name = "NixOS test Akkoma server";
+              description = "NixOS test Akkoma server";
+              email = "akkoma@nixos.test";
+              notify_email = "akkoma@nixos.test";
+              registration_open = true;
+            };
+
+            ":media_proxy" = {
+              enabled = false;
+            };
+
+            "Pleroma.Web.Endpoint" = {
+              url.host = "akkoma.nixos.test";
+            };
+          };
+        };
+
+        nginx = {
+          addSSL = true;
+          sslCertificate = "${tlsCert}/cert.pem";
+          sslCertificateKey = "${tlsCert}/key.pem";
+        };
+      };
+
+      services.nginx.enable = true;
+      services.postgresql.enable = true;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    akkoma.wait_for_unit('akkoma-initdb.service')
+    akkoma.systemctl('restart akkoma-initdb.service')  # test repeated initialisation
+    akkoma.wait_for_unit('akkoma.service')
+    akkoma.wait_for_file('/run/akkoma/socket');
+    akkoma.succeed('${provisionUser}/bin/provisionUser')
+    akkoma.wait_for_unit('nginx.service')
+    client.succeed('${sendToot}/bin/sendToot')
+    client.succeed('${checkFe}/bin/checkFe')
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/alice-lg.nix b/nixpkgs/nixos/tests/alice-lg.nix
new file mode 100644
index 000000000000..640e60030a04
--- /dev/null
+++ b/nixpkgs/nixos/tests/alice-lg.nix
@@ -0,0 +1,44 @@
+# This test does a basic functionality check for alice-lg
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+in
+makeTest {
+  name = "birdwatcher";
+  nodes = {
+    host1 = {
+      environment.systemPackages = with pkgs; [ jq ];
+      services.alice-lg = {
+        enable = true;
+        settings = {
+          server = {
+            listen_http = "[::]:7340";
+            enable_prefix_lookup = true;
+            asn = 1;
+            routes_store_refresh_parallelism = 5;
+            neighbors_store_refresh_parallelism = 10000;
+            routes_store_refresh_interval = 5;
+            neighbors_store_refresh_interval = 5;
+          };
+          housekeeping = {
+            interval = 5;
+            force_release_memory = true;
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("alice-lg.service")
+    host1.wait_for_open_port(7340)
+    host1.succeed("curl http://[::]:7340 | grep 'Alice BGP Looking Glass'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/all-tests.nix b/nixpkgs/nixos/tests/all-tests.nix
index c718c292b257..e597a26f31bb 100644
--- a/nixpkgs/nixos/tests/all-tests.nix
+++ b/nixpkgs/nixos/tests/all-tests.nix
@@ -1,4 +1,11 @@
-{ system, pkgs, callTest }:
+{ system,
+  pkgs,
+
+  # Projects the test configuration into a the desired value; usually
+  # the test runner: `config: config.test`.
+  callTest,
+
+}:
 # The return value of this function will be an attrset with arbitrary depth and
 # the `anything` returned by callTest at its test leafs.
 # The tests not supported by `system` will be replaced with `{}`, so that
@@ -11,9 +18,18 @@ with pkgs.lib;
 
 let
   discoverTests = val:
-    if !isAttrs val then val
-    else if hasAttr "test" val then callTest val
-    else mapAttrs (n: s: discoverTests s) val;
+    if isAttrs val
+    then
+      if hasAttr "test" val then callTest val
+      else mapAttrs (n: s: discoverTests s) val
+    else if isFunction val
+      then
+        # Tests based on make-test-python.nix will return the second lambda
+        # in that file, which are then forwarded to the test definition
+        # following the `import make-test-python.nix` expression
+        # (if it is a function).
+        discoverTests (val { inherit system pkgs; })
+      else val;
   handleTest = path: args:
     discoverTests (import path ({ inherit system pkgs; } // args));
   handleTestOn = systems: path: args:
@@ -27,21 +43,78 @@ let
   };
   evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
 
+  inherit
+    (rec {
+      doRunTest = arg: ((import ../lib/testing-python.nix { inherit system pkgs; }).evalTest {
+        imports = [ arg readOnlyPkgs ];
+      }).config.result;
+      findTests = tree:
+        if tree?recurseForDerivations && tree.recurseForDerivations
+        then
+          mapAttrs
+            (k: findTests)
+            (builtins.removeAttrs tree ["recurseForDerivations"])
+        else callTest tree;
+
+      runTest = arg: let r = doRunTest arg; in findTests r;
+      runTestOn = systems: arg:
+        if elem system systems then runTest arg
+        else {};
+    })
+    runTest
+    runTestOn
+    ;
+
+  # Using a single instance of nixpkgs makes test evaluation faster.
+  # To make sure we don't accidentally depend on a modified pkgs, we make the
+  # related options read-only. We need to test the right configuration.
+  #
+  # If your service depends on a nixpkgs setting, first try to avoid that, but
+  # otherwise, you can remove the readOnlyPkgs import and test your service as
+  # usual.
+  readOnlyPkgs =
+    # TODO: We currently accept this for nixosTests, so that the `pkgs` argument
+    #       is consistent with `pkgs` in `pkgs.nixosTests`. Can we reinitialize
+    #       it with `allowAliases = false`?
+    # warnIf pkgs.config.allowAliases "nixosTests: pkgs includes aliases."
+    {
+      _class = "nixosTest";
+      node.pkgs = pkgs;
+    };
+
 in {
-  _3proxy = handleTest ./3proxy.nix {};
-  acme = handleTest ./acme.nix {};
-  adguardhome = handleTest ./adguardhome.nix {};
-  aesmd = handleTest ./aesmd.nix {};
-  agate = handleTest ./web-servers/agate.nix {};
+
+  # Testing the test driver
+  nixos-test-driver = {
+    extra-python-packages = handleTest ./nixos-test-driver/extra-python-packages.nix {};
+    node-name = runTest ./nixos-test-driver/node-name.nix;
+  };
+
+  # NixOS vm tests and non-vm unit tests
+
+  _3proxy = runTest ./3proxy.nix;
+  aaaaxy = runTest ./aaaaxy.nix;
+  acme = runTest ./acme.nix;
+  acme-dns = handleTest ./acme-dns.nix {};
+  adguardhome = runTest ./adguardhome.nix;
+  aesmd = runTestOn ["x86_64-linux"] ./aesmd.nix;
+  agate = runTest ./web-servers/agate.nix;
   agda = handleTest ./agda.nix {};
   airsonic = handleTest ./airsonic.nix {};
+  akkoma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./akkoma.nix {};
+  akkoma-confined = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./akkoma.nix { confined = true; };
+  alice-lg = handleTest ./alice-lg.nix {};
   allTerminfo = handleTest ./all-terminfo.nix {};
+  alps = handleTest ./alps.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.nix {};
+  apcupsd = handleTest ./apcupsd.nix {};
   apfs = handleTest ./apfs.nix {};
   apparmor = handleTest ./apparmor.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
+  atuin = handleTest ./atuin.nix {};
   auth-mysql = handleTest ./auth-mysql.nix {};
+  authelia = handleTest ./authelia.nix {};
   avahi = handleTest ./avahi.nix {};
   avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
   babeld = handleTest ./babeld.nix {};
@@ -49,13 +122,16 @@ in {
   bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
   beanstalkd = handleTest ./beanstalkd.nix {};
   bees = handleTest ./bees.nix {};
+  binary-cache = handleTest ./binary-cache.nix {};
   bind = handleTest ./bind.nix {};
   bird = handleTest ./bird.nix {};
+  birdwatcher = handleTest ./birdwatcher.nix {};
   bitcoind = handleTest ./bitcoind.nix {};
   bittorrent = handleTest ./bittorrent.nix {};
   blockbook-frontend = handleTest ./blockbook-frontend.nix {};
   blocky = handleTest ./blocky.nix {};
   boot = handleTestOn ["x86_64-linux" "aarch64-linux"] ./boot.nix {};
+  bootspec = handleTestOn ["x86_64-linux"] ./bootspec.nix {};
   boot-stage1 = handleTest ./boot-stage1.nix {};
   borgbackup = handleTest ./borgbackup.nix {};
   botamusique = handleTest ./botamusique.nix {};
@@ -63,7 +139,10 @@ in {
   breitbandmessung = handleTest ./breitbandmessung.nix {};
   brscan5 = handleTest ./brscan5.nix {};
   btrbk = handleTest ./btrbk.nix {};
+  btrbk-doas = handleTest ./btrbk-doas.nix {};
   btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
+  btrbk-section-order = handleTest ./btrbk-section-order.nix {};
+  budgie = handleTest ./budgie.nix {};
   buildbot = handleTest ./buildbot.nix {};
   buildkite-agents = handleTest ./buildkite-agents.nix {};
   caddy = handleTest ./caddy.nix {};
@@ -71,24 +150,33 @@ in {
   cage = handleTest ./cage.nix {};
   cagebreak = handleTest ./cagebreak.nix {};
   calibre-web = handleTest ./calibre-web.nix {};
-  cassandra_2_1 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_1; };
-  cassandra_2_2 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_2; };
   cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; };
   cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; };
-  ceph-multi-node = handleTestOn ["x86_64-linux"] ./ceph-multi-node.nix {};
-  ceph-single-node = handleTestOn ["x86_64-linux"] ./ceph-single-node.nix {};
-  ceph-single-node-bluestore = handleTestOn ["x86_64-linux"] ./ceph-single-node-bluestore.nix {};
+  cassandra_4 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_4; };
+  ceph-multi-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-multi-node.nix {};
+  ceph-single-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node.nix {};
+  ceph-single-node-bluestore = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node-bluestore.nix {};
   certmgr = handleTest ./certmgr.nix {};
-  cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
+  cfssl = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cfssl.nix {};
+  cgit = handleTest ./cgit.nix {};
   charliecloud = handleTest ./charliecloud.nix {};
-  chromium = (handleTestOn ["x86_64-linux"] ./chromium.nix {}).stable or {};
+  chromium = (handleTestOn ["aarch64-linux" "x86_64-linux"] ./chromium.nix {}).stable or {};
+  chrony-ptp = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony-ptp.nix {};
+  cinnamon = handleTest ./cinnamon.nix {};
   cjdns = handleTest ./cjdns.nix {};
   clickhouse = handleTest ./clickhouse.nix {};
   cloud-init = handleTest ./cloud-init.nix {};
-  cntr = handleTest ./cntr.nix {};
+  cloud-init-hostname = handleTest ./cloud-init-hostname.nix {};
+  cloudlog = handleTest ./cloudlog.nix {};
+  cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {};
+  cockpit = handleTest ./cockpit.nix {};
   cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
+  code-server = handleTest ./code-server.nix {};
+  coder = handleTest ./coder.nix {};
   collectd = handleTest ./collectd.nix {};
+  connman = handleTest ./connman.nix {};
   consul = handleTest ./consul.nix {};
+  consul-template = handleTest ./consul-template.nix {};
   containers-bridge = handleTest ./containers-bridge.nix {};
   containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {};
   containers-ephemeral = handleTest ./containers-ephemeral.nix {};
@@ -104,13 +192,17 @@ in {
   containers-reloadable = handleTest ./containers-reloadable.nix {};
   containers-restart_networking = handleTest ./containers-restart_networking.nix {};
   containers-tmpfs = handleTest ./containers-tmpfs.nix {};
+  containers-unified-hierarchy = handleTest ./containers-unified-hierarchy.nix {};
   convos = handleTest ./convos.nix {};
   corerad = handleTest ./corerad.nix {};
   coturn = handleTest ./coturn.nix {};
   couchdb = handleTest ./couchdb.nix {};
-  cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {};
+  cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {};
+  cups-pdf = handleTest ./cups-pdf.nix {};
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
+  darling = handleTest ./darling.nix {};
+  deepin = handleTest ./deepin.nix {};
   deluge = handleTest ./deluge.nix {};
   dendrite = handleTest ./matrix/dendrite.nix {};
   dex-oidc = handleTest ./dex-oidc.nix {};
@@ -121,8 +213,8 @@ in {
   dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {};
   dnsdist = handleTest ./dnsdist.nix {};
   doas = handleTest ./doas.nix {};
-  docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
-  docker-rootless = handleTestOn ["x86_64-linux"] ./docker-rootless.nix {};
+  docker = handleTestOn ["aarch64-linux" "x86_64-linux"] ./docker.nix {};
+  docker-rootless = handleTestOn ["aarch64-linux" "x86_64-linux"] ./docker-rootless.nix {};
   docker-registry = handleTest ./docker-registry.nix {};
   docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
   docker-tools-cross = handleTestOn ["x86_64-linux" "aarch64-linux"] ./docker-tools-cross.nix {};
@@ -131,66 +223,90 @@ in {
   documentation = pkgs.callPackage ../modules/misc/documentation/test.nix { inherit nixosLib; };
   doh-proxy-rust = handleTest ./doh-proxy-rust.nix {};
   dokuwiki = handleTest ./dokuwiki.nix {};
+  dolibarr = handleTest ./dolibarr.nix {};
   domination = handleTest ./domination.nix {};
   dovecot = handleTest ./dovecot.nix {};
   drbd = handleTest ./drbd.nix {};
   earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {};
+  early-mount-options = handleTest ./early-mount-options.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 {};
+  fscrypt = handleTest ./fscrypt.nix {};
   ejabberd = handleTest ./xmpp/ejabberd.nix {};
   elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
   emacs-daemon = handleTest ./emacs-daemon.nix {};
+  endlessh = handleTest ./endlessh.nix {};
+  endlessh-go = handleTest ./endlessh-go.nix {};
   engelsystem = handleTest ./engelsystem.nix {};
   enlightenment = handleTest ./enlightenment.nix {};
   env = handleTest ./env.nix {};
+  envfs = handleTest ./envfs.nix {};
   envoy = handleTest ./envoy.nix {};
   ergo = handleTest ./ergo.nix {};
   ergochat = handleTest ./ergochat.nix {};
+  esphome = handleTest ./esphome.nix {};
   etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
+  activation = pkgs.callPackage ../modules/system/activation/test.nix { };
   etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
   etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
   etebase-server = handleTest ./etebase-server.nix {};
   etesync-dav = handleTest ./etesync-dav.nix {};
-  extra-python-packages = handleTest ./extra-python-packages.nix {};
+  evcc = handleTest ./evcc.nix {};
   fancontrol = handleTest ./fancontrol.nix {};
-  fcitx = handleTest ./fcitx {};
+  fcitx5 = handleTest ./fcitx5 {};
   fenics = handleTest ./fenics.nix {};
   ferm = handleTest ./ferm.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
+  firefox-beta = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-beta; };
+  firefox-devedition = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-devedition; };
   firefox-esr    = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
-  firefox-esr-91 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-91; };
   firefox-esr-102 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-102; };
   firejail = handleTest ./firejail.nix {};
-  firewall = handleTest ./firewall.nix {};
+  firewall = handleTest ./firewall.nix { nftables = false; };
+  firewall-nftables = handleTest ./firewall.nix { nftables = true; };
   fish = handleTest ./fish.nix {};
   flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
   fluentd = handleTest ./fluentd.nix {};
   fluidd = handleTest ./fluidd.nix {};
   fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
+  forgejo = handleTest ./gitea.nix { giteaPackage = pkgs.forgejo; };
+  freenet = handleTest ./freenet.nix {};
   freeswitch = handleTest ./freeswitch.nix {};
+  freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
+  freshrss-pgsql = handleTest ./freshrss-pgsql.nix {};
+  frigate = handleTest ./frigate.nix {};
   frr = handleTest ./frr.nix {};
   fsck = handleTest ./fsck.nix {};
+  fsck-systemd-stage-1 = handleTest ./fsck.nix { systemdStage1 = true; };
   ft2-clone = handleTest ./ft2-clone.nix {};
+  legit = handleTest ./legit.nix {};
   mimir = handleTest ./mimir.nix {};
+  garage = handleTest ./garage {};
+  gemstash = handleTest ./gemstash.nix {};
   gerrit = handleTest ./gerrit.nix {};
   geth = handleTest ./geth.nix {};
   ghostunnel = handleTest ./ghostunnel.nix {};
   gitdaemon = handleTest ./gitdaemon.nix {};
-  gitea = handleTest ./gitea.nix {};
-  gitlab = handleTest ./gitlab.nix {};
+  gitea = handleTest ./gitea.nix { giteaPackage = pkgs.gitea; };
+  github-runner = handleTest ./github-runner.nix {};
+  gitlab = runTest ./gitlab.nix;
   gitolite = handleTest ./gitolite.nix {};
   gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
   glusterfs = handleTest ./glusterfs.nix {};
   gnome = handleTest ./gnome.nix {};
+  gnome-flashback = handleTest ./gnome-flashback.nix {};
   gnome-xorg = handleTest ./gnome-xorg.nix {};
+  gnupg = handleTest ./gnupg.nix {};
   go-neb = handleTest ./go-neb.nix {};
   gobgpd = handleTest ./gobgpd.nix {};
   gocd-agent = handleTest ./gocd-agent.nix {};
   gocd-server = handleTest ./gocd-server.nix {};
+  gollum = handleTest ./gollum.nix {};
+  gonic = handleTest ./gonic.nix {};
   google-oslogin = handleTest ./google-oslogin {};
   gotify-server = handleTest ./gotify-server.nix {};
-  grafana = handleTest ./grafana.nix {};
+  grafana = handleTest ./grafana {};
   grafana-agent = handleTest ./grafana-agent.nix {};
   graphite = handleTest ./graphite.nix {};
   graylog = handleTest ./graylog.nix {};
@@ -204,15 +320,17 @@ in {
   haste-server = handleTest ./haste-server.nix {};
   haproxy = handleTest ./haproxy.nix {};
   hardened = handleTest ./hardened.nix {};
+  harmonia = runTest ./harmonia.nix;
+  headscale = handleTest ./headscale.nix {};
   healthchecks = handleTest ./web-apps/healthchecks.nix {};
-  hbase1 = handleTest ./hbase.nix { package=pkgs.hbase1; };
   hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
+  hbase_2_4 = handleTest ./hbase.nix { package=pkgs.hbase_2_4; };
   hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; };
   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 {};
+  oci-containers = handleTestOn ["aarch64-linux" "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
@@ -231,20 +349,20 @@ in {
   i3wm = handleTest ./i3wm.nix {};
   icingaweb2 = handleTest ./icingaweb2.nix {};
   iftop = handleTest ./iftop.nix {};
-  ihatemoney = handleTest ./ihatemoney {};
   incron = handleTest ./incron.nix {};
   influxdb = handleTest ./influxdb.nix {};
   initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
+  initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix {};
   initrdNetwork = handleTest ./initrd-network.nix {};
   initrd-secrets = handleTest ./initrd-secrets.nix {};
+  initrd-secrets-changing = handleTest ./initrd-secrets-changing.nix {};
   input-remapper = handleTest ./input-remapper.nix {};
   inspircd = handleTest ./inspircd.nix {};
   installer = handleTest ./installer.nix {};
   installer-systemd-stage-1 = handleTest ./installer-systemd-stage-1.nix {};
   invoiceplane = handleTest ./invoiceplane.nix {};
   iodine = handleTest ./iodine.nix {};
-  ipfs = handleTest ./ipfs.nix {};
   ipv6 = handleTest ./ipv6.nix {};
   iscsi-multipath-root = handleTest ./iscsi-multipath-root.nix {};
   iscsi-root = handleTest ./iscsi-root.nix {};
@@ -259,6 +377,8 @@ in {
   k3s = handleTest ./k3s {};
   kafka = handleTest ./kafka.nix {};
   kanidm = handleTest ./kanidm.nix {};
+  karma = handleTest ./karma.nix {};
+  kavita = handleTest ./kavita.nix {};
   kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
   kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {};
   kea = handleTest ./kea.nix {};
@@ -270,44 +390,57 @@ in {
   keter = handleTest ./keter.nix {};
   kexec = handleTest ./kexec.nix {};
   keycloak = discoverTests (import ./keycloak.nix);
+  keyd = handleTest ./keyd.nix {};
   keymap = handleTest ./keymap.nix {};
   knot = handleTest ./knot.nix {};
   komga = handleTest ./komga.nix {};
   krb5 = discoverTests (import ./krb5 {});
   ksm = handleTest ./ksm.nix {};
+  kthxbye = handleTest ./kthxbye.nix {};
   kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
+  kubo = runTest ./kubo.nix;
+  ladybird = handleTest ./ladybird.nix {};
+  languagetool = handleTest ./languagetool.nix {};
   latestKernel.login = handleTest ./login.nix { latestKernel = true; };
   leaps = handleTest ./leaps.nix {};
+  lemmy = handleTest ./lemmy.nix {};
   libinput = handleTest ./libinput.nix {};
   libreddit = handleTest ./libreddit.nix {};
   libresprite = handleTest ./libresprite.nix {};
   libreswan = handleTest ./libreswan.nix {};
   librewolf = handleTest ./firefox.nix { firefoxPackage = pkgs.librewolf; };
   libuiohook = handleTest ./libuiohook.nix {};
+  libvirtd = handleTest ./libvirtd.nix {};
   lidarr = handleTest ./lidarr.nix {};
   lightdm = handleTest ./lightdm.nix {};
   lighttpd = handleTest ./lighttpd.nix {};
   limesurvey = handleTest ./limesurvey.nix {};
+  listmonk = handleTest ./listmonk.nix {};
   litestream = handleTest ./litestream.nix {};
+  lldap = handleTest ./lldap.nix {};
   locate = handleTest ./locate.nix {};
   login = handleTest ./login.nix {};
   logrotate = handleTest ./logrotate.nix {};
   loki = handleTest ./loki.nix {};
+  luks = handleTest ./luks.nix {};
   lvm2 = handleTest ./lvm2 {};
   lxd = handleTest ./lxd.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 {};
+  maddy = discoverTests (import ./maddy { inherit handleTest; });
   maestral = handleTest ./maestral.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
   magnetico = handleTest ./magnetico.nix {};
   mailcatcher = handleTest ./mailcatcher.nix {};
   mailhog = handleTest ./mailhog.nix {};
+  mailman = handleTest ./mailman.nix {};
   man = handleTest ./man.nix {};
   mariadb-galera = handleTest ./mysql/mariadb-galera.nix {};
-  mastodon = handleTestOn ["x86_64-linux" "i686-linux" "aarch64-linux"] ./web-apps/mastodon.nix {};
+  mastodon = discoverTests (import ./web-apps/mastodon { inherit handleTestOn; });
+  pixelfed = discoverTests (import ./web-apps/pixelfed { inherit handleTestOn; });
+  mate = handleTest ./mate.nix {};
   matomo = handleTest ./matomo.nix {};
   matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
   matrix-conduit = handleTest ./matrix/conduit.nix {};
@@ -317,16 +450,20 @@ in {
   mediawiki = handleTest ./mediawiki.nix {};
   meilisearch = handleTest ./meilisearch.nix {};
   memcached = handleTest ./memcached.nix {};
+  merecat = handleTest ./merecat.nix {};
   metabase = handleTest ./metabase.nix {};
+  mindustry = handleTest ./mindustry.nix {};
   minecraft = handleTest ./minecraft.nix {};
   minecraft-server = handleTest ./minecraft-server.nix {};
   minidlna = handleTest ./minidlna.nix {};
   miniflux = handleTest ./miniflux.nix {};
   minio = handleTest ./minio.nix {};
+  miriway = handleTest ./miriway.nix {};
   misc = handleTest ./misc.nix {};
   mjolnir = handleTest ./matrix/mjolnir.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
+  monica = handleTest ./web-apps/monica.nix {};
   mongodb = handleTest ./mongodb.nix {};
   moodle = handleTest ./moodle.nix {};
   moonraker = handleTest ./moonraker.nix {};
@@ -336,8 +473,11 @@ in {
   mpd = handleTest ./mpd.nix {};
   mpv = handleTest ./mpv.nix {};
   mtp = handleTest ./mtp.nix {};
+  multipass = handleTest ./multipass.nix {};
   mumble = handleTest ./mumble.nix {};
-  musescore = handleTest ./musescore.nix {};
+  # Fails on aarch64-linux at the PDF creation step - need to debug this on an
+  # aarch64 machine..
+  musescore = handleTestOn ["x86_64-linux"] ./musescore.nix {};
   munin = handleTest ./munin.nix {};
   mutableUsers = handleTest ./mutable-users.nix {};
   mxisd = handleTest ./mxisd.nix {};
@@ -349,20 +489,22 @@ in {
   nagios = handleTest ./nagios.nix {};
   nar-serve = handleTest ./nar-serve.nix {};
   nat.firewall = handleTest ./nat.nix { withFirewall = true; };
-  nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
   nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  nat.nftables.firewall = handleTest ./nat.nix { withFirewall = true; nftables = true; };
+  nat.nftables.standalone = handleTest ./nat.nix { withFirewall = false; nftables = true; };
   nats = handleTest ./nats.nix {};
   navidrome = handleTest ./navidrome.nix {};
   nbd = handleTest ./nbd.nix {};
   ncdns = handleTest ./ncdns.nix {};
   ndppd = handleTest ./ndppd.nix {};
   nebula = handleTest ./nebula.nix {};
+  netbird = handleTest ./netbird.nix {};
   neo4j = handleTest ./neo4j.nix {};
   netdata = handleTest ./netdata.nix {};
   networking.networkd = handleTest ./networking.nix { networkd = true; };
   networking.scripted = handleTest ./networking.nix { networkd = false; };
-  specialisation = handleTest ./specialisation.nix {};
-  netbox = handleTest ./web-apps/netbox.nix {};
+  netbox = handleTest ./web-apps/netbox.nix { inherit (pkgs) netbox; };
+  netbox_3_3 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_3; };
   # TODO: put in networking.nix after the test becomes more complete
   networkingProxy = handleTest ./networking-proxy.nix {};
   nextcloud = handleTest ./nextcloud {};
@@ -374,12 +516,15 @@ in {
   nginx = handleTest ./nginx.nix {};
   nginx-auth = handleTest ./nginx-auth.nix {};
   nginx-etag = handleTest ./nginx-etag.nix {};
+  nginx-globalredirect = handleTest ./nginx-globalredirect.nix {};
   nginx-http3 = handleTest ./nginx-http3.nix {};
   nginx-modsecurity = handleTest ./nginx-modsecurity.nix {};
+  nginx-njs = handleTest ./nginx-njs.nix {};
   nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
   nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
   nginx-sso = handleTest ./nginx-sso.nix {};
   nginx-variants = handleTest ./nginx-variants.nix {};
+  nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {};
   nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {};
   nitter = handleTest ./nitter.nix {};
   nix-ld = handleTest ./nix-ld.nix {};
@@ -387,28 +532,36 @@ in {
   nix-serve-ssh = handleTest ./nix-serve-ssh.nix {};
   nixops = handleTest ./nixops/default.nix {};
   nixos-generate-config = handleTest ./nixos-generate-config.nix {};
+  nixos-rebuild-specialisations = handleTest ./nixos-rebuild-specialisations.nix {};
   nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
   node-red = handleTest ./node-red.nix {};
   nomad = handleTest ./nomad.nix {};
   non-default-filesystems = handleTest ./non-default-filesystems.nix {};
   noto-fonts = handleTest ./noto-fonts.nix {};
+  noto-fonts-cjk-qt-default-weight = handleTest ./noto-fonts-cjk-qt-default-weight.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
+  nscd = handleTest ./nscd.nix {};
   nsd = handleTest ./nsd.nix {};
+  ntfy-sh = handleTest ./ntfy-sh.nix {};
   nzbget = handleTest ./nzbget.nix {};
   nzbhydra2 = handleTest ./nzbhydra2.nix {};
   oh-my-zsh = handleTest ./oh-my-zsh.nix {};
   ombi = handleTest ./ombi.nix {};
   openarena = handleTest ./openarena.nix {};
   openldap = handleTest ./openldap.nix {};
+  opensearch = discoverTests (import ./opensearch.nix);
   openresty-lua = handleTest ./openresty-lua.nix {};
   opensmtpd = handleTest ./opensmtpd.nix {};
   opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {};
   openssh = handleTest ./openssh.nix {};
+  octoprint = handleTest ./octoprint.nix {};
   openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {};
   openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
   opentabletdriver = handleTest ./opentabletdriver.nix {};
   owncast = handleTest ./owncast.nix {};
+  outline = handleTest ./outline.nix {};
   image-contents = handleTest ./image-contents.nix {};
+  openvscode-server = handleTest ./openvscode-server.nix {};
   orangefs = handleTest ./orangefs.nix {};
   os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {};
   osrm-backend = handleTest ./osrm-backend.nix {};
@@ -419,49 +572,59 @@ in {
   pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
   pam-u2f = handleTest ./pam/pam-u2f.nix {};
   pam-ussh = handleTest ./pam/pam-ussh.nix {};
+  pam-zfs-key = handleTest ./pam/zfs-key.nix {};
   pass-secret-service = handleTest ./pass-secret-service.nix {};
-  patroni = handleTest ./patroni.nix {};
+  patroni = handleTestOn ["x86_64-linux"] ./patroni.nix {};
   pantalaimon = handleTest ./matrix/pantalaimon.nix {};
   pantheon = handleTest ./pantheon.nix {};
   paperless = handleTest ./paperless.nix {};
   parsedmarc = handleTest ./parsedmarc {};
   pdns-recursor = handleTest ./pdns-recursor.nix {};
   peerflix = handleTest ./peerflix.nix {};
+  peering-manager = handleTest ./web-apps/peering-manager.nix {};
   peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
+  peroxide = handleTest ./peroxide.nix {};
   pgadmin4 = handleTest ./pgadmin4.nix {};
-  pgadmin4-standalone = handleTest ./pgadmin4-standalone.nix {};
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
+  phosh = handleTest ./phosh.nix {};
+  photoprism = handleTest ./photoprism.nix {};
   php = handleTest ./php {};
   php80 = handleTest ./php { php = pkgs.php80; };
   php81 = handleTest ./php { php = pkgs.php81; };
+  php82 = handleTest ./php { php = pkgs.php82; };
   phylactery = handleTest ./web-apps/phylactery.nix {};
   pict-rs = handleTest ./pict-rs.nix {};
   pinnwand = handleTest ./pinnwand.nix {};
+  plasma-bigscreen = handleTest ./plasma-bigscreen.nix {};
   plasma5 = handleTest ./plasma5.nix {};
   plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
   plausible = handleTest ./plausible.nix {};
+  please = handleTest ./please.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/default.nix {};
-  podman-dnsname = handleTestOn ["x86_64-linux"] ./podman/dnsname.nix {};
-  podman-tls-ghostunnel = handleTestOn ["x86_64-linux"] ./podman/tls-ghostunnel.nix {};
+  podman = handleTestOn ["aarch64-linux" "x86_64-linux"] ./podman/default.nix {};
+  podman-tls-ghostunnel = handleTestOn ["aarch64-linux" "x86_64-linux"] ./podman/tls-ghostunnel.nix {};
   polaris = handleTest ./polaris.nix {};
   pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
+  portunus = handleTest ./portunus.nix { };
   postfix = handleTest ./postfix.nix {};
   postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
   postfixadmin = handleTest ./postfixadmin.nix {};
   postgis = handleTest ./postgis.nix {};
+  apache_datasketches = handleTest ./apache_datasketches.nix {};
   postgresql = handleTest ./postgresql.nix {};
+  postgresql-jit = handleTest ./postgresql-jit.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 {};
+  printing-socket = handleTest ./printing.nix { socket = true; };
+  printing-service = handleTest ./printing.nix { socket = false; };
   privacyidea = handleTest ./privacyidea.nix {};
   privoxy = handleTest ./privoxy.nix {};
   prometheus = handleTest ./prometheus.nix {};
@@ -473,21 +636,25 @@ in {
   pt2-clone = handleTest ./pt2-clone.nix {};
   pykms = handleTest ./pykms.nix {};
   public-inbox = handleTest ./public-inbox.nix {};
+  pufferpanel = handleTest ./pufferpanel.nix {};
   pulseaudio = discoverTests (import ./pulseaudio.nix);
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
+  qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
   quorum = handleTest ./quorum.nix {};
+  quake3 = handleTest ./quake3.nix {};
   rabbitmq = handleTest ./rabbitmq.nix {};
   radarr = handleTest ./radarr.nix {};
   radicale = handleTest ./radicale.nix {};
   rasdaemon = handleTest ./rasdaemon.nix {};
+  readarr = handleTest ./readarr.nix {};
   redis = handleTest ./redis.nix {};
   redmine = handleTest ./redmine.nix {};
-  resolv = handleTest ./resolv.nix {};
   restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
   restic = handleTest ./restic.nix {};
   retroarch = handleTest ./retroarch.nix {};
   robustirc-bridge = handleTest ./robustirc-bridge.nix {};
   roundcube = handleTest ./roundcube.nix {};
+  rshim = handleTest ./rshim.nix {};
   rspamd = handleTest ./rspamd.nix {};
   rss2email = handleTest ./rss2email.nix {};
   rstudio-server = handleTest ./rstudio-server.nix {};
@@ -503,7 +670,9 @@ in {
   seafile = handleTest ./seafile.nix {};
   searx = handleTest ./searx.nix {};
   service-runner = handleTest ./service-runner.nix {};
+  sftpgo = runTest ./sftpgo.nix;
   sfxr-qt = handleTest ./sfxr-qt.nix {};
+  sgtpuzzles = handleTest ./sgtpuzzles.nix {};
   shadow = handleTest ./shadow.nix {};
   shadowsocks = handleTest ./shadowsocks {};
   shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
@@ -514,23 +683,28 @@ in {
   smokeping = handleTest ./smokeping.nix {};
   snapcast = handleTest ./snapcast.nix {};
   snapper = handleTest ./snapper.nix {};
+  snipe-it = runTest ./web-apps/snipe-it.nix;
   soapui = handleTest ./soapui.nix {};
   sogo = handleTest ./sogo.nix {};
   solanum = handleTest ./solanum.nix {};
-  solr = handleTest ./solr.nix {};
   sonarr = handleTest ./sonarr.nix {};
   sourcehut = handleTest ./sourcehut.nix {};
   spacecookie = handleTest ./spacecookie.nix {};
   spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {};
+  sqlite3-to-mysql = handleTest ./sqlite3-to-mysql.nix {};
   sslh = handleTest ./sslh.nix {};
   sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
   sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {};
+  stargazer = runTest ./web-servers/stargazer.nix;
   starship = handleTest ./starship.nix {};
   step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
+  stratis = handleTest ./stratis {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
   stunnel = handleTest ./stunnel.nix {};
   sudo = handleTest ./sudo.nix {};
+  swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
   swap-partition = handleTest ./swap-partition.nix {};
+  swap-random-encryption = handleTest ./swap-random-encryption.nix {};
   sway = handleTest ./sway.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
@@ -541,16 +715,26 @@ in {
   systemd-analyze = handleTest ./systemd-analyze.nix {};
   systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
   systemd-boot = handleTest ./systemd-boot.nix {};
+  systemd-bpf = handleTest ./systemd-bpf.nix {};
   systemd-confinement = handleTest ./systemd-confinement.nix {};
   systemd-coredump = handleTest ./systemd-coredump.nix {};
   systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
+  systemd-credentials-tpm2 = handleTest ./systemd-credentials-tpm2.nix {};
   systemd-escaping = handleTest ./systemd-escaping.nix {};
   systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {};
+  systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {};
   systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {};
+  systemd-initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix { systemdStage1 = true; };
   systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {};
+  systemd-initrd-luks-tpm2 = handleTest ./systemd-initrd-luks-tpm2.nix {};
+  systemd-initrd-modprobe = handleTest ./systemd-initrd-modprobe.nix {};
   systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; };
   systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
   systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};
+  systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {};
+  systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {};
+  systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {};
+  systemd-initrd-networkd-openvpn = handleTest ./initrd-network-openvpn { systemdStage1 = true; };
   systemd-journal = handleTest ./systemd-journal.nix {};
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
@@ -558,11 +742,20 @@ in {
   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-no-tainted = handleTest ./systemd-no-tainted.nix {};
   systemd-nspawn = handleTest ./systemd-nspawn.nix {};
+  systemd-oomd = handleTest ./systemd-oomd.nix {};
+  systemd-portabled = handleTest ./systemd-portabled.nix {};
+  systemd-repart = handleTest ./systemd-repart.nix {};
   systemd-shutdown = handleTest ./systemd-shutdown.nix {};
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
+  systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {};
   systemd-misc = handleTest ./systemd-misc.nix {};
+  systemd-userdbd = handleTest ./systemd-userdbd.nix {};
+  systemd-homed = handleTest ./systemd-homed.nix {};
+  tandoor-recipes = handleTest ./tandoor-recipes.nix {};
   taskserver = handleTest ./taskserver.nix {};
+  tayga = handleTest ./tayga.nix {};
   teeworlds = handleTest ./teeworlds.nix {};
   telegraf = handleTest ./telegraf.nix {};
   teleport = handleTest ./teleport.nix {};
@@ -570,16 +763,20 @@ in {
   terminal-emulators = handleTest ./terminal-emulators.nix {};
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   tigervnc = handleTest ./tigervnc.nix {};
+  timescaledb = handleTest ./timescaledb.nix {};
+  promscale = handleTest ./promscale.nix {};
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
   tinywl = handleTest ./tinywl.nix {};
+  tmate-ssh-server = handleTest ./tmate-ssh-server.nix { };
   tomcat = handleTest ./tomcat.nix {};
   tor = handleTest ./tor.nix {};
-  # traefik test relies on docker-containers
-  traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {};
+  traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {};
   trafficserver = handleTest ./trafficserver.nix {};
   transmission = handleTest ./transmission.nix {};
+  # tracee requires bpf
+  tracee = handleTestOn ["x86_64-linux"] ./tracee.nix {};
   trezord = handleTest ./trezord.nix {};
   trickster = handleTest ./trickster.nix {};
   trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {};
@@ -590,17 +787,23 @@ in {
   tuxguitar = handleTest ./tuxguitar.nix {};
   ucarp = handleTest ./ucarp.nix {};
   udisks2 = handleTest ./udisks2.nix {};
+  ulogd = handleTest ./ulogd.nix {};
   unbound = handleTest ./unbound.nix {};
   unifi = handleTest ./unifi.nix {};
   unit-php = handleTest ./web-servers/unit-php.nix {};
   upnp = handleTest ./upnp.nix {};
   uptermd = handleTest ./uptermd.nix {};
+  uptime-kuma = handleTest ./uptime-kuma.nix {};
   usbguard = handleTest ./usbguard.nix {};
   user-activation-scripts = handleTest ./user-activation-scripts.nix {};
   user-home-mode = handleTest ./user-home-mode.nix {};
   uwsgi = handleTest ./uwsgi.nix {};
   v2ray = handleTest ./v2ray.nix {};
+  varnish60 = handleTest ./varnish.nix { package = pkgs.varnish60; };
+  varnish72 = handleTest ./varnish.nix { package = pkgs.varnish72; };
+  varnish73 = handleTest ./varnish.nix { package = pkgs.varnish73; };
   vault = handleTest ./vault.nix {};
+  vault-agent = handleTest ./vault-agent.nix {};
   vault-dev = handleTest ./vault-dev.nix {};
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
@@ -611,7 +814,9 @@ in {
   virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
   vscodium = discoverTests (import ./vscodium.nix);
   vsftpd = handleTest ./vsftpd.nix {};
+  warzone2100 = handleTest ./warzone2100.nix {};
   wasabibackend = handleTest ./wasabibackend.nix {};
+  webhook = runTest ./webhook.nix;
   wiki-js = handleTest ./wiki-js.nix {};
   wine = handleTest ./wine.nix {};
   wireguard = handleTest ./wireguard {};
@@ -619,11 +824,14 @@ in {
   wmderland = handleTest ./wmderland.nix {};
   wpa_supplicant = handleTest ./wpa_supplicant.nix {};
   wordpress = handleTest ./wordpress.nix {};
+  wrappers = handleTest ./wrappers.nix {};
+  writefreely = handleTest ./web-apps/writefreely.nix {};
   xandikos = handleTest ./xandikos.nix {};
   xautolock = handleTest ./xautolock.nix {};
   xfce = handleTest ./xfce.nix {};
   xmonad = handleTest ./xmonad.nix {};
   xmonad-xdg-autostart = handleTest ./xmonad-xdg-autostart.nix {};
+  xpadneo = handleTest ./xpadneo.nix {};
   xrdp = handleTest ./xrdp.nix {};
   xss-lock = handleTest ./xss-lock.nix {};
   xterm = handleTest ./xterm.nix {};
@@ -636,6 +844,7 @@ in {
   zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
   zoneminder = handleTest ./zoneminder.nix {};
   zookeeper = handleTest ./zookeeper.nix {};
+  zram-generator = handleTest ./zram-generator.nix {};
   zrepl = handleTest ./zrepl.nix {};
   zsh-history = handleTest ./zsh-history.nix {};
 }
diff --git a/nixpkgs/nixos/tests/alps.nix b/nixpkgs/nixos/tests/alps.nix
new file mode 100644
index 000000000000..9756f2d4da15
--- /dev/null
+++ b/nixpkgs/nixos/tests/alps.nix
@@ -0,0 +1,108 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "alps";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hmenke ];
+  };
+
+  nodes = {
+    server = {
+      imports = [ ./common/user-account.nix ];
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        127.0.0.1 ${domain}
+      '';
+      networking.firewall.allowedTCPPorts = [ 25 465 993 ];
+      services.postfix = {
+        enable = true;
+        enableSubmission = true;
+        enableSubmissions = true;
+        tlsTrustedAuthorities = "${certs.ca.cert}";
+        sslCert = "${certs.${domain}.cert}";
+        sslKey = "${certs.${domain}.key}";
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        sslCACert = "${certs.ca.cert}";
+        sslServerCert = "${certs.${domain}.cert}";
+        sslServerKey = "${certs.${domain}.key}";
+      };
+    };
+
+    client = { nodes, config, ... }: {
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        ${nodes.server.config.networking.primaryIPAddress} ${domain}
+      '';
+      services.alps = {
+        enable = true;
+        theme = "alps";
+        imaps = {
+          host = domain;
+          port = 993;
+        };
+        smtps = {
+          host = domain;
+          port = 465;
+        };
+      };
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "test-alps-login" { } ''
+          from urllib.request import build_opener, HTTPCookieProcessor, Request
+          from urllib.parse import urlencode, urljoin
+          from http.cookiejar import CookieJar
+
+          baseurl = "http://localhost:${toString config.services.alps.port}"
+          username = "alice"
+          password = "${nodes.server.config.users.users.alice.password}"
+          cookiejar = CookieJar()
+          cookieprocessor = HTTPCookieProcessor(cookiejar)
+          opener = build_opener(cookieprocessor)
+
+          data = urlencode({"username": username, "password": password}).encode()
+          req = Request(urljoin(baseurl, "login"), data=data, method="POST")
+          with opener.open(req) as ret:
+              # Check that the alps_session cookie is set
+              print(cookiejar)
+              assert any(cookie.name == "alps_session" for cookie in cookiejar)
+
+          req = Request(baseurl)
+          with opener.open(req) as ret:
+              # Check that the alps_session cookie is still there...
+              print(cookiejar)
+              assert any(cookie.name == "alps_session" for cookie in cookiejar)
+              # ...and that we have not been redirected back to the login page
+              print(ret.url)
+              assert ret.url == urljoin(baseurl, "mailbox/INBOX")
+
+          req = Request(urljoin(baseurl, "logout"))
+          with opener.open(req) as ret:
+              # Check that the alps_session cookie is now gone
+              print(cookiejar)
+              assert all(cookie.name != "alps_session" for cookie in cookiejar)
+        '')
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    server.start()
+    server.wait_for_unit("postfix.service")
+    server.wait_for_unit("dovecot2.service")
+    server.wait_for_open_port(465)
+    server.wait_for_open_port(993)
+
+    client.start()
+    client.wait_for_unit("alps.service")
+    client.wait_for_open_port(${toString nodes.client.config.services.alps.port})
+    client.succeed("test-alps-login")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/apache_datasketches.nix b/nixpkgs/nixos/tests/apache_datasketches.nix
new file mode 100644
index 000000000000..2bf099ac7991
--- /dev/null
+++ b/nixpkgs/nixos/tests/apache_datasketches.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "postgis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lsix ]; # TODO: Who's the maintener now?
+  };
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.postgresql = let mypg = pkgs.postgresql_15; in {
+            enable = true;
+            package = mypg;
+            extraPlugins = with mypg.pkgs; [
+              apache_datasketches
+            ];
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    master.wait_for_unit("postgresql")
+    master.sleep(10)  # Hopefully this is long enough!!
+    master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION datasketches;'")
+    master.succeed("sudo -u postgres psql -c 'SELECT hll_sketch_to_string(hll_sketch_build(1));'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/apcupsd.nix b/nixpkgs/nixos/tests/apcupsd.nix
new file mode 100644
index 000000000000..287140f039d8
--- /dev/null
+++ b/nixpkgs/nixos/tests/apcupsd.nix
@@ -0,0 +1,41 @@
+let
+  # arbitrary address
+  ipAddr = "192.168.42.42";
+in
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "apcupsd";
+  meta.maintainers = with lib.maintainers; [ bjornfor ];
+
+  nodes = {
+    machine = {
+      services.apcupsd = {
+        enable = true;
+        configText = ''
+          UPSTYPE usb
+          BATTERYLEVEL 42
+          # Configure NISIP so that the only way apcaccess can work is to read
+          # this config.
+          NISIP ${ipAddr}
+        '';
+      };
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [{
+          address = ipAddr;
+          prefixLength = 24;
+        }];
+      };
+    };
+  };
+
+  # Check that the service starts, that the CLI (apcaccess) works and that it
+  # uses the config (ipAddr) defined in the service config.
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("apcupsd.service")
+    machine.wait_for_open_port(3551, "${ipAddr}")
+    res = machine.succeed("apcaccess")
+    expect_line="MBATTCHG : 42 Percent"
+    assert "MBATTCHG : 42 Percent" in res, f"expected apcaccess output to contain '{expect_line}' but got '{res}'"
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/apfs.nix b/nixpkgs/nixos/tests/apfs.nix
index a8841fe93046..9fe689951c78 100644
--- a/nixpkgs/nixos/tests/apfs.nix
+++ b/nixpkgs/nixos/tests/apfs.nix
@@ -21,9 +21,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     with subtest("Enable case sensitivity and normalization sensitivity"):
       machine.succeed(
           "mkapfs -s -z /dev/vdb",
-          # Triggers a bug, see https://github.com/linux-apfs/linux-apfs-rw/issues/15
-          # "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
-          "mount -o readwrite /dev/vdb /tmp/mnt",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
           "echo 'Hello World 1' > /tmp/mnt/test.txt",
           "[ ! -f /tmp/mnt/TeSt.TxT ] || false", # Test case sensitivity
           "echo 'Hello World 1' | diff - /tmp/mnt/test.txt",
@@ -36,13 +34,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     with subtest("Disable case sensitivity and normalization sensitivity"):
       machine.succeed(
           "mkapfs /dev/vdb",
-          "mount -o readwrite /dev/vdb /tmp/mnt",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
           "echo 'bla bla bla' > /tmp/mnt/Test.txt",
           "echo -n 'Hello World' > /tmp/mnt/test.txt",
           "echo ' 1' >> /tmp/mnt/TEST.TXT",
           "umount /tmp/mnt",
           "apfsck /dev/vdb",
-          "mount -o readwrite /dev/vdb /tmp/mnt",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
           "echo 'Hello World 1' | diff - /tmp/mnt/TeSt.TxT", # Test case insensitivity
           "echo 'Hello World 2' > /tmp/mnt/\u0061\u0301.txt",
           "echo 'Hello World 2' | diff - /tmp/mnt/\u0061\u0301.txt",
@@ -50,5 +48,18 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           "umount /tmp/mnt",
           "apfsck /dev/vdb",
       )
+    with subtest("Snapshots"):
+      machine.succeed(
+          "mkapfs /dev/vdb",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
+          "echo 'Hello World' > /tmp/mnt/test.txt",
+          "apfs-snap /tmp/mnt snap-1",
+          "rm /tmp/mnt/test.txt",
+          "umount /tmp/mnt",
+          "mount -o cknodes,readwrite,snap=snap-1 /dev/vdb /tmp/mnt",
+          "echo 'Hello World' | diff - /tmp/mnt/test.txt",
+          "umount /tmp/mnt",
+          "apfsck /dev/vdb",
+      )
   '';
 })
diff --git a/nixpkgs/nixos/tests/apparmor.nix b/nixpkgs/nixos/tests/apparmor.nix
index f85bff0295e7..be91e9632849 100644
--- a/nixpkgs/nixos/tests/apparmor.nix
+++ b/nixpkgs/nixos/tests/apparmor.nix
@@ -1,14 +1,11 @@
-import ./make-test-python.nix ({ pkgs, ... } : {
+import ./make-test-python.nix ({ pkgs, lib, ... } : {
   name = "apparmor";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ julm ];
-  };
+  meta.maintainers = with lib.maintainers; [ julm ];
 
   nodes.machine =
     { lib, pkgs, config, ... }:
-    with lib;
     {
-      security.apparmor.enable = mkDefault true;
+      security.apparmor.enable = lib.mkDefault true;
     };
 
   testScript =
@@ -30,7 +27,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
       # 4. Using `diff` against the expected output.
       with subtest("apparmorRulesFromClosure"):
           machine.succeed(
-              "${pkgs.diffutils}/bin/diff ${pkgs.writeText "expected.rules" ''
+              "${pkgs.diffutils}/bin/diff -u ${pkgs.writeText "expected.rules" ''
                   mr ${pkgs.bash}/lib/**.so*,
                   r ${pkgs.bash},
                   r ${pkgs.bash}/etc/**,
@@ -67,6 +64,12 @@ import ./make-test-python.nix ({ pkgs, ... } : {
                   r ${pkgs.libunistring}/lib/**,
                   r ${pkgs.libunistring}/share/**,
                   x ${pkgs.libunistring}/foo/**,
+                  mr ${pkgs.glibc.libgcc}/lib/**.so*,
+                  r ${pkgs.glibc.libgcc},
+                  r ${pkgs.glibc.libgcc}/etc/**,
+                  r ${pkgs.glibc.libgcc}/lib/**,
+                  r ${pkgs.glibc.libgcc}/share/**,
+                  x ${pkgs.glibc.libgcc}/foo/**,
               ''} ${pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
                   ${pkgs.gnused}/bin/sed -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
                       pkgs.apparmorRulesFromClosure {
diff --git a/nixpkgs/nixos/tests/atop.nix b/nixpkgs/nixos/tests/atop.nix
index ec10369a24fd..f9335eecc20e 100644
--- a/nixpkgs/nixos/tests/atop.nix
+++ b/nixpkgs/nixos/tests/atop.nix
@@ -199,7 +199,7 @@ in
     ];
   };
   everything = makeTest {
-    name = "atop-everthing";
+    name = "atop-everything";
     nodes.machine = {
       programs.atop = {
         enable = true;
diff --git a/nixpkgs/nixos/tests/atuin.nix b/nixpkgs/nixos/tests/atuin.nix
new file mode 100644
index 000000000000..9f23a3cf6713
--- /dev/null
+++ b/nixpkgs/nixos/tests/atuin.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  testPort = 8888;
+  testUser = "testerman";
+  testPass = "password";
+  testEmail = "test.testerman@test.com";
+in
+{
+  name = "atuin";
+  meta.maintainers = with lib.maintainers; [ devusb ];
+
+  nodes = {
+    server =
+      { ... }:
+      {
+        services.atuin = {
+          enable = true;
+          port = testPort;
+          host = "0.0.0.0";
+          openFirewall = true;
+          openRegistration = true;
+        };
+      };
+
+    client =
+      { ... }:
+      { };
+
+  };
+
+  testScript = with pkgs; ''
+    start_all()
+
+    # wait for atuin server startup
+    server.wait_for_unit("atuin.service")
+    server.wait_for_open_port(${toString testPort})
+
+    # configure atuin client on server node
+    server.execute("mkdir -p ~/.config/atuin")
+    server.execute("echo 'sync_address = \"http://localhost:${toString testPort}\"' > ~/.config/atuin/config.toml")
+
+    # register with atuin server on server node
+    server.succeed("${atuin}/bin/atuin register -u ${testUser} -p ${testPass} -e ${testEmail}")
+    _, key = server.execute("${atuin}/bin/atuin key")
+
+    # store test record in atuin server and sync
+    server.succeed("ATUIN_SESSION=$(${atuin}/bin/atuin uuid) ${atuin}/bin/atuin history start 'shazbot'")
+    server.succeed("${atuin}/bin/atuin sync")
+
+    # configure atuin client on client node
+    client.execute("mkdir -p ~/.config/atuin")
+    client.execute("echo 'sync_address = \"http://server:${toString testPort}\"' > ~/.config/atuin/config.toml")
+
+    # log in to atuin server on client node
+    client.succeed(f"${atuin}/bin/atuin login -u ${testUser} -p ${testPass} -k \"{key}\"")
+
+    # pull records from atuin server
+    client.succeed("${atuin}/bin/atuin sync -f")
+
+    # check for test record
+    client.succeed("ATUIN_SESSION=$(${atuin}/bin/atuin uuid) ${atuin}/bin/atuin history list | grep shazbot")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/authelia.nix b/nixpkgs/nixos/tests/authelia.nix
new file mode 100644
index 000000000000..679c65fea087
--- /dev/null
+++ b/nixpkgs/nixos/tests/authelia.nix
@@ -0,0 +1,169 @@
+# Test Authelia as an auth server for Traefik as a reverse proxy of a local web service
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "authelia";
+  meta.maintainers = with lib.maintainers; [ jk ];
+
+  nodes = {
+    authelia = { config, pkgs, lib, ... }: {
+      services.authelia.instances.testing = {
+        enable = true;
+        secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile";
+        secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile";
+        settings = {
+          authentication_backend.file.path = "/etc/authelia/users_database.yml";
+          access_control.default_policy = "one_factor";
+          session.domain = "example.com";
+          storage.local.path = "/tmp/db.sqlite3";
+          notifier.filesystem.filename = "/tmp/notifications.txt";
+        };
+      };
+
+      # These should not be set from nix but through other means to not leak the secret!
+      # This is purely for testing purposes!
+      environment.etc."authelia/storageEncryptionKeyFile" = {
+        mode = "0400";
+        user = "authelia-testing";
+        text = "you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this";
+      };
+      environment.etc."authelia/jwtSecretFile" = {
+        mode = "0400";
+        user = "authelia-testing";
+        text = "a_very_important_secret";
+      };
+      environment.etc."authelia/users_database.yml" = {
+        mode = "0400";
+        user = "authelia-testing";
+        text = ''
+          users:
+            bob:
+              disabled: false
+              displayname: bob
+              # password of password
+              password: $argon2id$v=19$m=65536,t=3,p=4$2ohUAfh9yetl+utr4tLcCQ$AsXx0VlwjvNnCsa70u4HKZvFkC8Gwajr2pHGKcND/xs
+              email: bob@jim.com
+              groups:
+                - admin
+                - dev
+        '';
+      };
+
+      services.traefik = {
+        enable = true;
+
+        dynamicConfigOptions = {
+          tls.certificates =
+            let
+              certDir = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+                openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=auth.example.com/CN=static.example.com' -days 36500
+                mkdir -p $out
+                cp key.pem cert.pem $out
+              '';
+            in
+            [{
+              certFile = "${certDir}/cert.pem";
+              keyFile = "${certDir}/key.pem";
+            }];
+          http.middlewares.authelia.forwardAuth = {
+            address = "http://localhost:9091/api/verify?rd=https%3A%2F%2Fauth.example.com%2F";
+            trustForwardHeader = true;
+            authResponseHeaders = [
+              "Remote-User"
+              "Remote-Groups"
+              "Remote-Email"
+              "Remote-Name"
+            ];
+          };
+          http.middlewares.authelia-basic.forwardAuth = {
+            address = "http://localhost:9091/api/verify?auth=basic";
+            trustForwardHeader = true;
+            authResponseHeaders = [
+              "Remote-User"
+              "Remote-Groups"
+              "Remote-Email"
+              "Remote-Name"
+            ];
+          };
+
+          http.routers.simplehttp = {
+            rule = "Host(`static.example.com`)";
+            tls = true;
+            entryPoints = "web";
+            service = "simplehttp";
+          };
+          http.routers.simplehttp-basic-auth = {
+            rule = "Host(`static-basic-auth.example.com`)";
+            tls = true;
+            entryPoints = "web";
+            service = "simplehttp";
+            middlewares = [ "authelia-basic@file" ];
+          };
+
+          http.services.simplehttp = {
+            loadBalancer.servers = [{
+              url = "http://localhost:8000";
+            }];
+          };
+
+          http.routers.authelia = {
+            rule = "Host(`auth.example.com`)";
+            tls = true;
+            entryPoints = "web";
+            service = "authelia@file";
+          };
+
+          http.services.authelia = {
+            loadBalancer.servers = [{
+              url = "http://localhost:9091";
+            }];
+          };
+        };
+
+        staticConfigOptions = {
+          global = {
+            checkNewVersion = false;
+            sendAnonymousUsage = false;
+          };
+
+          entryPoints.web.address = ":443";
+        };
+      };
+
+      systemd.services.simplehttp =
+        let fakeWebPageDir = pkgs.writeTextDir "index.html" "hello"; in
+        {
+          script = "${pkgs.python3}/bin/python -m http.server --directory ${fakeWebPageDir} 8000";
+          serviceConfig.Type = "simple";
+          wantedBy = [ "multi-user.target" ];
+        };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    authelia.wait_for_unit("simplehttp.service")
+    authelia.wait_for_unit("traefik.service")
+    authelia.wait_for_unit("authelia-testing.service")
+    authelia.wait_for_open_port(443)
+    authelia.wait_for_unit("multi-user.target")
+
+    with subtest("Check for authelia"):
+      # expect the login page
+      assert "Login - Authelia", "could not reach authelia" in \
+        authelia.succeed("curl --insecure -sSf -H Host:auth.example.com https://authelia:443/")
+
+    with subtest("Check contacting basic http server via traefik with https works"):
+      assert "hello", "could not reach raw static site" in \
+        authelia.succeed("curl --insecure -sSf -H Host:static.example.com https://authelia:443/")
+
+    with subtest("Test traefik and authelia"):
+      with subtest("No details fail"):
+        authelia.fail("curl --insecure -sSf -H Host:static-basic-auth.example.com https://authelia:443/")
+      with subtest("Incorrect details fail"):
+        authelia.fail("curl --insecure -sSf -u 'bob:wordpass' -H Host:static-basic-auth.example.com https://authelia:443/")
+        authelia.fail("curl --insecure -sSf -u 'alice:password' -H Host:static-basic-auth.example.com https://authelia:443/")
+      with subtest("Correct details pass"):
+        assert "hello", "could not reach authed static site with valid credentials" in \
+          authelia.succeed("curl --insecure -sSf -u 'bob:password' -H Host:static-basic-auth.example.com https://authelia:443/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bazarr.nix b/nixpkgs/nixos/tests/bazarr.nix
index efcd9de01080..aa0550e243ae 100644
--- a/nixpkgs/nixos/tests/bazarr.nix
+++ b/nixpkgs/nixos/tests/bazarr.nix
@@ -1,13 +1,11 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 let
   port = 42069;
 in
 {
   name = "bazarr";
-  meta.maintainers = with maintainers; [ d-xo ];
+  meta.maintainers = with lib.maintainers; [ d-xo ];
 
   nodes.machine =
     { pkgs, ... }:
@@ -20,7 +18,7 @@ in
 
   testScript = ''
     machine.wait_for_unit("bazarr.service")
-    machine.wait_for_open_port(port)
+    machine.wait_for_open_port(${toString port})
     machine.succeed("curl --fail http://localhost:${toString port}/")
   '';
 })
diff --git a/nixpkgs/nixos/tests/binary-cache.nix b/nixpkgs/nixos/tests/binary-cache.nix
new file mode 100644
index 000000000000..0809e59e5a11
--- /dev/null
+++ b/nixpkgs/nixos/tests/binary-cache.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "binary-cache";
+  meta.maintainers = with maintainers; [ thomasjm ];
+
+  nodes.machine =
+    { pkgs, ... }: {
+      imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      environment.systemPackages = with pkgs; [python3];
+      system.extraDependencies = with pkgs; [hello.inputDerivation];
+      nix.extraOptions = ''
+        experimental-features = nix-command
+      '';
+    };
+
+  testScript = ''
+    # Build the cache, then remove it from the store
+    cachePath = machine.succeed("nix-build --no-out-link -E 'with import <nixpkgs> {}; mkBinaryCache { rootPaths = [hello]; }'").strip()
+    machine.succeed("cp -r %s/. /tmp/cache" % cachePath)
+    machine.succeed("nix-store --delete " + cachePath)
+
+    # Sanity test of cache structure
+    status, stdout = machine.execute("ls /tmp/cache")
+    cache_files = stdout.split()
+    assert ("nix-cache-info" in cache_files)
+    assert ("nar" in cache_files)
+
+    # Nix store ping should work
+    machine.succeed("nix store ping --store file:///tmp/cache")
+
+    # Cache should contain a .narinfo referring to "hello"
+    grepLogs = machine.succeed("grep -l 'StorePath: /nix/store/[[:alnum:]]*-hello-.*' /tmp/cache/*.narinfo")
+
+    # Get the store path referenced by the .narinfo
+    narInfoFile = grepLogs.strip()
+    narInfoContents = machine.succeed("cat " + narInfoFile)
+    import re
+    match = re.match(r"^StorePath: (/nix/store/[a-z0-9]*-hello-.*)$", narInfoContents, re.MULTILINE)
+    if not match: raise Exception("Couldn't find hello store path in cache")
+    storePath = match[1]
+
+    # Delete the store path
+    machine.succeed("nix-store --delete " + storePath)
+    machine.succeed("[ ! -d %s ] || exit 1" % storePath)
+
+    # Should be able to build hello using the cache
+    logs = machine.succeed("nix-build -A hello '<nixpkgs>' --option require-sigs false --option trusted-substituters file:///tmp/cache --option substituters file:///tmp/cache 2>&1")
+    logLines = logs.split("\n")
+    if not "this path will be fetched" in logLines[0]: raise Exception("Unexpected first log line")
+    def shouldBe(got, desired):
+      if got != desired: raise Exception("Expected '%s' but got '%s'" % (desired, got))
+    shouldBe(logLines[1], "  " + storePath)
+    shouldBe(logLines[2], "copying path '%s' from 'file:///tmp/cache'..." % storePath)
+    shouldBe(logLines[3], storePath)
+
+    # Store path should exist in the store now
+    machine.succeed("[ -d %s ] || exit 1" % storePath)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/birdwatcher.nix b/nixpkgs/nixos/tests/birdwatcher.nix
new file mode 100644
index 000000000000..5c41b4d0e4f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/birdwatcher.nix
@@ -0,0 +1,94 @@
+# This test does a basic functionality check for birdwatcher
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+in
+makeTest {
+  name = "birdwatcher";
+  nodes = {
+    host1 = {
+      environment.systemPackages = with pkgs; [ jq ];
+      services.bird2 = {
+        enable = true;
+        config = ''
+          log syslog all;
+
+          debug protocols all;
+
+          router id 10.0.0.1;
+
+          protocol device {
+          }
+
+          protocol kernel kernel4 {
+            ipv4 {
+              import none;
+              export all;
+            };
+          }
+
+          protocol kernel kernel6 {
+            ipv6 {
+              import none;
+              export all;
+            };
+          }
+        '';
+      };
+      services.birdwatcher = {
+        enable = true;
+        settings = ''
+          [server]
+          allow_from = []
+          allow_uncached = false
+          modules_enabled = ["status",
+                             "protocols",
+                             "protocols_bgp",
+                             "protocols_short",
+                             "routes_protocol",
+                             "routes_peer",
+                             "routes_table",
+                             "routes_table_filtered",
+                             "routes_table_peer",
+                             "routes_filtered",
+                             "routes_prefixed",
+                             "routes_noexport",
+                             "routes_pipe_filtered_count",
+                             "routes_pipe_filtered"
+                            ]
+          [status]
+          reconfig_timestamp_source = "bird"
+          reconfig_timestamp_match = "# created: (.*)"
+          filter_fields = []
+          [bird]
+          listen = "0.0.0.0:29184"
+          config = "/etc/bird/bird2.conf"
+          birdc  = "${pkgs.bird}/bin/birdc"
+          ttl = 5 # time to live (in minutes) for caching of cli output
+          [parser]
+          filter_fields = []
+          [cache]
+          use_redis = false # if not using redis cache, activate housekeeping to save memory!
+          [housekeeping]
+          interval = 5
+          force_release_memory = true
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("bird2.service")
+    host1.wait_for_unit("birdwatcher.service")
+    host1.wait_for_open_port(29184)
+    host1.succeed("curl http://[::]:29184/status | jq -r .status.message | grep 'Daemon is up and running'")
+    host1.succeed("curl http://[::]:29184/protocols | jq -r .protocols.device1.state | grep 'up'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/boot-stage1.nix b/nixpkgs/nixos/tests/boot-stage1.nix
index fbe82d61afae..f07802b8c31e 100644
--- a/nixpkgs/nixos/tests/boot-stage1.nix
+++ b/nixpkgs/nixos/tests/boot-stage1.nix
@@ -107,8 +107,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         '';
       };
 
-      copyCanaries = with lib; concatMapStrings (canary: ''
-        ${optionalString (canary ? child) ''
+      copyCanaries = lib.concatMapStrings (canary: ''
+        ${lib.optionalString (canary ? child) ''
           copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
         ''}
         copy_bin_and_libs "${canary}/bin/${canary.name}"
@@ -132,7 +132,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         '';
       })
 
-      # This canary process mimicks a storage daemon, which we do NOT want to be
+      # This canary process mimics a storage daemon, which we do NOT want to be
       # killed before going into stage 2. For more on root storage daemons, see:
       # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
       (mkCmdlineCanary {
diff --git a/nixpkgs/nixos/tests/bootspec.nix b/nixpkgs/nixos/tests/bootspec.nix
new file mode 100644
index 000000000000..9295500422a9
--- /dev/null
+++ b/nixpkgs/nixos/tests/bootspec.nix
@@ -0,0 +1,172 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  baseline = {
+    virtualisation.useBootLoader = true;
+  };
+  grub = {
+    boot.loader.grub.enable = true;
+  };
+  systemd-boot = {
+    boot.loader.systemd-boot.enable = true;
+  };
+  uefi = {
+    virtualisation.useEFIBoot = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+    boot.loader.grub.efiSupport = true;
+    environment.systemPackages = [ pkgs.efibootmgr ];
+  };
+  standard = {
+    boot.bootspec.enable = true;
+
+    imports = [
+      baseline
+      systemd-boot
+      uefi
+    ];
+  };
+in
+{
+  basic = makeTest {
+    name = "systemd-boot-with-bootspec";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = standard;
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+    '';
+  };
+
+  grub = makeTest {
+    name = "grub-with-bootspec";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      boot.bootspec.enable = true;
+
+      imports = [
+        baseline
+        grub
+        uefi
+      ];
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+    '';
+  };
+
+  legacy-boot = makeTest {
+    name = "legacy-boot-with-bootspec";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      boot.bootspec.enable = true;
+
+      imports = [
+        baseline
+        grub
+      ];
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+    '';
+  };
+
+  # Check that initrd create corresponding entries in bootspec.
+  initrd = makeTest {
+    name = "bootspec-with-initrd";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      # It's probably the case, but we want to make it explicit here.
+      boot.initrd.enable = true;
+    };
+
+    testScript = ''
+      import json
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+
+      bootspec = json.loads(machine.succeed("jq -r '.\"org.nixos.bootspec.v1\"' /run/current-system/boot.json"))
+
+      assert all(key in bootspec for key in ('initrd', 'initrdSecrets')), "Bootspec should contain initrd or initrdSecrets field when initrd is enabled"
+    '';
+  };
+
+  # Check that specialisations create corresponding entries in bootspec.
+  specialisation = makeTest {
+    name = "bootspec-with-specialisation";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      specialisation.something.configuration = {};
+    };
+
+    testScript = ''
+      import json
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+      machine.succeed("test -e /run/current-system/specialisation/something/boot.json")
+
+      sp_in_parent = json.loads(machine.succeed("jq -r '.\"org.nixos.specialisation.v1\".something' /run/current-system/boot.json"))
+      sp_in_fs = json.loads(machine.succeed("cat /run/current-system/specialisation/something/boot.json"))
+
+      assert sp_in_parent['org.nixos.bootspec.v1'] == sp_in_fs['org.nixos.bootspec.v1'], "Bootspecs of the same specialisation are different!"
+    '';
+  };
+
+  # Check that extensions are propagated.
+  extensions = makeTest {
+    name = "bootspec-with-extensions";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = { config, ... }: {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      boot.bootspec.extensions = {
+        "org.nix-tests.product" = {
+          osRelease = config.environment.etc."os-release".source;
+        };
+      };
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      current_os_release = machine.succeed("cat /etc/os-release")
+      bootspec_os_release = machine.succeed("cat $(jq -r '.\"org.nix-tests.product\".osRelease' /run/current-system/boot.json)")
+
+      assert current_os_release == bootspec_os_release, "Filename referenced by extension has unexpected contents"
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/tests/borgbackup.nix b/nixpkgs/nixos/tests/borgbackup.nix
index d3cd6c66bfeb..4160e727f047 100644
--- a/nixpkgs/nixos/tests/borgbackup.nix
+++ b/nixpkgs/nixos/tests/borgbackup.nix
@@ -99,14 +99,28 @@ in {
           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
         };
 
+        sleepInhibited = {
+          inhibitsSleep = true;
+          # Blocks indefinitely while "backing up" so that we can try to suspend the local system while it's hung
+          dumpCommand = pkgs.writeScript "sleepInhibited" ''
+            cat /dev/zero
+          '';
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
       };
     };
 
     server = { ... }: {
       services.openssh = {
         enable = true;
-        passwordAuthentication = false;
-        kbdInteractiveAuthentication = false;
+        settings = {
+          PasswordAuthentication = false;
+          KbdInteractiveAuthentication = false;
+        };
       };
 
       services.borgbackup.repos.repo1 = {
@@ -204,5 +218,13 @@ in {
         client.wait_for_unit("network.target")
         client.systemctl("start --wait borgbackup-job-commandFail")
         client.succeed("systemctl is-failed borgbackup-job-commandFail")
+
+    with subtest("sleepInhibited"):
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.fail("systemd-inhibit --list | grep -q borgbackup")
+        client.systemctl("start borgbackup-job-sleepInhibited")
+        client.wait_until_succeeds("systemd-inhibit --list | grep -q borgbackup")
+        client.systemctl("stop borgbackup-job-sleepInhibited")
   '';
 })
diff --git a/nixpkgs/nixos/tests/bpf.nix b/nixpkgs/nixos/tests/bpf.nix
index 5868e3bfcb4c..150ed0958862 100644
--- a/nixpkgs/nixos/tests/bpf.nix
+++ b/nixpkgs/nixos/tests/bpf.nix
@@ -25,5 +25,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     print(machine.succeed("bpftrace -e 'kprobe:schedule { "
         "    printf(\"tgid: %d\", ((struct task_struct*) curtask)->tgid); exit() "
         "}'"))
+    # module BTF (bpftrace >= 0.17)
+    # test is currently disabled on aarch64 as kfunc does not work there yet
+    # https://github.com/iovisor/bpftrace/issues/2496
+    print(machine.succeed("uname -m | grep aarch64 || "
+        "bpftrace -e 'kfunc:nft_trans_alloc_gfp { "
+        "    printf(\"portid: %d\\n\", args->ctx->portid); "
+        "} BEGIN { exit() }'"))
   '';
 })
diff --git a/nixpkgs/nixos/tests/btrbk-doas.nix b/nixpkgs/nixos/tests/btrbk-doas.nix
new file mode 100644
index 000000000000..1e3f8d56addb
--- /dev/null
+++ b/nixpkgs/nixos/tests/btrbk-doas.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let
+    privateKey = ''
+      -----BEGIN OPENSSH PRIVATE KEY-----
+      b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+      QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+      RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+      AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+      9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+      -----END OPENSSH PRIVATE KEY-----
+    '';
+    publicKey = ''
+      ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
+    '';
+  in
+  {
+    name = "btrbk-doas";
+    meta = with pkgs.lib; {
+      maintainers = with maintainers; [ symphorien tu-maurice ];
+    };
+
+    nodes = {
+      archive = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        # note: this makes the privateKey world readable.
+        # don't do it with real ssh keys.
+        environment.etc."btrbk_key".text = privateKey;
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          instances = {
+            remote = {
+              onCalendar = "minutely";
+              settings = {
+                ssh_identity = "/etc/btrbk_key";
+                ssh_user = "btrbk";
+                stream_compress = "lz4";
+                volume = {
+                  "ssh://main/mnt" = {
+                    target = "/mnt";
+                    snapshot_dir = "btrbk/remote";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+
+      main = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        services.openssh = {
+          enable = true;
+          passwordAuthentication = false;
+          kbdInteractiveAuthentication = false;
+        };
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          sshAccess = [
+            {
+              key = publicKey;
+              roles = [ "source" "send" "info" "delete" ];
+            }
+          ];
+          instances = {
+            local = {
+              onCalendar = "minutely";
+              settings = {
+                volume = {
+                  "/mnt" = {
+                    snapshot_dir = "btrbk/local";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # create btrfs partition at /mnt
+      for machine in (archive, main):
+        machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
+        machine.succeed("mkfs.btrfs /data_fs")
+        machine.succeed("mkdir /mnt")
+        machine.succeed("mount /data_fs /mnt")
+
+      # what to backup and where
+      main.succeed("btrfs subvolume create /mnt/to_backup")
+      main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
+
+      # check that local snapshots work
+      with subtest("local"):
+          main.succeed("echo foo > /mnt/to_backup/bar")
+          main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+          main.succeed("echo bar > /mnt/to_backup/bar")
+          main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
+
+      # check that btrfs send/receive works and ssh access works
+      with subtest("remote"):
+          archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
+          main.succeed("echo baz > /mnt/to_backup/bar")
+          archive.succeed("cat /mnt/*/bar | grep bar")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/btrbk-section-order.nix b/nixpkgs/nixos/tests/btrbk-section-order.nix
new file mode 100644
index 000000000000..20f1afcf80ec
--- /dev/null
+++ b/nixpkgs/nixos/tests/btrbk-section-order.nix
@@ -0,0 +1,51 @@
+# This tests validates the order of generated sections that may contain
+# other sections.
+# When a `volume` section has both `subvolume` and `target` children,
+# `target` must go before `subvolume`. Otherwise, `target` will become
+# a child of the last `subvolume` instead of `volume`, due to the
+# order-sensitive config format.
+#
+# Issue: https://github.com/NixOS/nixpkgs/issues/195660
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "btrbk-section-order";
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = { ... }: {
+    services.btrbk.instances.local = {
+      onCalendar = null;
+      settings = {
+        timestamp_format = "long";
+        target."ssh://global-target/".ssh_user = "root";
+        volume."/btrfs" = {
+          snapshot_dir = "/volume-snapshots";
+          target."ssh://volume-target/".ssh_user = "root";
+          subvolume."@subvolume" = {
+            snapshot_dir = "/subvolume-snapshots";
+            target."ssh://subvolume-target/".ssh_user = "root";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("basic.target")
+    got = machine.succeed("cat /etc/btrbk/local.conf")
+    expect = """
+    backend btrfs-progs-sudo
+    timestamp_format long
+    target ssh://global-target/
+     ssh_user root
+    volume /btrfs
+     snapshot_dir /volume-snapshots
+     target ssh://volume-target/
+      ssh_user root
+     subvolume @subvolume
+      snapshot_dir /subvolume-snapshots
+      target ssh://subvolume-target/
+       ssh_user root
+    """.strip()
+    print(got)
+    assert got == expect
+  '';
+})
diff --git a/nixpkgs/nixos/tests/btrbk.nix b/nixpkgs/nixos/tests/btrbk.nix
index 9f34f7dfbe38..5261321dfa2c 100644
--- a/nixpkgs/nixos/tests/btrbk.nix
+++ b/nixpkgs/nixos/tests/btrbk.nix
@@ -52,8 +52,10 @@ import ./make-test-python.nix ({ pkgs, ... }:
         environment.systemPackages = with pkgs; [ btrfs-progs ];
         services.openssh = {
           enable = true;
-          passwordAuthentication = false;
-          kbdInteractiveAuthentication = false;
+          settings = {
+            KbdInteractiveAuthentication = false;
+            PasswordAuthentication = false;
+          };
         };
         services.btrbk = {
           extraPackages = [ pkgs.lz4 ];
diff --git a/nixpkgs/nixos/tests/budgie.nix b/nixpkgs/nixos/tests/budgie.nix
new file mode 100644
index 000000000000..b1b0e618c884
--- /dev/null
+++ b/nixpkgs/nixos/tests/budgie.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "budgie";
+
+  meta.maintainers = [ lib.maintainers.federicoschonborn ];
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.budgie = {
+      enable = true;
+      extraPlugins = [
+        pkgs.budgiePlugins.budgie-analogue-clock-applet
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+    ''
+      with subtest("Wait for login"):
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if Budgie session components actually start"):
+          machine.wait_until_succeeds("pgrep budgie-daemon")
+          machine.wait_for_window("budgie-daemon")
+          machine.wait_until_succeeds("pgrep budgie-panel")
+          machine.wait_for_window("budgie-panel")
+
+      with subtest("Open MATE terminal"):
+          machine.succeed("su - ${user.name} -c 'DISPLAY=:0 mate-terminal >&2 &'")
+          machine.wait_for_window("Terminal")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/buildbot.nix b/nixpkgs/nixos/tests/buildbot.nix
index 977c728835f0..467c8d8baff4 100644
--- a/nixpkgs/nixos/tests/buildbot.nix
+++ b/nixpkgs/nixos/tests/buildbot.nix
@@ -23,7 +23,7 @@ import ./make-test-python.nix {
         ];
       };
       networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
-      environment.systemPackages = with pkgs; [ git python3Packages.buildbot-full ];
+      environment.systemPackages = with pkgs; [ git buildbot-full ];
     };
 
     bbworker = { pkgs, ... }: {
@@ -31,7 +31,7 @@ import ./make-test-python.nix {
         enable = true;
         masterUrl = "bbmaster:9989";
       };
-      environment.systemPackages = with pkgs; [ git python3Packages.buildbot-worker ];
+      environment.systemPackages = with pkgs; [ git buildbot-worker ];
     };
 
     gitrepo = { pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/cadvisor.nix b/nixpkgs/nixos/tests/cadvisor.nix
index c372dea301d2..70e068fcf21c 100644
--- a/nixpkgs/nixos/tests/cadvisor.nix
+++ b/nixpkgs/nixos/tests/cadvisor.nix
@@ -1,15 +1,13 @@
-import ./make-test-python.nix ({ pkgs, ... } : {
+import ./make-test-python.nix ({ lib, pkgs, ... } : {
   name = "cadvisor";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ offline ];
-  };
+  meta.maintainers = with lib.maintainers; [ offline ];
 
   nodes = {
     machine = { ... }: {
       services.cadvisor.enable = true;
     };
 
-    influxdb = { lib, ... }: with lib; {
+    influxdb = { lib, ... }: {
       services.cadvisor.enable = true;
       services.cadvisor.storageDriver = "influxdb";
       services.influxdb.enable = true;
diff --git a/nixpkgs/nixos/tests/cage.nix b/nixpkgs/nixos/tests/cage.nix
index 39c8d0441b6d..db1b7854673d 100644
--- a/nixpkgs/nixos/tests/cage.nix
+++ b/nixpkgs/nixos/tests/cage.nix
@@ -10,11 +10,13 @@ import ./make-test-python.nix ({ pkgs, ...} :
 
   {
     imports = [ ./common/user-account.nix ];
+
+    fonts.fonts = with pkgs; [ dejavu_fonts ];
+
     services.cage = {
       enable = true;
       user = "alice";
-      # Disable color and bold and use a larger font to make OCR easier:
-      program = "${pkgs.xterm}/bin/xterm -cm -pc -fa Monospace -fs 24";
+      program = "${pkgs.xterm}/bin/xterm";
     };
 
     # Need to switch to a different GPU driver than the default one (-vga std) so that Cage can launch:
diff --git a/nixpkgs/nixos/tests/cagebreak.nix b/nixpkgs/nixos/tests/cagebreak.nix
index 1dcc910f9744..1fef7cb57cfc 100644
--- a/nixpkgs/nixos/tests/cagebreak.nix
+++ b/nixpkgs/nixos/tests/cagebreak.nix
@@ -33,6 +33,7 @@ in
 
     hardware.opengl.enable = true;
     programs.xwayland.enable = true;
+    security.polkit.enable = true;
     environment.systemPackages = [ pkgs.cagebreak pkgs.wayland-utils ];
 
     # Need to switch to a different GPU driver than the default one (-vga std) so that Cagebreak can launch:
diff --git a/nixpkgs/nixos/tests/calibre-web.nix b/nixpkgs/nixos/tests/calibre-web.nix
index 9832d5469787..aea9bca3ebe9 100644
--- a/nixpkgs/nixos/tests/calibre-web.nix
+++ b/nixpkgs/nixos/tests/calibre-web.nix
@@ -5,10 +5,9 @@ import ./make-test-python.nix (
       port = 3142;
       defaultPort = 8083;
     in
-      with lib;
       {
         name = "calibre-web";
-        meta.maintainers = with pkgs.lib.maintainers; [ pborzenkov ];
+        meta.maintainers = with lib.maintainers; [ pborzenkov ];
 
         nodes = {
           customized = { pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/ceph-single-node.nix b/nixpkgs/nixos/tests/ceph-single-node.nix
index 4fe5dc59ff8f..4a5636fac156 100644
--- a/nixpkgs/nixos/tests/ceph-single-node.nix
+++ b/nixpkgs/nixos/tests/ceph-single-node.nix
@@ -181,6 +181,17 @@ let
     monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
     monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+
+    # Enable the dashboard and recheck health
+    monA.succeed(
+        "ceph mgr module enable dashboard",
+        "ceph config set mgr mgr/dashboard/ssl false",
+        # default is 8080 but it's better to be explicit
+        "ceph config set mgr mgr/dashboard/server_port 8080",
+    )
+    monA.wait_for_open_port(8080)
+    monA.wait_until_succeeds("curl -q --fail http://localhost:8080")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
   '';
 in {
   name = "basic-single-node-ceph-cluster";
diff --git a/nixpkgs/nixos/tests/cgit.nix b/nixpkgs/nixos/tests/cgit.nix
new file mode 100644
index 000000000000..6aed06adefdf
--- /dev/null
+++ b/nixpkgs/nixos/tests/cgit.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  robotsTxt = pkgs.writeText "cgit-robots.txt" ''
+    User-agent: *
+    Disallow: /
+  '';
+in {
+  name = "cgit";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ schnusch ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.cgit."localhost" = {
+        enable = true;
+        package = pkgs.cgit.overrideAttrs ({ postInstall, ... }: {
+          postInstall = ''
+            ${postInstall}
+            cp ${robotsTxt} "$out/cgit/robots.txt"
+          '';
+        });
+        nginx.location = "/(c)git/";
+        repos = {
+          some-repo = {
+            path = "/srv/git/some-repo";
+            desc = "some-repo description";
+          };
+        };
+      };
+
+      environment.systemPackages = [ pkgs.git ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("network.target")
+    server.wait_for_open_port(80)
+
+    server.succeed("curl -fsS http://localhost/%28c%29git/cgit.css")
+
+    server.succeed("curl -fsS http://localhost/%28c%29git/robots.txt | diff -u - ${robotsTxt}")
+
+    server.succeed(
+        "curl -fsS http://localhost/%28c%29git/ | grep -F 'some-repo description'"
+    )
+
+    server.fail("curl -fsS http://localhost/robots.txt")
+
+    server.succeed("${pkgs.writeShellScript "setup-cgit-test-repo" ''
+      set -e
+      git init --bare -b master /srv/git/some-repo
+      git init -b master reference
+      cd reference
+      git remote add origin /srv/git/some-repo
+      date > date.txt
+      git add date.txt
+      git -c user.name=test -c user.email=test@localhost commit -m 'add date'
+      git push -u origin master
+    ''}")
+
+    server.succeed(
+        "curl -fsS 'http://localhost/%28c%29git/some-repo/plain/date.txt?id=master' | diff -u reference/date.txt -"
+    )
+
+    server.succeed(
+       "git clone http://localhost/%28c%29git/some-repo && diff -u reference/date.txt some-repo/date.txt"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/chromium.nix b/nixpkgs/nixos/tests/chromium.nix
index 6b296fe8a61a..cdfdcc9bcdd2 100644
--- a/nixpkgs/nixos/tests/chromium.nix
+++ b/nixpkgs/nixos/tests/chromium.nix
@@ -39,7 +39,9 @@ mapAttrs (channel: chromiumPkg: makeTest {
   name = "chromium-${channel}";
   meta = {
     maintainers = with maintainers; [ aszlig primeos ];
+  } // optionalAttrs (chromiumPkg.meta ? timeout) {
     # https://github.com/NixOS/hydra/issues/591#issuecomment-435125621
+    # Note: optionalAttrs is used since meta.timeout is not set for Google Chrome
     inherit (chromiumPkg.meta) timeout;
   };
 
@@ -65,6 +67,9 @@ mapAttrs (channel: chromiumPkg: makeTest {
     from contextlib import contextmanager
 
 
+    major_version = "${versions.major (getVersion chromiumPkg.name)}"
+
+
     # Run as user alice
     def ru(cmd):
         return "su - ${user} -c " + shlex.quote(cmd)
@@ -84,7 +89,6 @@ mapAttrs (channel: chromiumPkg: makeTest {
             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")
@@ -162,6 +166,8 @@ mapAttrs (channel: chromiumPkg: makeTest {
         clipboard = machine.succeed(
             ru("${pkgs.xclip}/bin/xclip -o")
         )
+        if url == "chrome://gpu":
+            clipboard = ""  # TODO: We cannot copy the text via Ctrl+a
         print(f"{description} window content:\n{clipboard}")
         with machine.nested(description):
             yield clipboard
@@ -242,9 +248,10 @@ mapAttrs (channel: chromiumPkg: makeTest {
         machine.screenshot("after_copy_from_chromium")
 
 
-    with test_new_win("gpu_info", "chrome://gpu", "chrome://gpu"):
+    with test_new_win("gpu_info", "chrome://gpu", "GPU Internals"):
         # To check the text rendering (catches regressions like #131074):
         machine.wait_for_text("Graphics Feature Status")
+        # TODO: Fix copying all of the text to the clipboard
 
 
     with test_new_win("version_info", "chrome://version", "About Version") as clipboard:
diff --git a/nixpkgs/nixos/tests/chrony-ptp.nix b/nixpkgs/nixos/tests/chrony-ptp.nix
new file mode 100644
index 000000000000..b2634a8cfc5c
--- /dev/null
+++ b/nixpkgs/nixos/tests/chrony-ptp.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "chrony-ptp";
+
+  meta = {
+    maintainers = with lib.maintainers; [ gkleen ];
+  };
+
+  nodes = {
+    qemuGuest = { lib, ... }: {
+      boot.kernelModules = [ "ptp_kvm" ];
+
+      services.chrony = {
+        enable = true;
+        extraConfig = ''
+          refclock PHC /dev/ptp_kvm poll 2 dpoll -2 offset 0 stratum 3
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    qemuGuest.wait_for_unit('multi-user.target')
+    qemuGuest.succeed('systemctl is-active chronyd.service')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cinnamon.nix b/nixpkgs/nixos/tests/cinnamon.nix
new file mode 100644
index 000000000000..2a1389231904
--- /dev/null
+++ b/nixpkgs/nixos/tests/cinnamon.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "cinnamon";
+
+  meta.maintainers = lib.teams.cinnamon.members;
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.desktopManager.cinnamon.enable = true;
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      uid = toString user.uid;
+      bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
+      display = "DISPLAY=:0.0";
+      env = "${bus} ${display}";
+      gdbus = "${env} gdbus";
+      su = command: "su - ${user.name} -c '${env} ${command}'";
+
+      # Call javascript in cinnamon (the shell), returns a tuple (success, output),
+      # where `success` is true if the dbus call was successful and `output` is what
+      # the javascript evaluates to.
+      eval = "call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval";
+
+      # Should be 2 (RunState.RUNNING) when startup is done.
+      # https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
+      getRunState = su "${gdbus} ${eval} Main.runState";
+
+      # Start gnome-terminal.
+      gnomeTerminalCommand = su "gnome-terminal";
+
+      # Hopefully gnome-terminal's wm class.
+      wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
+    in
+    ''
+      machine.wait_for_unit("display-manager.service")
+
+      with subtest("Test if we can see username in slick-greeter"):
+          machine.wait_for_text("${user.description}")
+          machine.screenshot("slick_greeter_lightdm")
+
+      with subtest("Login with slick-greeter"):
+          machine.send_chars("${user.password}\n")
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Wait for the Cinnamon shell"):
+          # Correct output should be (true, '2')
+          machine.wait_until_succeeds("${getRunState} | grep -q 'true,..2'")
+
+      with subtest("Open GNOME Terminal"):
+          machine.succeed("${gnomeTerminalCommand}")
+          # Correct output should be (true, '"Gnome-terminal"')
+          machine.wait_until_succeeds("${wmClass} | grep -q 'true,...Gnome-terminal'")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/clickhouse.nix b/nixpkgs/nixos/tests/clickhouse.nix
index 043263ec05dd..77d6a7ab8be4 100644
--- a/nixpkgs/nixos/tests/clickhouse.nix
+++ b/nixpkgs/nixos/tests/clickhouse.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "clickhouse";
-  meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
 
   nodes.machine = {
     services.clickhouse.enable = true;
diff --git a/nixpkgs/nixos/tests/cloud-init-hostname.nix b/nixpkgs/nixos/tests/cloud-init-hostname.nix
new file mode 100644
index 000000000000..7c657cc9f6f9
--- /dev/null
+++ b/nixpkgs/nixos/tests/cloud-init-hostname.nix
@@ -0,0 +1,46 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  # Hostname can also be set through "hostname" in user-data.
+  # This is how proxmox configures hostname through cloud-init.
+  metadataDrive = pkgs.stdenv.mkDerivation {
+    name = "metadata";
+    buildCommand = ''
+      mkdir -p $out/iso
+
+      cat << EOF > $out/iso/user-data
+      #cloud-config
+      hostname: testhostname
+      EOF
+
+      cat << EOF > $out/iso/meta-data
+      instance-id: iid-local02
+      EOF
+
+      ${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
+    '';
+  };
+
+in makeTest {
+  name = "cloud-init-hostname";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lewo illustris ];
+  };
+
+  nodes.machine2 = { ... }: {
+    virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
+    services.cloud-init.enable = true;
+    networking.hostName = "";
+  };
+
+  testScript = ''
+    unnamed.wait_for_unit("cloud-final.service")
+    assert "testhostname" in unnamed.succeed("hostname")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/cloud-init.nix b/nixpkgs/nixos/tests/cloud-init.nix
index 9feb8d5c1526..786e01add7d4 100644
--- a/nixpkgs/nixos/tests/cloud-init.nix
+++ b/nixpkgs/nixos/tests/cloud-init.nix
@@ -49,18 +49,17 @@ let
               gateway: '12.34.56.9'
           - type: nameserver
             address:
-            - '8.8.8.8'
+            - '6.7.8.9'
             search:
             - 'example.com'
       EOF
       ${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
       '';
   };
+
 in makeTest {
   name = "cloud-init";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ lewo ];
-  };
+  meta.maintainers = with pkgs.lib.maintainers; [ lewo illustris ];
   nodes.machine = { ... }:
   {
     virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
@@ -89,21 +88,27 @@ in makeTest {
 
     # we should be able to log in as the root user, as well as the created nixos user
     unnamed.succeed(
-        "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil root@localhost 'true'"
+        "timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil root@localhost 'true'"
     )
     unnamed.succeed(
-        "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'true'"
+        "timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'true'"
     )
 
     # test changing hostname via cloud-init worked
     assert (
         unnamed.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'hostname'"
+            "timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'hostname'"
         ).strip()
         == "test"
     )
 
+    # check IP and route configs
     assert "default via 12.34.56.9 dev eth0 proto static" in unnamed.succeed("ip route")
     assert "12.34.56.0/24 dev eth0 proto kernel scope link src 12.34.56.78" in unnamed.succeed("ip route")
+
+    # check nameserver and search configs
+    assert "6.7.8.9" in unnamed.succeed("resolvectl status")
+    assert "example.com" in unnamed.succeed("resolvectl status")
+
   '';
 }
diff --git a/nixpkgs/nixos/tests/cloudlog.nix b/nixpkgs/nixos/tests/cloudlog.nix
new file mode 100644
index 000000000000..c99951c1b22c
--- /dev/null
+++ b/nixpkgs/nixos/tests/cloudlog.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "cloudlog";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ melling ];
+  };
+  nodes = {
+    machine = {
+      services.mysql.package = pkgs.mariadb;
+      services.cloudlog.enable = true;
+    };
+  };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("phpfpm-cloudlog")
+    machine.wait_for_open_port(80);
+    machine.wait_until_succeeds("curl -s -L --fail http://localhost | grep 'Login - Cloudlog'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cockpit.nix b/nixpkgs/nixos/tests/cockpit.nix
new file mode 100644
index 000000000000..6f86d1e2c464
--- /dev/null
+++ b/nixpkgs/nixos/tests/cockpit.nix
@@ -0,0 +1,135 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+
+  let
+    user = "alice"; # from ./common/user-account.nix
+    password = "foobar"; # from ./common/user-account.nix
+  in {
+    name = "cockpit";
+    meta = {
+      maintainers = with lib.maintainers; [ lucasew ];
+    };
+    nodes = {
+      server = { config, ... }: {
+        imports = [ ./common/user-account.nix ];
+        security.polkit.enable = true;
+        users.users.${user} = {
+          extraGroups = [ "wheel" ];
+        };
+        services.cockpit = {
+          enable = true;
+          openFirewall = true;
+          settings = {
+            WebService = {
+              Origins = "https://server:9090";
+            };
+          };
+        };
+      };
+      client = { config, ... }: {
+        imports = [ ./common/user-account.nix ];
+        environment.systemPackages = let
+          seleniumScript = pkgs.writers.writePython3Bin "selenium-script" {
+            libraries = with pkgs.python3Packages; [ selenium ];
+            } ''
+            from selenium import webdriver
+            from selenium.webdriver.common.by import By
+            from selenium.webdriver.firefox.options import Options
+            from selenium.webdriver.support.ui import WebDriverWait
+            from selenium.webdriver.support import expected_conditions as EC
+            from time import sleep
+
+
+            def log(msg):
+                from sys import stderr
+                print(f"[*] {msg}", file=stderr)
+
+
+            log("Initializing")
+
+            options = Options()
+            options.add_argument("--headless")
+
+            driver = webdriver.Firefox(options=options)
+
+            driver.implicitly_wait(10)
+
+            log("Opening homepage")
+            driver.get("https://server:9090")
+
+            wait = WebDriverWait(driver, 60)
+
+
+            def wait_elem(by, query):
+                wait.until(EC.presence_of_element_located((by, query)))
+
+
+            def wait_title_contains(title):
+                wait.until(EC.title_contains(title))
+
+
+            def find_element(by, query):
+                return driver.find_element(by, query)
+
+
+            def set_value(elem, value):
+                script = 'arguments[0].value = arguments[1]'
+                return driver.execute_script(script, elem, value)
+
+
+            log("Waiting for the homepage to load")
+
+            # cockpit sets initial title as hostname
+            wait_title_contains("server")
+            wait_elem(By.CSS_SELECTOR, 'input#login-user-input')
+
+            log("Homepage loaded!")
+
+            log("Filling out username")
+            login_input = find_element(By.CSS_SELECTOR, 'input#login-user-input')
+            set_value(login_input, "${user}")
+
+            log("Filling out password")
+            password_input = find_element(By.CSS_SELECTOR, 'input#login-password-input')
+            set_value(password_input, "${password}")
+
+            log("Submitting credentials for login")
+            driver.find_element(By.CSS_SELECTOR, 'button#login-button').click()
+
+            # driver.implicitly_wait(1)
+            # driver.get("https://server:9090/system")
+
+            log("Waiting dashboard to load")
+            wait_title_contains("${user}@server")
+
+            log("Waiting for the frontend to initialize")
+            sleep(1)
+
+            log("Looking for that banner that tells about limited access")
+            container_iframe = find_element(By.CSS_SELECTOR, 'iframe.container-frame')
+            driver.switch_to.frame(container_iframe)
+
+            assert "Web console is running in limited access mode" in driver.page_source
+
+            driver.close()
+          '';
+        in with pkgs; [ firefox-unwrapped geckodriver seleniumScript ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      server.wait_for_open_port(9090)
+      server.wait_for_unit("network.target")
+      server.wait_for_unit("multi-user.target")
+      server.systemctl("start", "polkit")
+
+      client.wait_for_unit("multi-user.target")
+
+      client.succeed("curl -k https://server:9090 -o /dev/stderr")
+      print(client.succeed("whoami"))
+      client.succeed('PYTHONUNBUFFERED=1 selenium-script')
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/cockroachdb.nix b/nixpkgs/nixos/tests/cockroachdb.nix
index d793842f0ab2..5b1e1a7dee1f 100644
--- a/nixpkgs/nixos/tests/cockroachdb.nix
+++ b/nixpkgs/nixos/tests/cockroachdb.nix
@@ -8,7 +8,7 @@
 # Cluster joins that are outside this window will fail, and nodes that skew
 # outside the window after joining will promptly get kicked out.
 #
-# To accomodate this, we use QEMU/virtio infrastructure and load the 'ptp_kvm'
+# To accommodate this, we use QEMU/virtio infrastructure and load the 'ptp_kvm'
 # driver inside a guest. This driver allows the host machine to pass its clock
 # through to the guest as a hardware clock that appears as a Precision Time
 # Protocol (PTP) Clock device, generally /dev/ptp0. PTP devices can be measured
diff --git a/nixpkgs/nixos/tests/code-server.nix b/nixpkgs/nixos/tests/code-server.nix
new file mode 100644
index 000000000000..7d523dfc617e
--- /dev/null
+++ b/nixpkgs/nixos/tests/code-server.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+{
+  name = "code-server";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.code-server = {
+        enable = true;
+        auth = "none";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("code-server.service")
+    machine.wait_for_open_port(4444)
+    machine.succeed("curl -k --fail http://localhost:4444", timeout=10)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+})
diff --git a/nixpkgs/nixos/tests/coder.nix b/nixpkgs/nixos/tests/coder.nix
new file mode 100644
index 000000000000..12813827284b
--- /dev/null
+++ b/nixpkgs/nixos/tests/coder.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "coder";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ shyim ghuntley ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.coder = {
+        enable = true;
+        accessUrl = "http://localhost:3000";
+      };
+    };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_unit("coder.service")
+    machine.wait_for_open_port(3000)
+
+    machine.succeed("curl --fail http://localhost:3000")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/common/acme/client/default.nix b/nixpkgs/nixos/tests/common/acme/client/default.nix
index 9dbe345e7a01..503e610d1ac9 100644
--- a/nixpkgs/nixos/tests/common/acme/client/default.nix
+++ b/nixpkgs/nixos/tests/common/acme/client/default.nix
@@ -1,7 +1,7 @@
 { lib, nodes, pkgs, ... }:
 let
-  caCert = nodes.acme.config.test-support.acme.caCert;
-  caDomain = nodes.acme.config.test-support.acme.caDomain;
+  caCert = nodes.acme.test-support.acme.caCert;
+  caDomain = nodes.acme.test-support.acme.caDomain;
 
 in {
   security.acme = {
diff --git a/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem b/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem
index 76b0d916a817..48f488ab8f90 100644
--- a/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem
+++ b/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem
@@ -1,19 +1,19 @@
 -----BEGIN CERTIFICATE-----
-MIIDLDCCAhSgAwIBAgIIRDAN3FHH//IwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
-AxMVbWluaWNhIHJvb3QgY2EgNzg3NDZmMB4XDTIwMTAyMTEzMjgzNloXDTIyMTEy
-MDEzMjgzNlowFDESMBAGA1UEAxMJYWNtZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEF
-AAOCAQ8AMIIBCgKCAQEAo8XjMVUaljcaqQ5MFhfPuQgSwdyXEUbpSHz+5yPkE0h9
-Z4Xu5BJF1Oq7h5ggCtadVsIspiY6Jm6aWDOjlh4myzW5UNBNUG3OPEk50vmmHFeH
-pImHO/d8yb33QoF9VRcTZs4tuJYg7l9bSs4jNG72vYvv2YiGAcmjJcsmAZIfniCN
-Xf/LjIm+Cxykn+Vo3UuzO1w5/iuofdgWO/aZxMezmXUivlL3ih4cNzCJei8WlB/l
-EnHrkcy3ogRmmynP5zcz7vmGIJX2ji6dhCa4Got5B7eZK76o2QglhQXqPatG0AOY
-H+RfQfzKemqPG5om9MgJtwFtTOU1LoaiBw//jXKESQIDAQABo3YwdDAOBgNVHQ8B
+MIIDLDCCAhSgAwIBAgIIajCXIUnozqQwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgMjMwYjU4MB4XDTIyMTEyMTE3MTcxMFoXDTQyMTEy
+MTE3MTcxMFowFDESMBAGA1UEAxMJYWNtZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA5INxJwKDVYNfTnkXwvKM/SufBNjvxWZxlkaMFbkAN5wJ
+6HwuesRZE9IgfRO9N+rSq1U2lDBm9gFPERqsQJVZHHJ5kkaNUr89h25+wgX5emGy
+UV2KEpCFssDD4aSBF+b0sryguCa1ZRj9b+pdfRxiYaORjSh5UzlXZoRm9iwHdzHT
+oKLlmqozqzEt0o9qpZL8gv+rv8C5BGOY6hfXAHYmkWRt87FN5BkSjgEWiY++DOAU
+X0TdobdSTrs/xJP+IbadRchqTH2kiG0g2BoCSXUsl7Mdh4IOUeQGDz/F5tH8PAtz
+p3dyjdQEFex2J5tlScLfVHoCBKV3gpCg+Keuum2j8QIDAQABo3YwdDAOBgNVHQ8B
 Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB
-/wQCMAAwHwYDVR0jBBgwFoAU+8IZlLV/Qp5CXqpXMLvtxWlxcJwwFAYDVR0RBA0w
-C4IJYWNtZS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQB0pe8I5/VDkB5VMgQB2GJV
-GKzyigfWbVez9uLmqMj9PPP/zzYKSYeq+91aMuOZrnH7NqBxSTwanULkmqAmhbJJ
-YkXw+FlFekf9FyxcuArzwzzNZDSGcjcdXpN8S2K1qkBd00iSJF9kU7pdZYCIKR20
-QirdBrELEfsJ3GU62a6N3a2YsrisZUvq5TbjGJDcytAtt+WG3gmV7RInLdFfPwbw
-bEHPCnx0uiV0nxLjd/aVT+RceVrFQVt4hR99jLoMlBitSKluZ1ljsrpIyroBhQT0
-pp/pVi6HJdijG0fsPrC325NEGAwcpotLUhczoeM/rffKJd54wLhDkfYxOyRZXivs
+/wQCMAAwHwYDVR0jBBgwFoAUvTCE3Lj/P6OWkmOGtsjcTcIDzkAwFAYDVR0RBA0w
+C4IJYWNtZS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQAvZM4Ik1NOXQfbPRgbolyL
+b3afsSHbhHl9B2f0HGi5EAPdwyeWZsK3BF+SKFGAW5BlXr2SSlW/MQOMiUKTadnS
+8xTOFc1Ws8JWWc82zQqWcOWEXhU+AI8p70sTVFeXPWwLFy3nBRwDH4ZPU8UFHeje
+YXqbfxrsdEFXrbCfWSzPQP24xqVt7n9Am/5XFGtDkRsYlVgLwq/F6lN9hO0/gYIx
+8NsZ8Xy+QvBlGL+z9Zo7EylB8bP9OBtOtEv9fZcnxgughieiTDs36GwdQRE2aI+d
+cG3lQX8NGxgcpDoH8+rNx7Uw7odg0gVbI3agyyvax6DPht+/bzXmHm8ogklGTOoG
 -----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem b/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem
index 741df99a372e..4837f19b3024 100644
--- a/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem
+++ b/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAo8XjMVUaljcaqQ5MFhfPuQgSwdyXEUbpSHz+5yPkE0h9Z4Xu
-5BJF1Oq7h5ggCtadVsIspiY6Jm6aWDOjlh4myzW5UNBNUG3OPEk50vmmHFeHpImH
-O/d8yb33QoF9VRcTZs4tuJYg7l9bSs4jNG72vYvv2YiGAcmjJcsmAZIfniCNXf/L
-jIm+Cxykn+Vo3UuzO1w5/iuofdgWO/aZxMezmXUivlL3ih4cNzCJei8WlB/lEnHr
-kcy3ogRmmynP5zcz7vmGIJX2ji6dhCa4Got5B7eZK76o2QglhQXqPatG0AOYH+Rf
-QfzKemqPG5om9MgJtwFtTOU1LoaiBw//jXKESQIDAQABAoIBADox/2FwVFo8ioS4
-R+Ex5OZjMAcjU6sX/516jTmlT05q2+UFerYgqB/YqXqtW/V9/brulN8VhmRRuRbO
-grq9TBu5o3hMDK0f18EkZB/MBnLbx594H033y6gEkPBZAyhRYtuNOEH3VwxdZhtW
-1Lu1EoiYSUqLcNMBy6+KWJ8GRaXyacMYBlj2lMHmyzkA/t1+2mwTGC3lT6zN0F5Y
-E5umXOxsn6Tb6q3KM9O5IvtmMMKpgj4HIHZLZ6j40nNgHwGRaAv4Sha/vx0DeBw3
-6VlNiTTPdShEkhESlM5/ocqTfI92VHJpM5gkqTYOWBi2aKIPfAopXoqoJdWl4pQ/
-NCFIu2ECgYEAzntNKIcQtf0ewe0/POo07SIFirvz6jVtYNMTzeQfL6CoEjYArJeu
-Vzc4wEQfA4ZFVerBb1/O6M449gI3zex1PH4AX0h8q8DSjrppK1Jt2TnpVh97k7Gg
-Tnat/M/yW3lWYkcMVJJ3AYurXLFTT1dYP0HvBwZN04yInrEcPNXKfmcCgYEAywyJ
-51d4AE94PrANathKqSI/gk8sP+L1gzylZCcUEAiGk/1r45iYB4HN2gvWbS+CvSdp
-F7ShlDWrTaNh2Bm1dgTjc4pWb4J+CPy/KN2sgLwIuM4+ZWIZmEDcio6khrM/gNqK
-aR7xUsvWsqU26O84woY/xR8IHjSNF7cFWE1H2c8CgYEAt6SSi2kVQ8dMg84uYE8t
-o3qO00U3OycpkOQqyQQLeKC62veMwfRl6swCfX4Y11mkcTXJtPTRYd2Ia8StPUkB
-PDwUuKoPt/JXUvoYb59wc7M+BIsbrdBdc2u6cw+/zfutCNuH6/AYSBeg4WAVaIuW
-wSwzG1xP+8cR+5IqOzEqWCECgYATweeVTCyQEyuHJghYMi2poXx+iIesu7/aAkex
-pB/Oo5W8xrb90XZRnK7UHbzCqRHWqAQQ23Gxgztk9ZXqui2vCzC6qGZauV7cLwPG
-zTMg36sVmHP314DYEM+k59ZYiQ6P0jQPoIQo407D2VGrfsOOIhQIcUmP7tsfyJ5L
-hlGMfwKBgGq4VNnnuX8I5kl03NpaKfG+M8jEHmVwtI9RkPTCCX9bMjeG0cDxqPTF
-TRkf3r8UWQTZ5QfAfAXYAOlZvmGhHjSembRbXMrMdi3rGsYRSrQL6n5NHnORUaMy
-FCWo4gyAnniry7tx9dVNgmHmbjEHuQnf8AC1r3dibRCjvJWUiQ8H
+MIIEpQIBAAKCAQEA5INxJwKDVYNfTnkXwvKM/SufBNjvxWZxlkaMFbkAN5wJ6Hwu
+esRZE9IgfRO9N+rSq1U2lDBm9gFPERqsQJVZHHJ5kkaNUr89h25+wgX5emGyUV2K
+EpCFssDD4aSBF+b0sryguCa1ZRj9b+pdfRxiYaORjSh5UzlXZoRm9iwHdzHToKLl
+mqozqzEt0o9qpZL8gv+rv8C5BGOY6hfXAHYmkWRt87FN5BkSjgEWiY++DOAUX0Td
+obdSTrs/xJP+IbadRchqTH2kiG0g2BoCSXUsl7Mdh4IOUeQGDz/F5tH8PAtzp3dy
+jdQEFex2J5tlScLfVHoCBKV3gpCg+Keuum2j8QIDAQABAoIBAHfnUHQ7qVYxfMzc
+VU+BneEqBmKwwf8+ZdOIaPDtBeQoCDrpDip05Ji15T48IUk5+hjUubLAQwZKYYaE
+DGZG918p4giS5IzKtCpgHDsKj4FbyglPn6dmFgFZjG7VtrcoBLXUrDB0fzHxDuqu
+eyeuwSCihzkeR6sXp3iveKcrKy+rA31aqWvJZb24qyAu1y8KIcf2ZMUiYcJF2kpL
+XZz4uyx4x/B9NE+PmLqo7x/9iS+p5aT2kWVCVUGmhII0ChFnWSnjxqecBMhWFY1O
+3U0lKhloj6UKBya91hGospEJdaLHpHCWUgYPvA5mG+48kqYkPkecmTf8Xha3TxPf
+g1qv3sECgYEA+hMO1qTlnqhBajCMcAGIlpRHwr97hQMdSylHBXob1xCnuTEJKHOo
+7UmQw9hJgD4JgYxcivg/OFErXdefbSae9NqSNdOshxmrxz6DFTN3Ms3WR1I1be3c
+B2mpGllMPbxJ3CKFet2CQSvOM9jfbK68R7Jlhiap0bESvWrT9ztUCWUCgYEA6e2Y
+iMNNo1dWushSMVvCkWR9CLAsnWnjFG4FYIPz/iuxJjRXDiWyR6x4WYjUx3ZBhpf5
+wVFUK7VaPJBfOn7KCan59dqOvL3LSB/5SupwRMecCEhYPQvSaxn4MNrx0Vi83O4C
+togyD9/UJ4ji+TXwMj2eMzwRspmO/26hXkQGzZ0CgYEA0qlLTrYKWOUUdgf/xjsE
+fRTcfsofm6VMAAz9rzd2TG3TXMZaGKGWJI5cTR7ejBG2oFNFgiwt1ZtLFPqXarOm
+JE4b7QwrwoN1mZqngiygtUOAxwQRzlEZkYUI1xFykG8VKURLfX0sRQpJ4pNHY56v
+LRazP5dCZ0rrpnVfql1oJaECgYEAxtvT728XcOOuNtpUBOGcZTynjds2EhsRjyx4
+JbQGlutNjMyxtLUW+RcEuBg5ydYdne1Tw6L/iqiALTwNuAxQdCaq9vT0oj41sPp9
+UdI53j5Rxji5yitilOlesylsqCpnYuhyJflhlV0RXQpg6LmRlyQKeEN4R/uCNGI3
+i4sIvYECgYEA4DC2qObfB0UkN81uGluwwM5rR04qvIc5xX3QIvHuIJOs/uP54daD
+OiEDTxTpiqDNsFL0Pyl07aL7jubHNqU/eQpQIEZRlDy4Mr31QSbQ9R2/NNBwHu22
+BnnNKzZ97T0NVgxJXOqcOlRGjwb/5OUDpaIClJY+GqilEdOeu7Pl3aA=
 -----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem b/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem
index 5c33e879b675..b6f2b9e3a91f 100644
--- a/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem
+++ b/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem
@@ -1,20 +1,20 @@
 -----BEGIN CERTIFICATE-----
-MIIDSzCCAjOgAwIBAgIIeHRvRrNvbGQwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
-AxMVbWluaWNhIHJvb3QgY2EgNzg3NDZmMCAXDTIwMTAyMTEzMjgzNloYDzIxMjAx
-MDIxMTMyODM2WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA3ODc0NmYwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrNTzVLDJOKtGYGLU98EEcLKps
-tXHCLC6G54LKbEcU80fn+ArX8qsPSHyhdXQkcYjq6Vh/EDJ1TctyRSnvAjwyG4Aa
-1Zy1QFc/JnjMjvzimCkUc9lQ+wkLwHSM/KGwR1cGjmtQ/EMClZTA0NwulJsXMKVz
-bd5asXbq/yJTQ5Ww25HtdNjwRQXTvB7r3IKcY+DsED9CvFvC9oG/ZhtZqZuyyRdC
-kFUrrv8WNUDkWSN+lMR6xMx8v0583IN6f11IhX0b+svK98G81B2eswBdkzvVyv9M
-unZBO0JuJG8sdM502KhWLmzBC1ZbvgUBF9BumDRpMFH4DCj7+qQ2taWeGyc7AgMB
+MIIDSzCCAjOgAwIBAgIIIwtYp+WlBbswDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgMjMwYjU4MCAXDTIyMTEyMTE3MTcxMFoYDzIxMjIx
+MTIxMTcxNzEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyMzBiNTgwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvqAoAyV8igrmBnU6T1nQDfkkQ
+HjQp+ANCthNCi4kGPOoTxrYrUMWa6d/aSIv5hKO2A+r2GdTeM1RvSo6GUr3GmsJc
+WUMbIsJ0SJSLQEyvmFPpzfV3NdfIt6vZRiqJbLt7yuDiZil33GdQEKYywJxIsCb2
+CSd55V1cZSiLItWEIURAhHhSxHabMRmIF/xZWxKFEDeagzXOxUBPAvIwzzqQroBv
+3vZhfgcAjCyS0crJ/E2Wa6GLKfFvaXGEj/KlXftwpbvFtnNBtmtJcNy9a8LJoOcA
+E+ZjD21hidnCc+Yag7LaR3ZtAVkpeRJ9rRNBkVP4rv2mq2skIkgDfY/F8smPAgMB
 AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
-BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT7whmUtX9CnkJe
-qlcwu+3FaXFwnDAfBgNVHSMEGDAWgBT7whmUtX9CnkJeqlcwu+3FaXFwnDANBgkq
-hkiG9w0BAQsFAAOCAQEARMe1wKmF33GjEoLLw0oDDS4EdAv26BzCwtrlljsEtwQN
-95oSzUNd6o4Js7WCG2o543OX6cxzM+yju8TES3+vJKDgsbNMU0bWCv//tdrb0/G8
-OkU3Kfi5q4fOauZ1pqGv/pXdfYhZ5ieB/zwis3ykANe5JfB0XqwCb1Vd0C3UCIS2
-NPKngRwNSzphIsbzfvxGDkdM1enuGl5CVyDhrwTMqGaJGDSOv6U5jKFxKRvigqTN
-Ls9lPmT5NXYETduWLBR3yUIdH6kZXrcozZ02B9vjOB2Cv4RMDc+9eM30CLIWpf1I
-097e7JkhzxFhfC/bMMt3P1FeQc+fwH91wdBmNi7tQw==
+BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS9MITcuP8/o5aS
+Y4a2yNxNwgPOQDAfBgNVHSMEGDAWgBS9MITcuP8/o5aSY4a2yNxNwgPOQDANBgkq
+hkiG9w0BAQsFAAOCAQEADCcgaxrI/pqjkYb0c3QHwfKCNz4khSWs/9tBpBfdxdUX
+uvG7rZzVW7pkzML+m4tSo2wm9sHRAgG+dIpzbSoRTouMntWlvYEnrr1SCw4NyBo1
+cwmNUz4JL+E3dnpI4FSOpyFyO87qL9ep0dxQEADWSppyCA762wfFpY+FvT6b/he8
+eDEc/Umjfm+X0tqNWx3aVoeyIJT46AeElry2IRLAk7z/vEPGFFzgd2Jh6Qsdeagk
+YkU0tFl9q9BotPYGlCMtVjmzbJtxh4uM9YCgiz1THzFjrUvfaTM8VjuBxbpoCZkS
+85mNhFZvNq8/cgYc0kYZOg8+jRdy87xmTRp64LBd6w==
 -----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/ca.key.pem b/nixpkgs/nixos/tests/common/acme/server/ca.key.pem
index ed46f5dccf46..5d46c025788f 100644
--- a/nixpkgs/nixos/tests/common/acme/server/ca.key.pem
+++ b/nixpkgs/nixos/tests/common/acme/server/ca.key.pem
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAqzU81SwyTirRmBi1PfBBHCyqbLVxwiwuhueCymxHFPNH5/gK
-1/KrD0h8oXV0JHGI6ulYfxAydU3LckUp7wI8MhuAGtWctUBXPyZ4zI784pgpFHPZ
-UPsJC8B0jPyhsEdXBo5rUPxDApWUwNDcLpSbFzClc23eWrF26v8iU0OVsNuR7XTY
-8EUF07we69yCnGPg7BA/QrxbwvaBv2YbWambsskXQpBVK67/FjVA5FkjfpTEesTM
-fL9OfNyDen9dSIV9G/rLyvfBvNQdnrMAXZM71cr/TLp2QTtCbiRvLHTOdNioVi5s
-wQtWW74FARfQbpg0aTBR+Awo+/qkNrWlnhsnOwIDAQABAoIBAA3ykVkgd5ysmlSU
-trcsCnHcJaojgff6l3PACoSpG4VWaGY6a8+54julgRm6MtMBONFCX0ZCsImj484U
-Wl0xRmwil2YYPuL5MeJgJPktMObY1IfpBCw3tz3w2M3fiuCMf0d2dMGtO1xLiUnH
-+hgFXTkfamsj6ThkOrbcQBSebeRxbKM5hqyCaQoieV+0IJnyxUVq/apib8N50VsH
-SHd4oqLUuEZgg6N70+l5DpzedJUb4nrwS/KhUHUBgnoPItYBCiGPmrwLk7fUhPs6
-kTDqJDtc/xW/JbjmzhWEpVvtumcC/OEKULss7HLdeQqwVBrRQkznb0M9AnSra3d0
-X11/Y4ECgYEA3FC8SquLPFb2lHK4+YbJ4Ac6QVWeYFEHiZ0Rj+CmONmjcAvOGLPE
-SblRLm3Nbrkxbm8FF6/AfXa/rviAKEVPs5xqGfSDw/3n1uInPcmShiBCLwM/jHH5
-NeVG+R5mTg5zyQ/pQMLWRcs+Ail+ZAnZuoGpW3Cdc8OtCUYFQ7XB6nsCgYEAxvBJ
-zFxcTtsDzWbMWXejugQiUqJcEbKWwEfkRbf3J2rAVO2+EFr7LxdRfN2VwPiTQcWc
-LnN2QN+ouOjqBMTh3qm5oQY+TLLHy86k9g1k0gXWkMRQgP2ZdfWH1HyrwjLUgLe1
-VezFN7N1azgy6xFkInAAvuA4loxElZNvkGBgekECgYA/Xw26ILvNIGqO6qzgQXAh
-+5I7JsiGheg4IjDiBMlrQtbrLMoceuD0H9UFGNplhel9DXwWgxxIOncKejpK2x0A
-2fX+/0FDh+4+9hA5ipiV8gN3iGSoHkSDxy5yC9d7jlapt+TtFt4Rd1OfxZWwatDw
-/8jaH3t6yAcmyrhK8KYVrwKBgAE5KwsBqmOlvyE9N5Z5QN189wUREIXfVkP6bTHs
-jq2EX4hmKdwJ4y+H8i1VY31bSfSGlY5HkXuWpH/2lrHO0CDBZG3UDwADvWzIaYVF
-0c/kz0v2mRQh+xaZmus4lQnNrDbaalgL666LAPbW0qFVaws3KxoBYPe0BxvwWyhF
-H3LBAoGBAKRRNsq2pWQ8Gqxc0rVoH0FlexU9U2ci3lsLmgEB0A/o/kQkSyAxaRM+
-VdKp3sWfO8o8lX5CVQslCNBSjDTNcat3Co4NEBLg6Xv1yKN/WN1GhusnchP9szsP
-oU47gC89QhUyWSd6vvr2z2NG9C3cACxe4dhDSHQcE4nHSldzCKv2
+MIIEowIBAAKCAQEAr6gKAMlfIoK5gZ1Ok9Z0A35JEB40KfgDQrYTQouJBjzqE8a2
+K1DFmunf2kiL+YSjtgPq9hnU3jNUb0qOhlK9xprCXFlDGyLCdEiUi0BMr5hT6c31
+dzXXyLer2UYqiWy7e8rg4mYpd9xnUBCmMsCcSLAm9gkneeVdXGUoiyLVhCFEQIR4
+UsR2mzEZiBf8WVsShRA3moM1zsVATwLyMM86kK6Ab972YX4HAIwsktHKyfxNlmuh
+iynxb2lxhI/ypV37cKW7xbZzQbZrSXDcvWvCyaDnABPmYw9tYYnZwnPmGoOy2kd2
+bQFZKXkSfa0TQZFT+K79pqtrJCJIA32PxfLJjwIDAQABAoIBAErEFJXnIIY47Cq+
+QS7t7e16uDCTGpLujLy9cQ83AzjTfrKyNuHS/HkGqRBpJqMrEN+tZTohHpkBciP4
+sRd9amd5gdb663RGZExIhGmNEdb/2F/BGYUHNvSpMQ1HL13VGSwE25mh8G6jMppC
+q+sYTq0lxT+d/96DgSyNpicqyYT2S2CTCRkWGAsc6KQwRpBYqoEqUeakyGfe2k85
+pj32H53Si/49fkWkQ9RciPdg7qcu7u/iegwAkkjKoATeEjNf0NqBlkWag1qU0UHR
+r2xDin+3ffEU2GQEwSvnGwlo7uyAN0UsryEWa9suuhX5T4eSWAMgTL4iVkh8Aa24
++YEFOGkCgYEA0DUb++31+nuxU8N+GPaPQXiob8C0RmSzSzSHJ3daJpzq8k576jqs
+3TgkhLDzQepcTYVU2ucn6+9ziXEsz4H06W3FNGktnyK4BRqYitt5TjZvPc+WTPhR
+0U+iUqBZilCAhUkIsNUiGvnMhz9VfcS/gn+NqhL7kvYi11/jAc4bbB0CgYEA1/oh
++t1ZKVLkbANrma/M8AX27Vl3k4jgOWGzFwAVD10zN31gGyVjv1knmG22pmL2+N+Z
+8CnVmdHQQQIWV1pYbgwRkvpnZWyH7AvHd9l1XLYyOU3VEpz+e2bpMtzesaza3UWW
+k8NELNE5sBopY939XkQ9G3aMXtbkx01zX+0BZJsCgYB+MdJ2TfKrEVGXfYPuSXLm
+seUVZu1dRSfOy1WnvBVuFenpV1yPyWSA6MhpjH7EUvIDIm8eBsERpZ6XjXslgpUY
+7ql6bM10CK0UmtwePYw2tZOTGUD2AgRFI0k1X28mAEkFgBC+bVAwnXsz9lUw15Fj
+3T/V9493savIcpu6uluwmQKBgQCE/I4jzFv0aAgiwlBlB6znNqT/LRHGFIgMjS4b
+QX+2QCsjRd4BmRo8XodVAmlvNozgXb6J9RiDaIAVJ1XeX9EHogLIP8ue1h8zp2Uh
+VRNBDScLxfMnTOgd0BZTrVCqkscJbKn1Pk0iU4pz9wf5aF10yAvgdzSjySqB1hzu
+uh8bdQKBgEpFIyhqfXf/NzchI5y23Cok14LFIPJ1yERD/B8taS7muVQwpgffy+Ld
+BH7dhafWSDVqIk1e6yl+82b4amleTEmDfopgc6FR7uPid1JoFxrwhnEfC3FjZamp
+1OzXAOE/mX3jHf1spqpB2J/rDVPKi934ocQVoWnBeRopGTXxzbed
 -----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/default.nix b/nixpkgs/nixos/tests/common/acme/server/default.nix
index 450d49e60399..2a2e3b08a1df 100644
--- a/nixpkgs/nixos/tests/common/acme/server/default.nix
+++ b/nixpkgs/nixos/tests/common/acme/server/default.nix
@@ -18,10 +18,10 @@
 #
 #   example = { nodes, ... }: {
 #     networking.nameservers = [
-#       nodes.acme.config.networking.primaryIPAddress
+#       nodes.acme.networking.primaryIPAddress
 #     ];
 #     security.pki.certificateFiles = [
-#       nodes.acme.config.test-support.acme.caCert
+#       nodes.acme.test-support.acme.caCert
 #     ];
 #   };
 # }
@@ -36,7 +36,7 @@
 #   acme = { nodes, lib, ... }: {
 #     imports = [ ./common/acme/server ];
 #     networking.nameservers = lib.mkForce [
-#       nodes.myresolver.config.networking.primaryIPAddress
+#       nodes.myresolver.networking.primaryIPAddress
 #     ];
 #   };
 #
@@ -76,24 +76,24 @@ let
 in {
   imports = [ ../../resolver.nix ];
 
-  options.test-support.acme = with lib; {
-    caDomain = mkOption {
-      type = types.str;
+  options.test-support.acme = {
+    caDomain = lib.mkOption {
+      type = lib.types.str;
       readOnly = true;
       default = domain;
-      description = ''
-        A domain name to use with the <literal>nodes</literal> attribute to
+      description = lib.mdDoc ''
+        A domain name to use with the `nodes` attribute to
         identify the CA server.
       '';
     };
-    caCert = mkOption {
-      type = types.path;
+    caCert = lib.mkOption {
+      type = lib.types.path;
       readOnly = true;
       default = testCerts.ca.cert;
-      description = ''
-        A certificate file to use with the <literal>nodes</literal> attribute to
+      description = lib.mdDoc ''
+        A certificate file to use with the `nodes` attribute to
         inject the test CA certificate used in the ACME server into
-        <option>security.pki.certificateFiles</option>.
+        {option}`security.pki.certificateFiles`.
       '';
     };
   };
diff --git a/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix b/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix
index cd8fe0dffca1..4f38ca309b05 100644
--- a/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix
+++ b/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix
@@ -10,8 +10,12 @@ let
   domain = conf.domain;
 in mkDerivation {
   name = "test-certs";
-  buildInputs = [ minica ];
-  phases = [ "buildPhase" "installPhase" ];
+  buildInputs = [ (minica.overrideAttrs (old: {
+    prePatch = ''
+      sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
+    '';
+  })) ];
+  dontUnpack = true;
 
   buildPhase = ''
     minica \
diff --git a/nixpkgs/nixos/tests/common/auto.nix b/nixpkgs/nixos/tests/common/auto.nix
index da6b14e9f160..ac56bed4a88f 100644
--- a/nixpkgs/nixos/tests/common/auto.nix
+++ b/nixpkgs/nixos/tests/common/auto.nix
@@ -1,46 +1,35 @@
 { config, lib, ... }:
 
-with lib;
-
 let
-
   dmcfg = config.services.xserver.displayManager;
   cfg = config.test-support.displayManager.auto;
-
 in
-
 {
 
   ###### interface
 
   options = {
-
     test-support.displayManager.auto = {
-
-      enable = mkOption {
+      enable = lib.mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the fake "auto" display manager, which
           automatically logs in the user specified in the
-          <option>user</option> option.  This is mostly useful for
+          {option}`user` option.  This is mostly useful for
           automated tests.
         '';
       };
 
-      user = mkOption {
+      user = lib.mkOption {
         default = "root";
-        description = "The user account to login automatically.";
+        description = lib.mdDoc "The user account to login automatically.";
       };
-
     };
-
   };
 
-
   ###### implementation
 
-  config = mkIf cfg.enable {
-
+  config = lib.mkIf cfg.enable {
     services.xserver.displayManager = {
       lightdm.enable = true;
       autoLogin = {
@@ -62,7 +51,5 @@ in
 
         session  include   lightdm
     '';
-
   };
-
 }
diff --git a/nixpkgs/nixos/tests/common/ec2.nix b/nixpkgs/nixos/tests/common/ec2.nix
index 64b0a91ac1f7..1a64c464039b 100644
--- a/nixpkgs/nixos/tests/common/ec2.nix
+++ b/nixpkgs/nixos/tests/common/ec2.nix
@@ -17,6 +17,7 @@ with pkgs.lib;
           ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
         '';
       };
+      indentLines = str: concatLines (map (s: "  " + s) (splitString "\n" str));
     in makeTest {
       name = "ec2-" + name;
       nodes = {};
@@ -36,6 +37,8 @@ with pkgs.lib;
                 "create",
                 "-f",
                 "qcow2",
+                "-F",
+                "qcow2",
                 "-o",
                 "backing_file=${image}",
                 disk_image,
@@ -46,7 +49,7 @@ with pkgs.lib;
         # Note: we use net=169.0.0.0/8 rather than
         # net=169.254.0.0/16 to prevent dhcpcd from getting horribly
         # confused. (It would get a DHCP lease in the 169.254.*
-        # range, which it would then configure and prompty delete
+        # range, which it would then configure and promptly delete
         # again when it deletes link-local addresses.) Ideally we'd
         # turn off the DHCP server, but qemu does not have an option
         # to do that.
@@ -59,7 +62,11 @@ with pkgs.lib;
         )
 
         machine = create_machine({"startCommand": start_command})
-      '' + script;
+        try:
+      '' + indentLines script + ''
+        finally:
+          machine.shutdown()
+      '';
 
       inherit meta;
     };
diff --git a/nixpkgs/nixos/tests/common/resolver.nix b/nixpkgs/nixos/tests/common/resolver.nix
index 09a74de20faa..3ddf730668c4 100644
--- a/nixpkgs/nixos/tests/common/resolver.nix
+++ b/nixpkgs/nixos/tests/common/resolver.nix
@@ -10,15 +10,15 @@
     type = lib.types.bool;
     default = true;
     internal = true;
-    description = ''
+    description = lib.mdDoc ''
       Whether to enable the resolver that automatically discovers zone in the
       test network.
 
-      This option is <literal>true</literal> by default, because the module
+      This option is `true` by default, because the module
       defining this option needs to be explicitly imported.
 
       The reason this option exists is for the
-      <filename>nixos/tests/common/acme/server</filename> module, which
+      {file}`nixos/tests/common/acme/server` module, which
       needs that option to disable the resolver once the user has set its own
       resolver.
     '';
diff --git a/nixpkgs/nixos/tests/connman.nix b/nixpkgs/nixos/tests/connman.nix
new file mode 100644
index 000000000000..348b2a895a63
--- /dev/null
+++ b/nixpkgs/nixos/tests/connman.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+{
+  name = "connman";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  # Router running radvd on VLAN 1
+  nodes.router = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    virtualisation.vlans = [ 1 ];
+
+    boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+
+    networking = {
+      useDHCP = false;
+      interfaces.eth1.ipv6.addresses =
+        [ { address = "fd12::1"; prefixLength = 64; } ];
+    };
+
+    services.radvd = {
+      enable = true;
+      config = ''
+        interface eth1 {
+          AdvSendAdvert on;
+          AdvManagedFlag on;
+          AdvOtherConfigFlag on;
+          prefix fd12::/64 {
+            AdvAutonomous off;
+          };
+        };
+      '';
+    };
+  };
+
+  # Client running connman, connected to VLAN 1
+  nodes.client = { ... }: {
+    virtualisation.vlans = [ 1 ];
+
+    # add a virtual wlan interface
+    boot.kernelModules = [ "mac80211_hwsim" ];
+    boot.extraModprobeConfig = ''
+      options mac80211_hwsim radios=1
+    '';
+
+    # Note: the overrides are needed because the wifi is
+    # disabled with mkVMOverride in qemu-vm.nix.
+    services.connman.enable = lib.mkOverride 0 true;
+    services.connman.networkInterfaceBlacklist = [ "eth0" ];
+    networking.wireless.enable = lib.mkOverride 0 true;
+    networking.wireless.interfaces = [ "wlan0" ];
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("Router is ready"):
+          router.wait_for_unit("radvd.service")
+
+      with subtest("Daemons are running"):
+          client.wait_for_unit("wpa_supplicant-wlan0.service")
+          client.wait_for_unit("connman.service")
+          client.wait_until_succeeds("connmanctl state | grep -q ready")
+
+      with subtest("Wired interface is configured"):
+          client.wait_until_succeeds("ip -6 route | grep -q fd12::/64")
+          client.wait_until_succeeds("ping -c 1 fd12::1")
+
+      with subtest("Can set up a wireless access point"):
+          client.succeed("connmanctl enable wifi")
+          client.wait_until_succeeds("connmanctl tether wifi on nixos-test reproducibility | grep -q 'Enabled'")
+          client.wait_until_succeeds("iw wlan0 info | grep -q nixos-test")
+    '';
+})
+
diff --git a/nixpkgs/nixos/tests/consul-template.nix b/nixpkgs/nixos/tests/consul-template.nix
new file mode 100644
index 000000000000..cbffa94569e3
--- /dev/null
+++ b/nixpkgs/nixos/tests/consul-template.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "consul-template";
+
+  nodes.machine = { ... }: {
+    services.consul-template.instances.example.settings = {
+      template = [{
+        contents = ''
+          {{ key "example" }}
+        '';
+        perms = "0600";
+        destination = "/example";
+      }];
+    };
+
+    services.consul = {
+      enable = true;
+      extraConfig = {
+        server = true;
+        bootstrap_expect = 1;
+        bind_addr = "127.0.0.1";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("consul.service")
+    machine.wait_for_open_port(8500)
+
+    machine.wait_for_unit("consul-template-example.service")
+
+    machine.wait_until_succeeds('consul kv put example example')
+
+    machine.wait_for_file("/example")
+    machine.succeed('grep "example" /example')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/consul.nix b/nixpkgs/nixos/tests/consul.nix
index ee85f1d0b917..6233234ff083 100644
--- a/nixpkgs/nixos/tests/consul.nix
+++ b/nixpkgs/nixos/tests/consul.nix
@@ -145,7 +145,7 @@ in {
     client2.succeed("[ $(consul kv get testkey) == 42 ]")
 
 
-    def rolling_reboot_test(proper_rolling_procedure=True):
+    def rolling_restart_test(proper_rolling_procedure=True):
         """
         Tests that the cluster can tolearate failures of any single server,
         following the recommended rolling upgrade procedure from
@@ -158,7 +158,13 @@ in {
         """
 
         for server in servers:
-            server.crash()
+            server.block()
+            server.systemctl("stop consul")
+
+            # Make sure the stopped peer is recognized as being down
+            client1.wait_until_succeeds(
+              f"[ $(consul members | grep {server.name} | grep -o -E 'failed|left' | wc -l) == 1 ]"
+            )
 
             # For each client, wait until they have connection again
             # using `kv get -recurse` before issuing commands.
@@ -170,8 +176,8 @@ in {
             client2.succeed("[ $(consul kv get testkey) == 43 ]")
             client2.succeed("consul kv delete testkey")
 
-            # Restart crashed machine.
-            server.start()
+            server.unblock()
+            server.systemctl("start consul")
 
             if proper_rolling_procedure:
                 # Wait for recovery.
@@ -197,10 +203,14 @@ in {
         """
 
         for server in servers:
-            server.crash()
+            server.block()
+            server.systemctl("stop --no-block consul")
 
         for server in servers:
-            server.start()
+            # --no-block is async, so ensure it has been stopped by now
+            server.wait_until_fails("systemctl is-active --quiet consul")
+            server.unblock()
+            server.systemctl("start consul")
 
         # Wait for recovery.
         wait_for_healthy_servers()
@@ -217,13 +227,13 @@ in {
 
     # Run the tests.
 
-    print("rolling_reboot_test()")
-    rolling_reboot_test()
+    print("rolling_restart_test()")
+    rolling_restart_test()
 
     print("all_servers_crash_simultaneously_test()")
     all_servers_crash_simultaneously_test()
 
-    print("rolling_reboot_test(proper_rolling_procedure=False)")
-    rolling_reboot_test(proper_rolling_procedure=False)
+    print("rolling_restart_test(proper_rolling_procedure=False)")
+    rolling_restart_test(proper_rolling_procedure=False)
   '';
 })
diff --git a/nixpkgs/nixos/tests/containers-imperative.nix b/nixpkgs/nixos/tests/containers-imperative.nix
index 3007efaf8871..22b664a90e17 100644
--- a/nixpkgs/nixos/tests/containers-imperative.nix
+++ b/nixpkgs/nixos/tests/containers-imperative.nix
@@ -25,6 +25,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
               system.stateVersion = "18.03";
             };
           };
+
+          # The system is inherited from the host above.
+          # Set it to null, to remove the "legacy" entrypoint's non-hermetic default.
+          system = null;
         };
       in with pkgs; [
         stdenv stdenvNoCC emptyContainer.config.containers.foo.path
diff --git a/nixpkgs/nixos/tests/containers-unified-hierarchy.nix b/nixpkgs/nixos/tests/containers-unified-hierarchy.nix
new file mode 100644
index 000000000000..978d59e12c8a
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-unified-hierarchy.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-unified-hierarchy";
+  meta = {
+    maintainers = with lib.maintainers; [ farnoy ];
+  };
+
+  nodes.machine = { ... }: {
+    containers = {
+      test-container = {
+        autoStart = true;
+        config = { };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+
+    machine.succeed("echo 'stat -fc %T /sys/fs/cgroup/ | grep cgroup2fs' | nixos-container root-login test-container")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/convos.nix b/nixpkgs/nixos/tests/convos.nix
index a5dafed8f6f0..8fe5892da9e5 100644
--- a/nixpkgs/nixos/tests/convos.nix
+++ b/nixpkgs/nixos/tests/convos.nix
@@ -1,14 +1,12 @@
 import ./make-test-python.nix ({ lib, pkgs, ... }:
 
-with lib;
+
 let
   port = 3333;
 in
 {
   name = "convos";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ sgo ];
-  };
+  meta.maintainers = with lib.maintainers; [ sgo ];
 
   nodes = {
     machine =
diff --git a/nixpkgs/nixos/tests/corerad.nix b/nixpkgs/nixos/tests/corerad.nix
index 638010f92f44..b6f5d7fc6f75 100644
--- a/nixpkgs/nixos/tests/corerad.nix
+++ b/nixpkgs/nixos/tests/corerad.nix
@@ -1,5 +1,6 @@
 import ./make-test-python.nix (
   {
+    name = "corerad";
     nodes = {
       router = {config, pkgs, ...}: {
         config = {
diff --git a/nixpkgs/nixos/tests/coturn.nix b/nixpkgs/nixos/tests/coturn.nix
index dff832281c7c..301b34b0da70 100644
--- a/nixpkgs/nixos/tests/coturn.nix
+++ b/nixpkgs/nixos/tests/coturn.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ ... }: {
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "coturn";
   nodes = {
     default = {
@@ -25,5 +25,9 @@ import ./make-test-python.nix ({ ... }: {
       with subtest("works with static-auth-secret-file"):
           secretsfile.wait_for_unit("coturn.service")
           secretsfile.succeed("grep 'some-very-secret-string' /run/coturn/turnserver.cfg")
+          # Forbidden IP, fails:
+          secretsfile.fail("${pkgs.coturn}/bin/turnutils_uclient -W some-very-secret-string 127.0.0.1 -DgX -e 127.0.0.1 -n 1 -c -y")
+          # allowed-peer-ip, should succeed:
+          secretsfile.succeed("${pkgs.coturn}/bin/turnutils_uclient -W some-very-secret-string 192.168.1.2 -DgX -e 192.168.1.2 -n 1 -c -y")
     '';
 })
diff --git a/nixpkgs/nixos/tests/couchdb.nix b/nixpkgs/nixos/tests/couchdb.nix
index b57072d6be2d..cf6ca8e4548d 100644
--- a/nixpkgs/nixos/tests/couchdb.nix
+++ b/nixpkgs/nixos/tests/couchdb.nix
@@ -1,9 +1,8 @@
 let
-
   makeNode = couchpkg: user: passwd:
     { pkgs, ... } :
 
-      { environment.systemPackages = with pkgs; [ jq ];
+      { environment.systemPackages = [ pkgs.jq ];
         services.couchdb.enable = true;
         services.couchdb.package = couchpkg;
         services.couchdb.adminUser = user;
@@ -12,16 +11,11 @@ let
   testuser = "testadmin";
   testpass = "cowabunga";
   testlogin = "${testuser}:${testpass}@";
-
-in import ./make-test-python.nix ({ pkgs, lib, ...}:
-
-with lib;
-
+in
+import ./make-test-python.nix ({ pkgs, lib, ...}:
 {
   name = "couchdb";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ];
-  };
+  meta.maintainers = [ ];
 
   nodes = {
     couchdb3 = makeNode pkgs.couchdb3 testuser testpass;
diff --git a/nixpkgs/nixos/tests/cri-o.nix b/nixpkgs/nixos/tests/cri-o.nix
index d3a8713d6a9b..08e1e8f36b06 100644
--- a/nixpkgs/nixos/tests/cri-o.nix
+++ b/nixpkgs/nixos/tests/cri-o.nix
@@ -1,7 +1,7 @@
 # This test runs CRI-O and verifies via critest
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "cri-o";
-  meta.maintainers = with pkgs.lib.maintainers; teams.podman.members;
+  meta.maintainers = with pkgs.lib; teams.podman.members;
 
   nodes = {
     crio = {
diff --git a/nixpkgs/nixos/tests/cups-pdf.nix b/nixpkgs/nixos/tests/cups-pdf.nix
new file mode 100644
index 000000000000..957b0296a755
--- /dev/null
+++ b/nixpkgs/nixos/tests/cups-pdf.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "cups-pdf";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    environment.systemPackages = [ pkgs.poppler_utils ];
+    fonts.fonts = [ pkgs.dejavu_fonts ];  # yields more OCR-able pdf
+    services.printing.cups-pdf.enable = true;
+    services.printing.cups-pdf.instances = {
+      opt = {};
+      noopt.installPrinter = false;
+    };
+    hardware.printers.ensurePrinters = [{
+      name = "noopt";
+      model = "CUPS-PDF_noopt.ppd";
+      deviceUri = "cups-pdf:/noopt";
+    }];
+  };
+
+  # we cannot check the files with pdftotext, due to
+  # https://github.com/alexivkin/CUPS-PDF-to-PDF/issues/7
+  # we need `imagemagickBig` as it has ghostscript support
+
+  testScript = ''
+    from subprocess import run
+    machine.wait_for_unit("multi-user.target")
+    for name in ("opt", "noopt"):
+        text = f"test text {name}".upper()
+        machine.wait_until_succeeds(f"lpstat -v {name}")
+        machine.succeed(f"su - alice -c 'echo -e \"\n  {text}\" | lp -d {name}'")
+        # wait until the pdf files are completely produced and readable by alice
+        machine.wait_until_succeeds(f"su - alice -c 'pdfinfo /var/spool/cups-pdf-{name}/users/alice/*.pdf'")
+        machine.succeed(f"cp /var/spool/cups-pdf-{name}/users/alice/*.pdf /tmp/{name}.pdf")
+        machine.copy_from_vm(f"/tmp/{name}.pdf", "")
+        run(f"${pkgs.imagemagickBig}/bin/convert -density 300 $out/{name}.pdf $out/{name}.jpeg", shell=True, check=True)
+        assert text.encode() in run(f"${lib.getExe pkgs.tesseract} $out/{name}.jpeg stdout", shell=True, check=True, capture_output=True).stdout
+  '';
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+})
diff --git a/nixpkgs/nixos/tests/custom-ca.nix b/nixpkgs/nixos/tests/custom-ca.nix
index 73e47c3c9d0d..25a7b6fdea46 100644
--- a/nixpkgs/nixos/tests/custom-ca.nix
+++ b/nixpkgs/nixos/tests/custom-ca.nix
@@ -191,5 +191,5 @@ in
   firefox = { error = "Security Risk"; };
   chromium = { error = "not private"; };
   qutebrowser = { args = "-T"; error = "Certificate error"; };
-  midori = { error = "Security"; };
+  midori = { args = "-p"; error = "Security"; };
 }
diff --git a/nixpkgs/nixos/tests/darling.nix b/nixpkgs/nixos/tests/darling.nix
new file mode 100644
index 000000000000..5665b4c2ffef
--- /dev/null
+++ b/nixpkgs/nixos/tests/darling.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  # Well, we _can_ cross-compile from Linux :)
+  hello = pkgs.runCommand "hello" {
+    sdk = "${pkgs.darling.sdk}/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
+    nativeBuildInputs = with pkgs.llvmPackages_14; [ clang-unwrapped lld ];
+    src = pkgs.writeText "hello.c" ''
+      #include <stdio.h>
+      int main() {
+        printf("Hello, Darling!\n");
+        return 0;
+      }
+    '';
+  } ''
+    clang \
+      -target x86_64-apple-darwin \
+      -fuse-ld=lld \
+      -nostdinc -nostdlib \
+      -mmacosx-version-min=10.15 \
+      --sysroot $sdk \
+      -isystem $sdk/usr/include \
+      -L $sdk/usr/lib -lSystem \
+      $src -o $out
+  '';
+in
+{
+  name = "darling";
+
+  meta.maintainers = with lib.maintainers; [ zhaofengli ];
+
+  nodes.machine = {
+    programs.darling.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    # Darling holds stdout until the server is shutdown
+    machine.succeed("darling ${hello} >hello.out")
+    machine.succeed("grep Hello hello.out")
+    machine.succeed("darling shutdown")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/deepin.nix b/nixpkgs/nixos/tests/deepin.nix
new file mode 100644
index 000000000000..0fe93486b6cb
--- /dev/null
+++ b/nixpkgs/nixos/tests/deepin.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "deepin";
+
+  meta = with lib; {
+    maintainers = teams.deepin.members;
+  };
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.deepin.enable = true;
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+    ''
+      with subtest("Wait for login"):
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if DDE wm chooser actually start"):
+          machine.wait_until_succeeds("pgrep -f dde-wm-chooser")
+          machine.wait_for_window("dde-wm-chooser")
+          machine.execute("pkill dde-wm-chooser")
+
+
+      with subtest("Check if Deepin session components actually start"):
+          machine.wait_until_succeeds("pgrep -f dde-session-daemon")
+          machine.wait_for_window("dde-session-daemon")
+          machine.wait_until_succeeds("pgrep -f dde-desktop")
+          machine.wait_for_window("dde-desktop")
+
+      with subtest("Open deepin-terminal"):
+          machine.succeed("su - ${user.name} -c 'DISPLAY=:0 deepin-terminal >&2 &'")
+          machine.wait_for_window("deepin-terminal")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/deluge.nix b/nixpkgs/nixos/tests/deluge.nix
index 0cd1d21870ad..e8945fdea003 100644
--- a/nixpkgs/nixos/tests/deluge.nix
+++ b/nixpkgs/nixos/tests/deluge.nix
@@ -54,8 +54,10 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     declarative.wait_for_unit("deluged")
     declarative.wait_for_unit("delugeweb")
     declarative.wait_until_succeeds("curl --fail http://declarative:3142")
+
+    # deluge-console always exits with 1. https://dev.deluge-torrent.org/ticket/3291
     declarative.succeed(
-        "deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm.*Remove a torrent'"
+        "(deluge-console 'connect 127.0.0.1:58846 andrew password; help' || true) | grep -q 'rm.*Remove a torrent'"
     )
   '';
 })
diff --git a/nixpkgs/nixos/tests/dhparams.nix b/nixpkgs/nixos/tests/dhparams.nix
index a0de2911777c..021042fafdb1 100644
--- a/nixpkgs/nixos/tests/dhparams.nix
+++ b/nixpkgs/nixos/tests/dhparams.nix
@@ -1,81 +1,69 @@
-let
-  common = { pkgs, ... }: {
-    security.dhparams.enable = true;
-    environment.systemPackages = [ pkgs.openssl ];
-  };
-
-in import ./make-test-python.nix {
+import ./make-test-python.nix {
   name = "dhparams";
 
-  nodes.generation1 = { pkgs, config, ... }: {
-    imports = [ common ];
-    security.dhparams.params = {
-      # Use low values here because we don't want the test to run for ages.
-      foo.bits = 16;
-      # Also use the old format to make sure the type is coerced in the right
-      # way.
-      bar = 17;
-    };
+  nodes.machine = { pkgs, ... }: {
+    security.dhparams.enable = true;
+    environment.systemPackages = [ pkgs.openssl ];
 
-    systemd.services.foo = {
-      description = "Check systemd Ordering";
-      wantedBy = [ "multi-user.target" ];
-      unitConfig = {
-        # This is to make sure that the dhparams generation of foo occurs
-        # before this service so we need this service to start as early as
-        # possible to provoke a race condition.
-        DefaultDependencies = false;
-
-        # We check later whether the service has been started or not.
-        ConditionPathExists = config.security.dhparams.params.foo.path;
+    specialisation = {
+      gen1.configuration = { config, ... }: {
+        security.dhparams.params = {
+          # Use low values here because we don't want the test to run for ages.
+          foo.bits = 1024;
+          # Also use the old format to make sure the type is coerced in the right
+          # way.
+          bar = 1025;
+        };
+
+        systemd.services.foo = {
+          description = "Check systemd Ordering";
+          wantedBy = [ "multi-user.target" ];
+          unitConfig = {
+            # This is to make sure that the dhparams generation of foo occurs
+            # before this service so we need this service to start as early as
+            # possible to provoke a race condition.
+            DefaultDependencies = false;
+
+            # We check later whether the service has been started or not.
+            ConditionPathExists = config.security.dhparams.params.foo.path;
+          };
+          serviceConfig.Type = "oneshot";
+          serviceConfig.RemainAfterExit = true;
+          # The reason we only provide an ExecStop here is to ensure that we don't
+          # accidentally trigger an error because a file system is not yet ready
+          # during very early startup (we might not even have the Nix store
+          # available, for example if future changes in NixOS use systemd mount
+          # units to do early file system initialisation).
+          serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true";
+        };
+      };
+      gen2.configuration = {
+        security.dhparams.params.foo.bits = 1026;
+      };
+      gen3.configuration =  {};
+      gen4.configuration = {
+        security.dhparams.stateful = false;
+        security.dhparams.params.foo2.bits = 1027;
+        security.dhparams.params.bar2.bits = 1028;
+      };
+      gen5.configuration = {
+        security.dhparams.defaultBitSize = 1029;
+        security.dhparams.params.foo3 = {};
+        security.dhparams.params.bar3 = {};
       };
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
-      # The reason we only provide an ExecStop here is to ensure that we don't
-      # accidentally trigger an error because a file system is not yet ready
-      # during very early startup (we might not even have the Nix store
-      # available, for example if future changes in NixOS use systemd mount
-      # units to do early file system initialisation).
-      serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true";
     };
   };
 
-  nodes.generation2 = {
-    imports = [ common ];
-    security.dhparams.params.foo.bits = 18;
-  };
-
-  nodes.generation3 = common;
-
-  nodes.generation4 = {
-    imports = [ common ];
-    security.dhparams.stateful = false;
-    security.dhparams.params.foo2.bits = 18;
-    security.dhparams.params.bar2.bits = 19;
-  };
-
-  nodes.generation5 = {
-    imports = [ common ];
-    security.dhparams.defaultBitSize = 30;
-    security.dhparams.params.foo3 = {};
-    security.dhparams.params.bar3 = {};
-  };
-
   testScript = { nodes, ... }: let
     getParamPath = gen: name: let
-      node = "generation${toString gen}";
-    in nodes.${node}.config.security.dhparams.params.${name}.path;
+      node = "gen${toString gen}";
+    in nodes.machine.config.specialisation.${node}.configuration.security.dhparams.params.${name}.path;
 
     switchToGeneration = gen: let
-      node = "generation${toString gen}";
-      inherit (nodes.${node}.config.system.build) toplevel;
-      switchCmd = "${toplevel}/bin/switch-to-configuration test";
+      switchCmd = "${nodes.machine.config.system.build.toplevel}/specialisation/gen${toString gen}/bin/switch-to-configuration test";
     in ''
       with machine.nested("switch to generation ${toString gen}"):
-          machine.succeed(
-              "${switchCmd}"
-          )
-          machine = ${node}
+        machine.succeed("${switchCmd}")
     '';
 
   in ''
@@ -92,22 +80,20 @@ in import ./make-test-python.nix {
             if match[1] != str(bits):
                 raise Exception(f"bit size should be {bits} but it is {match[1]} instead.")
 
-
-    machine = generation1
-
     machine.wait_for_unit("multi-user.target")
+    ${switchToGeneration 1}
 
     with subtest("verify startup order"):
         machine.succeed("systemctl is-active foo.service")
 
     with subtest("check bit sizes of dhparam files"):
-        assert_param_bits("${getParamPath 1 "foo"}", 16)
-        assert_param_bits("${getParamPath 1 "bar"}", 17)
+        assert_param_bits("${getParamPath 1 "foo"}", 1024)
+        assert_param_bits("${getParamPath 1 "bar"}", 1025)
 
     ${switchToGeneration 2}
 
     with subtest("check whether bit size has changed"):
-        assert_param_bits("${getParamPath 2 "foo"}", 18)
+        assert_param_bits("${getParamPath 2 "foo"}", 1026)
 
     with subtest("ensure that dhparams file for 'bar' was deleted"):
         machine.fail("test -e ${getParamPath 1 "bar"}")
@@ -115,16 +101,16 @@ in import ./make-test-python.nix {
     ${switchToGeneration 3}
 
     with subtest("ensure that 'security.dhparams.path' has been deleted"):
-        machine.fail("test -e ${nodes.generation3.config.security.dhparams.path}")
+        machine.fail("test -e ${nodes.machine.config.specialisation.gen3.configuration.security.dhparams.path}")
 
     ${switchToGeneration 4}
 
     with subtest("check bit sizes dhparam files"):
         assert_param_bits(
-            "${getParamPath 4 "foo2"}", 18
+            "${getParamPath 4 "foo2"}", 1027
         )
         assert_param_bits(
-            "${getParamPath 4 "bar2"}", 19
+            "${getParamPath 4 "bar2"}", 1028
         )
 
     with subtest("check whether dhparam files are in the Nix store"):
@@ -136,7 +122,7 @@ in import ./make-test-python.nix {
     ${switchToGeneration 5}
 
     with subtest("check whether defaultBitSize works as intended"):
-        assert_param_bits("${getParamPath 5 "foo3"}", 30)
-        assert_param_bits("${getParamPath 5 "bar3"}", 30)
+        assert_param_bits("${getParamPath 5 "foo3"}", 1029)
+        assert_param_bits("${getParamPath 5 "bar3"}", 1029)
   '';
 }
diff --git a/nixpkgs/nixos/tests/discourse.nix b/nixpkgs/nixos/tests/discourse.nix
index 35ca083c6c4e..c79ba41c2eb9 100644
--- a/nixpkgs/nixos/tests/discourse.nix
+++ b/nixpkgs/nixos/tests/discourse.nix
@@ -40,7 +40,7 @@ import ./make-test-python.nix (
 
         networking.extraHosts = ''
           127.0.0.1 ${discourseDomain}
-          ${nodes.client.config.networking.primaryIPAddress} ${clientDomain}
+          ${nodes.client.networking.primaryIPAddress} ${clientDomain}
         '';
 
         services.postfix = {
@@ -90,7 +90,7 @@ import ./make-test-python.nix (
 
         networking.extraHosts = ''
           127.0.0.1 ${clientDomain}
-          ${nodes.discourse.config.networking.primaryIPAddress} ${discourseDomain}
+          ${nodes.discourse.networking.primaryIPAddress} ${discourseDomain}
         '';
 
         services.dovecot2 = {
@@ -178,8 +178,8 @@ import ./make-test-python.nix (
         discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}")
         discourse.succeed(
             "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token",
-            "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.config.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.config.services.discourse.admin.username}\"'",
-            "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.config.services.discourse.admin.username}",
+            "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.services.discourse.admin.username}\"'",
+            "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.services.discourse.admin.username}",
         )
 
         client.wait_for_unit("postfix.service")
diff --git a/nixpkgs/nixos/tests/dnscrypt-proxy2.nix b/nixpkgs/nixos/tests/dnscrypt-proxy2.nix
index 1ba5d983e9b9..a75a745d3553 100644
--- a/nixpkgs/nixos/tests/dnscrypt-proxy2.nix
+++ b/nixpkgs/nixos/tests/dnscrypt-proxy2.nix
@@ -1,4 +1,6 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }: let
+  localProxyPort = 43;
+in {
   name = "dnscrypt-proxy2";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ joachifm ];
@@ -9,7 +11,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     # for a caching DNS client.
     client =
     { ... }:
-    let localProxyPort = 43; in
     {
       security.apparmor.enable = true;
 
@@ -25,12 +26,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       };
 
       services.dnsmasq.enable = true;
-      services.dnsmasq.servers = [ "127.0.0.1#${toString localProxyPort}" ];
+      services.dnsmasq.settings.server = [ "127.0.0.1#${toString localProxyPort}" ];
     };
   };
 
   testScript = ''
     client.wait_for_unit("dnsmasq")
     client.wait_for_unit("dnscrypt-proxy2")
+    client.wait_until_succeeds("ss --numeric --udp --listening | grep -q ${toString localProxyPort}")
   '';
 })
diff --git a/nixpkgs/nixos/tests/doas.nix b/nixpkgs/nixos/tests/doas.nix
index 3713c728195c..2aa8b02caf57 100644
--- a/nixpkgs/nixos/tests/doas.nix
+++ b/nixpkgs/nixos/tests/doas.nix
@@ -2,9 +2,7 @@
 import ./make-test-python.nix (
   { lib, ... }: {
     name = "doas";
-    meta = with lib.maintainers; {
-      maintainers = [ cole-h ];
-    };
+    meta.maintainers = with lib.maintainers; [ cole-h ];
 
     nodes.machine =
       { ... }:
diff --git a/nixpkgs/nixos/tests/docker-tools.nix b/nixpkgs/nixos/tests/docker-tools.nix
index d76f70b791ce..44b583ebcea5 100644
--- a/nixpkgs/nixos/tests/docker-tools.nix
+++ b/nixpkgs/nixos/tests/docker-tools.nix
@@ -1,6 +1,52 @@
 # this test creates a simple GNU image with docker tools and sees if it executes
 
-import ./make-test-python.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # nixpkgs#214434: dockerTools.buildImage fails to unpack base images
+  # containing duplicate layers when those duplicate tarballs
+  # appear under the manifest's 'Layers'. Docker can generate images
+  # like this even though dockerTools does not.
+  repeatedLayerTestImage =
+    let
+      # Rootfs diffs for layers 1 and 2 are identical (and empty)
+      layer1 = pkgs.dockerTools.buildImage {  name = "empty";  };
+      layer2 = layer1.overrideAttrs (_: { fromImage = layer1; });
+      repeatedRootfsDiffs = pkgs.runCommandNoCC "image-with-links.tar" {
+        nativeBuildInputs = [pkgs.jq];
+      } ''
+        mkdir contents
+        tar -xf "${layer2}" -C contents
+        cd contents
+        first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json)
+        second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json)
+        target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar
+
+        # Replace duplicated rootfs diffs with symlinks to one tarball
+        chmod -R ug+w .
+        mv "$first_rootfs" "$target_rootfs"
+        rm "$second_rootfs"
+        ln -s "../$target_rootfs" "$first_rootfs"
+        ln -s "../$target_rootfs" "$second_rootfs"
+
+        # Update manifest's layers to use the symlinks' target
+        cat manifest.json | \
+        jq ".[0].Layers[0] = \"$target_rootfs\"" |
+        jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new
+        mv manifest.json.new manifest.json
+
+        tar --sort=name --hard-dereference -cf $out .
+        '';
+    in pkgs.dockerTools.buildImage {
+      fromImage = repeatedRootfsDiffs;
+      name = "repeated-layer-test";
+      tag = "latest";
+      copyToRoot = pkgs.bash;
+      # A runAsRoot script is required to force previous layers to be unpacked
+      runAsRoot = ''
+        echo 'runAsRoot has run.'
+      '';
+    };
+in {
   name = "docker-tools";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ lnl7 roberth ];
@@ -221,6 +267,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order"
         )
 
+    with subtest("Ensure repeated base layers handled by buildImage"):
+        docker.succeed(
+            "docker load --input='${repeatedLayerTestImage}'",
+            "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'"
+        )
+
     with subtest("Ensure environment variables are correctly inherited"):
         docker.succeed(
             "docker load --input='${examples.environmentVariables}'"
@@ -419,10 +471,84 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker rmi layered-image-with-path",
         )
 
+    with subtest("Ensure correct architecture is present in manifests."):
+        docker.succeed("""
+            docker load --input='${examples.build-image-with-architecture}'
+            docker inspect build-image-with-architecture \
+              | ${pkgs.jq}/bin/jq -er '.[] | select(.Architecture=="arm64").Architecture'
+            docker rmi build-image-with-architecture
+        """)
+        docker.succeed("""
+            ${examples.layered-image-with-architecture} | docker load
+            docker inspect layered-image-with-architecture \
+              | ${pkgs.jq}/bin/jq -er '.[] | select(.Architecture=="arm64").Architecture'
+            docker rmi layered-image-with-architecture
+        """)
+
     with subtest("etc"):
         docker.succeed("${examples.etc} | docker load")
         docker.succeed("docker run --rm etc | grep localhost")
         docker.succeed("docker image rm etc:latest")
 
+    with subtest("image-with-certs"):
+        docker.succeed("<${examples.image-with-certs} docker load")
+        docker.succeed("docker run --rm image-with-certs:latest test -r /etc/ssl/certs/ca-bundle.crt")
+        docker.succeed("docker run --rm image-with-certs:latest test -r /etc/ssl/certs/ca-certificates.crt")
+        docker.succeed("docker run --rm image-with-certs:latest test -r /etc/pki/tls/certs/ca-bundle.crt")
+        docker.succeed("docker image rm image-with-certs:latest")
+
+    with subtest("buildNixShellImage: Can build a basic derivation"):
+        docker.succeed(
+            "${examples.nix-shell-basic} | docker load",
+            "docker run --rm nix-shell-basic bash -c 'buildDerivation && $out/bin/hello' | grep '^Hello, world!$'"
+        )
+
+    with subtest("buildNixShellImage: Runs the shell hook"):
+        docker.succeed(
+            "${examples.nix-shell-hook} | docker load",
+            "docker run --rm -it nix-shell-hook | grep 'This is the shell hook!'"
+        )
+
+    with subtest("buildNixShellImage: Sources stdenv, making build inputs available"):
+        docker.succeed(
+            "${examples.nix-shell-inputs} | docker load",
+            "docker run --rm -it nix-shell-inputs | grep 'Hello, world!'"
+        )
+
+    with subtest("buildNixShellImage: passAsFile works"):
+        docker.succeed(
+            "${examples.nix-shell-pass-as-file} | docker load",
+            "docker run --rm -it nix-shell-pass-as-file | grep 'this is a string'"
+        )
+
+    with subtest("buildNixShellImage: run argument works"):
+        docker.succeed(
+            "${examples.nix-shell-run} | docker load",
+            "docker run --rm -it nix-shell-run | grep 'This shell is not interactive'"
+        )
+
+    with subtest("buildNixShellImage: command argument works"):
+        docker.succeed(
+            "${examples.nix-shell-command} | docker load",
+            "docker run --rm -it nix-shell-command | grep 'This shell is interactive'"
+        )
+
+    with subtest("buildNixShellImage: home directory is writable by default"):
+        docker.succeed(
+            "${examples.nix-shell-writable-home} | docker load",
+            "docker run --rm -it nix-shell-writable-home"
+        )
+
+    with subtest("buildNixShellImage: home directory can be made non-existent"):
+        docker.succeed(
+            "${examples.nix-shell-nonexistent-home} | docker load",
+            "docker run --rm -it nix-shell-nonexistent-home"
+        )
+
+    with subtest("buildNixShellImage: can build derivations"):
+        docker.succeed(
+            "${examples.nix-shell-build-derivation} | docker load",
+            "docker run --rm -it nix-shell-build-derivation"
+        )
   '';
 })
diff --git a/nixpkgs/nixos/tests/docker.nix b/nixpkgs/nixos/tests/docker.nix
index dee7480eb4a9..93baa198088b 100644
--- a/nixpkgs/nixos/tests/docker.nix
+++ b/nixpkgs/nixos/tests/docker.nix
@@ -11,6 +11,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       { pkgs, ... }:
         {
           virtualisation.docker.enable = true;
+          virtualisation.docker.autoPrune.enable = true;
           virtualisation.docker.package = pkgs.docker;
 
           users.users = {
diff --git a/nixpkgs/nixos/tests/doh-proxy-rust.nix b/nixpkgs/nixos/tests/doh-proxy-rust.nix
index 11ed87d23bbe..8c743fe77e32 100644
--- a/nixpkgs/nixos/tests/doh-proxy-rust.nix
+++ b/nixpkgs/nixos/tests/doh-proxy-rust.nix
@@ -1,8 +1,6 @@
 import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "doh-proxy-rust";
-  meta = with lib.maintainers; {
-    maintainers = [ stephank ];
-  };
+  meta.maintainers = with lib.maintainers; [ stephank ];
 
   nodes = {
     machine = { pkgs, lib, ... }: {
diff --git a/nixpkgs/nixos/tests/dokuwiki.nix b/nixpkgs/nixos/tests/dokuwiki.nix
index 67657e89f74c..ce3102eec780 100644
--- a/nixpkgs/nixos/tests/dokuwiki.nix
+++ b/nixpkgs/nixos/tests/dokuwiki.nix
@@ -1,86 +1,101 @@
 import ./make-test-python.nix ({ pkgs, ... }:
 
 let
-  template-bootstrap3 = pkgs.stdenv.mkDerivation {
+  template-bootstrap3 = pkgs.stdenv.mkDerivation rec {
     name = "bootstrap3";
-    # Download the theme from the dokuwiki site
-    src = pkgs.fetchurl {
-      url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip";
-      sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
+    version = "2022-07-27";
+    src = pkgs.fetchFromGitHub {
+      owner = "giterlizzi";
+      repo = "dokuwiki-template-bootstrap3";
+      rev = "v${version}";
+      hash = "sha256-B3Yd4lxdwqfCnfmZdp+i/Mzwn/aEuZ0ovagDxuR6lxo=";
     };
-    # We need unzip to build this package
-    nativeBuildInputs = [ pkgs.unzip ];
-    # Installing simply means copying all files to the output directory
     installPhase = "mkdir -p $out; cp -R * $out/";
   };
 
 
-  # Let's package the icalevents plugin
-  plugin-icalevents = pkgs.stdenv.mkDerivation {
+  plugin-icalevents = pkgs.stdenv.mkDerivation rec {
     name = "icalevents";
-    # Download the plugin from the dokuwiki site
-    src = pkgs.fetchurl {
-      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip";
-      sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
+    version = "2017-06-16";
+    src = pkgs.fetchzip {
+      stripRoot = false;
+      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/${version}/dokuwiki-plugin-icalevents-${version}.zip";
+      hash = "sha256-IPs4+qgEfe8AAWevbcCM9PnyI0uoyamtWeg4rEb+9Wc=";
     };
-    # We need unzip to build this package
-    nativeBuildInputs = [ pkgs.unzip ];
-    sourceRoot = ".";
-    # Installing simply means copying all files to the output directory
     installPhase = "mkdir -p $out; cp -R * $out/";
   };
 
+  acronymsFile = pkgs.writeText "acronyms.local.conf" ''
+    r13y  reproducibility
+  '';
+
+  dwWithAcronyms = pkgs.dokuwiki.overrideAttrs (prev: {
+    installPhase = prev.installPhase or "" + ''
+      ln -sf ${acronymsFile} $out/share/dokuwiki/conf/acronyms.local.conf
+    '';
+  });
+
+  mkNode = webserver: { ... }: {
+    services.dokuwiki = {
+      inherit webserver;
+
+      sites = {
+        "site1.local" = {
+          templates = [ template-bootstrap3 ];
+          settings = {
+            useacl = false;
+            userewrite = true;
+            template = "bootstrap3";
+          };
+        };
+        "site2.local" = {
+          package = dwWithAcronyms;
+          usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
+          plugins = [ plugin-icalevents ];
+          settings = {
+            useacl = true;
+            superuser = "admin";
+            title._file = titleFile;
+            plugin.dummy.empty = "This is just for testing purposes";
+          };
+          acl = [
+            { page = "*";
+              actor = "@ALL";
+              level = "read"; }
+            { page = "acl-test";
+              actor = "@ALL";
+              level = "none"; }
+          ];
+          pluginsConfig = {
+            authad = false;
+            authldap = false;
+            authmysql = false;
+            authpgsql = false;
+            tag = false;
+            icalevents = true;
+          };
+        };
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+    networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+  };
+
+  titleFile = pkgs.writeText "dokuwiki-title" "DokuWiki on site2";
 in {
   name = "dokuwiki";
   meta = with pkgs.lib; {
     maintainers = with maintainers; [
       _1000101
       onny
+      e1mo
     ];
   };
 
   nodes = {
-    dokuwiki_nginx = {...}: {
-      services.dokuwiki = {
-        sites = {
-          "site1.local" = {
-            aclUse = false;
-            superUser = "admin";
-          };
-          "site2.local" = {
-            usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
-            superUser = "admin";
-            templates = [ template-bootstrap3 ];
-            plugins = [ plugin-icalevents ];
-          };
-        };
-      };
-
-      networking.firewall.allowedTCPPorts = [ 80 ];
-      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
-    };
-
-    dokuwiki_caddy = {...}: {
-      services.dokuwiki = {
-        webserver = "caddy";
-        sites = {
-          "site1.local" = {
-            aclUse = false;
-            superUser = "admin";
-          };
-          "site2.local" = {
-            usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
-            superUser = "admin";
-            templates = [ template-bootstrap3 ];
-            plugins = [ plugin-icalevents ];
-          };
-        };
-      };
-
-      networking.firewall.allowedTCPPorts = [ 80 ];
-      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
-    };
-
+    dokuwiki_nginx = mkNode "nginx";
+    dokuwiki_caddy = mkNode "caddy";
   };
 
   testScript = ''
@@ -99,13 +114,48 @@ in {
         machine.succeed("curl -sSfL http://site1.local/ | grep 'DokuWiki'")
         machine.fail("curl -sSfL 'http://site1.local/doku.php?do=login' | grep 'Login'")
 
-        machine.succeed("curl -sSfL http://site2.local/ | grep 'DokuWiki'")
+        machine.succeed("curl -sSfL http://site2.local/ | grep 'DokuWiki on site2'")
         machine.succeed("curl -sSfL 'http://site2.local/doku.php?do=login' | grep 'Login'")
 
-        machine.succeed(
+        with subtest("ACL Operations"):
+          machine.succeed(
             "echo 'admin:$2y$10$ijdBQMzSVV20SrKtCna8gue36vnsbVm2wItAXvdm876sshI4uwy6S:Admin:admin@example.test:user' >> /var/lib/dokuwiki/site2.local/users.auth.php",
             "curl -sSfL -d 'u=admin&p=password' --cookie-jar cjar 'http://site2.local/doku.php?do=login'",
             "curl -sSfL --cookie cjar --cookie-jar cjar 'http://site2.local/doku.php?do=login' | grep 'Logged in as: <bdi>Admin</bdi>'",
-        )
+          )
+
+          # Ensure the generated ACL is valid
+          machine.succeed(
+            "echo 'No Hello World! for @ALL here' >> /var/lib/dokuwiki/site2.local/data/pages/acl-test.txt",
+            "curl -sSL 'http://site2.local/doku.php?id=acl-test' | grep 'Permission Denied'"
+          )
+
+        with subtest("Customizing Dokuwiki"):
+          machine.succeed(
+            "echo 'r13y is awesome!' >> /var/lib/dokuwiki/site2.local/data/pages/acronyms-test.txt",
+            "curl -sSfL 'http://site2.local/doku.php?id=acronyms-test' | grep '<abbr title=\"reproducibility\">r13y</abbr>'",
+          )
+
+          # Testing if plugins (a) be correctly loaded and (b) configuration to enable them works
+          machine.succeed(
+              "echo '~~INFO:syntaxplugins~~' >> /var/lib/dokuwiki/site2.local/data/pages/plugin-list.txt",
+              "curl -sSfL 'http://site2.local/doku.php?id=plugin-list' | grep 'plugin:icalevents'",
+              "curl -sSfL 'http://site2.local/doku.php?id=plugin-list' | (! grep 'plugin:tag')",
+          )
+
+          # Test if theme is applied and working correctly (no weird relative PHP import errors)
+          machine.succeed(
+            "curl -sSfL 'http://site1.local/doku.php' | grep 'bootstrap3/images/logo.png'",
+            "curl -sSfL 'http://site1.local/lib/exe/css.php' | grep 'bootstrap3'",
+            "curl -sSfL 'http://site1.local/lib/tpl/bootstrap3/css.php'",
+          )
+
+
+        # Just to ensure both Webserver configurations are consistent in allowing that
+        with subtest("Rewriting"):
+          machine.succeed(
+            "echo 'Hello, NixOS!' >> /var/lib/dokuwiki/site1.local/data/pages/rewrite-test.txt",
+            "curl -sSfL http://site1.local/rewrite-test | grep 'Hello, NixOS!'",
+          )
   '';
 })
diff --git a/nixpkgs/nixos/tests/dolibarr.nix b/nixpkgs/nixos/tests/dolibarr.nix
new file mode 100644
index 000000000000..2f012a0c67da
--- /dev/null
+++ b/nixpkgs/nixos/tests/dolibarr.nix
@@ -0,0 +1,59 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "dolibarr";
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
+
+  nodes.machine =
+    { ... }:
+    {
+      services.dolibarr = {
+        enable = true;
+        domain = "localhost";
+        nginx = {
+          forceSSL = false;
+          enableACME = false;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+
+  testScript = ''
+    from html.parser import HTMLParser
+    start_all()
+
+    csrf_token = None
+    class TokenParser(HTMLParser):
+      def handle_starttag(self, tag, attrs):
+        attrs = dict(attrs) # attrs is an assoc list originally
+        if tag == 'input' and attrs.get('name') == 'token':
+            csrf_token = attrs.get('value')
+            print(f'[+] Caught CSRF token: {csrf_token}')
+      def handle_endtag(self, tag): pass
+      def handle_data(self, data): pass
+
+    machine.wait_for_unit("phpfpm-dolibarr.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(80)
+    # Sanity checks on URLs.
+    # machine.succeed("curl -fL http://localhost/index.php")
+    # machine.succeed("curl -fL http://localhost/")
+    # Perform installation.
+    machine.succeed('curl -fL -X POST http://localhost/install/check.php -F selectlang=auto')
+    machine.succeed('curl -fL -X POST http://localhost/install/fileconf.php -F selectlang=auto')
+    # First time is to write the configuration file correctly.
+    machine.succeed('curl -fL -X POST http://localhost/install/step1.php -F "testpost=ok" -F "action=set" -F "selectlang=auto"')
+    # Now, we have a proper conf.php in $stateDir.
+    assert 'nixos' in machine.succeed("cat /var/lib/dolibarr/conf.php")
+    machine.succeed('curl -fL -X POST http://localhost/install/step2.php --data "testpost=ok&action=set&dolibarr_main_db_character_set=utf8&dolibarr_main_db_collation=utf8_unicode_ci&selectlang=auto"')
+    machine.succeed('curl -fL -X POST http://localhost/install/step4.php --data "testpost=ok&action=set&selectlang=auto"')
+    machine.succeed('curl -fL -X POST http://localhost/install/step5.php --data "testpost=ok&action=set&login=root&pass=hunter2&pass_verif=hunter2&selectlang=auto"')
+    # Now, we have installed the machine, let's verify we still have the right configuration.
+    assert 'nixos' in machine.succeed("cat /var/lib/dolibarr/conf.php")
+    # We do not want any redirect now as we have installed the machine.
+    machine.succeed('curl -f -X POST http://localhost')
+    # Test authentication to the webservice.
+    parser = TokenParser()
+    parser.feed(machine.succeed('curl -f -X GET http://localhost/index.php?mainmenu=login&username=root'))
+    machine.succeed(f'curl -f -X POST http://localhost/index.php?mainmenu=login&token={csrf_token}&username=root&password=hunter2')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/domination.nix b/nixpkgs/nixos/tests/domination.nix
index 09027740ab8d..409a7f3029c4 100644
--- a/nixpkgs/nixos/tests/domination.nix
+++ b/nixpkgs/nixos/tests/domination.nix
@@ -20,7 +20,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       machine.wait_for_x()
       machine.execute("domination >&2 &")
       machine.wait_for_window("Menu")
-      machine.wait_for_text("New Game")
+      machine.wait_for_text(r"(New Game|Start Server|Load Game|Help Manual|Join Game|About|Play Online)")
       machine.screenshot("screen")
     '';
 })
diff --git a/nixpkgs/nixos/tests/early-mount-options.nix b/nixpkgs/nixos/tests/early-mount-options.nix
new file mode 100644
index 000000000000..8be318ae13bc
--- /dev/null
+++ b/nixpkgs/nixos/tests/early-mount-options.nix
@@ -0,0 +1,19 @@
+# Test for https://github.com/NixOS/nixpkgs/pull/193469
+import ./make-test-python.nix {
+  name = "early-mount-options";
+
+  nodes.machine = {
+    virtualisation.fileSystems."/var" = {
+      options = [ "bind" "nosuid" "nodev" "noexec" ];
+      device = "/var";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    var_mount_info = machine.succeed("findmnt /var -n -o OPTIONS")
+    options = var_mount_info.strip().split(",")
+    assert "nosuid" in options and "nodev" in options and "noexec" in options
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ec2.nix b/nixpkgs/nixos/tests/ec2.nix
index aa3c2b7051f6..e649761d029d 100644
--- a/nixpkgs/nixos/tests/ec2.nix
+++ b/nixpkgs/nixos/tests/ec2.nix
@@ -16,8 +16,6 @@ let
       ../modules/testing/test-instrumentation.nix
       ../modules/profiles/qemu-guest.nix
       {
-        ec2.hvm = true;
-
         # Hack to make the partition resizing work in QEMU.
         boot.initrd.postDeviceCommands = mkBefore ''
           ln -s vda /dev/xvda
diff --git a/nixpkgs/nixos/tests/elk.nix b/nixpkgs/nixos/tests/elk.nix
index f42be00f23b8..0122bc440361 100644
--- a/nixpkgs/nixos/tests/elk.nix
+++ b/nixpkgs/nixos/tests/elk.nix
@@ -1,4 +1,4 @@
-# To run the test on the unfree ELK use the folllowing command:
+# To run the test on the unfree ELK use the following command:
 # cd path/to/nixpkgs
 # NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-6
 
@@ -268,14 +268,6 @@ let
     '';
   }) { 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" {
@@ -287,13 +279,6 @@ in {
   #   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;
-    };
     ELK-7 = mkElkTest "elk-7" {
       elasticsearch = pkgs.elasticsearch7;
       logstash      = pkgs.logstash7;
diff --git a/nixpkgs/nixos/tests/endlessh-go.nix b/nixpkgs/nixos/tests/endlessh-go.nix
new file mode 100644
index 000000000000..b261dbf1c560
--- /dev/null
+++ b/nixpkgs/nixos/tests/endlessh-go.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "endlessh-go";
+  meta.maintainers = with lib.maintainers; [ azahi ];
+
+  nodes = {
+    server = { ... }: {
+      services.endlessh-go = {
+        enable = true;
+        prometheus.enable = true;
+        openFirewall = true;
+      };
+
+      specialisation = {
+        unprivileged.configuration = {
+          services.endlessh-go = {
+            port = 2222;
+            prometheus.port = 9229;
+          };
+        };
+
+        privileged.configuration = {
+          services.endlessh-go = {
+            port = 22;
+            prometheus.port = 92;
+          };
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ curl netcat ];
+    };
+  };
+
+  testScript = ''
+    def activate_specialisation(name: str):
+        server.succeed(f"/run/booted-system/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+    start_all()
+
+    with subtest("Unprivileged"):
+        activate_specialisation("unprivileged")
+        server.wait_for_unit("endlessh-go.service")
+        server.wait_for_open_port(2222)
+        server.wait_for_open_port(9229)
+        client.succeed("nc -dvW5 server 2222")
+        client.succeed("curl -kv server:9229/metrics")
+
+    with subtest("Privileged"):
+        activate_specialisation("privileged")
+        server.wait_for_unit("endlessh-go.service")
+        server.wait_for_open_port(22)
+        server.wait_for_open_port(92)
+        client.succeed("nc -dvW5 server 22")
+        client.succeed("curl -kv server:92/metrics")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/endlessh.nix b/nixpkgs/nixos/tests/endlessh.nix
new file mode 100644
index 000000000000..be742a749fdd
--- /dev/null
+++ b/nixpkgs/nixos/tests/endlessh.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "endlessh";
+  meta.maintainers = with lib.maintainers; [ azahi ];
+
+  nodes = {
+    server = { ... }: {
+      services.endlessh = {
+        enable = true;
+        openFirewall = true;
+      };
+
+      specialisation = {
+        unprivileged.configuration.services.endlessh.port = 2222;
+
+        privileged.configuration.services.endlessh.port = 22;
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ curl netcat ];
+    };
+  };
+
+  testScript = ''
+    def activate_specialisation(name: str):
+        server.succeed(f"/run/booted-system/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+    start_all()
+
+    with subtest("Unprivileged"):
+        activate_specialisation("unprivileged")
+        server.wait_for_unit("endlessh.service")
+        server.wait_for_open_port(2222)
+        client.succeed("nc -dvW5 server 2222")
+
+    with subtest("Privileged"):
+        activate_specialisation("privileged")
+        server.wait_for_unit("endlessh.service")
+        server.wait_for_open_port(22)
+        client.succeed("nc -dvW5 server 22")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/enlightenment.nix b/nixpkgs/nixos/tests/enlightenment.nix
index 2e06eedd9915..bce14c1ddd5c 100644
--- a/nixpkgs/nixos/tests/enlightenment.nix
+++ b/nixpkgs/nixos/tests/enlightenment.nix
@@ -65,7 +65,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.screenshot("wizard7")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
-        machine.wait_for_text("BlusZ")  # Bluetooh Management (default)
+        machine.wait_for_text("BlusZ")  # Bluetooth Management (default)
         machine.screenshot("wizard8")
         machine.succeed("xdotool mousemove 512 740 click 1")  # Next
 
diff --git a/nixpkgs/nixos/tests/envfs.nix b/nixpkgs/nixos/tests/envfs.nix
new file mode 100644
index 000000000000..3f9cd1edb595
--- /dev/null
+++ b/nixpkgs/nixos/tests/envfs.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+  pythonShebang = pkgs.writeScript "python-shebang" ''
+    #!/usr/bin/python
+    print("OK")
+  '';
+
+  bashShebang = pkgs.writeScript "bash-shebang" ''
+    #!/usr/bin/bash
+    echo "OK"
+  '';
+in
+{
+  name = "envfs";
+  nodes.machine.services.envfs.enable = true;
+
+  testScript = ''
+    start_all()
+    machine.wait_until_succeeds("mountpoint -q /usr/bin/")
+    machine.succeed(
+        "PATH=${pkgs.coreutils}/bin /usr/bin/cp --version",
+        # check fallback paths
+        "PATH= /usr/bin/sh --version",
+        "PATH= /usr/bin/env --version",
+        "PATH= test -e /usr/bin/sh",
+        "PATH= test -e /usr/bin/env",
+        # no stat
+        "! test -e /usr/bin/cp",
+        # also picks up PATH that was set after execve
+        "! /usr/bin/hello",
+        "PATH=${pkgs.hello}/bin /usr/bin/hello",
+    )
+
+    out = machine.succeed("PATH=${pkgs.python3}/bin ${pythonShebang}")
+    print(out)
+    assert out == "OK\n"
+
+    out = machine.succeed("PATH=${pkgs.bash}/bin ${bashShebang}")
+    print(out)
+    assert out == "OK\n"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/envoy.nix b/nixpkgs/nixos/tests/envoy.nix
index 9d2c32ce102f..1e4bfe626398 100644
--- a/nixpkgs/nixos/tests/envoy.nix
+++ b/nixpkgs/nixos/tests/envoy.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
           socket_address = {
             protocol = "TCP";
             address = "127.0.0.1";
-            port_value = 9901;
+            port_value = 80;
           };
         };
       };
@@ -22,12 +22,33 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
         clusters = [];
       };
     };
+    specialisation = {
+      withoutConfigValidation.configuration = { ... }: {
+        services.envoy = {
+          requireValidConfig = false;
+          settings.admin.access_log_path = lib.mkForce "/var/log/envoy/access.log";
+        };
+      };
+    };
   };
 
-  testScript = ''
-    machine.start()
-    machine.wait_for_unit("envoy.service")
-    machine.wait_for_open_port(9901)
-    machine.wait_until_succeeds("curl -fsS localhost:9901/ready")
-  '';
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      machine.start()
+
+      with subtest("envoy.service starts and responds with ready"):
+        machine.wait_for_unit("envoy.service")
+        machine.wait_for_open_port(80)
+        machine.wait_until_succeeds("curl -fsS localhost:80/ready")
+
+      with subtest("envoy.service works with config path not available at eval time"):
+        machine.succeed('${specialisations}/withoutConfigValidation/bin/switch-to-configuration test')
+        machine.wait_for_unit("envoy.service")
+        machine.wait_for_open_port(80)
+        machine.wait_until_succeeds("curl -fsS localhost:80/ready")
+        machine.succeed('test -f /var/log/envoy/access.log')
+    '';
 })
diff --git a/nixpkgs/nixos/tests/esphome.nix b/nixpkgs/nixos/tests/esphome.nix
new file mode 100644
index 000000000000..5a318b65a723
--- /dev/null
+++ b/nixpkgs/nixos/tests/esphome.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  testPort = 6052;
+  unixSocket = "/run/esphome/esphome.sock";
+in
+{
+  name = "esphome";
+  meta.maintainers = with lib.maintainers; [ oddlama ];
+
+  nodes = {
+    esphomeTcp = { ... }:
+      {
+        services.esphome = {
+          enable = true;
+          port = testPort;
+          address = "0.0.0.0";
+          openFirewall = true;
+        };
+      };
+
+    esphomeUnix = { ... }:
+      {
+        services.esphome = {
+          enable = true;
+          enableUnixSocket = true;
+        };
+      };
+  };
+
+  testScript = ''
+    esphomeTcp.wait_for_unit("esphome.service")
+    esphomeTcp.wait_for_open_port(${toString testPort})
+    esphomeTcp.succeed("curl --fail http://localhost:${toString testPort}/")
+
+    esphomeUnix.wait_for_unit("esphome.service")
+    esphomeUnix.wait_for_file("${unixSocket}")
+    esphomeUnix.succeed("curl --fail --unix-socket ${unixSocket} http://localhost/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/etcd-cluster.nix b/nixpkgs/nixos/tests/etcd-cluster.nix
index 410cb654794f..c77c0dd73c25 100644
--- a/nixpkgs/nixos/tests/etcd-cluster.nix
+++ b/nixpkgs/nixos/tests/etcd-cluster.nix
@@ -53,7 +53,7 @@ import ./make-test-python.nix ({ pkgs, ... } : let
     [ v3_req ]
     basicConstraints = CA:FALSE
     keyUsage = digitalSignature, keyEncipherment
-    extendedKeyUsage = serverAuth
+    extendedKeyUsage = serverAuth, clientAuth
     subjectAltName = @alt_names
     [alt_names]
     DNS.1 = node1
@@ -79,23 +79,26 @@ import ./make-test-python.nix ({ pkgs, ... } : let
         keyFile = etcd_key;
         certFile = etcd_cert;
         trustedCaFile = ca_pem;
-        peerClientCertAuth = true;
+        clientCertAuth = true;
         listenClientUrls = ["https://127.0.0.1:2379"];
         listenPeerUrls = ["https://0.0.0.0:2380"];
       };
     };
 
     environment.variables = {
-      ETCDCTL_CERT_FILE = "${etcd_client_cert}";
-      ETCDCTL_KEY_FILE = "${etcd_client_key}";
-      ETCDCTL_CA_FILE = "${ca_pem}";
-      ETCDCTL_PEERS = "https://127.0.0.1:2379";
+      ETCD_CERT_FILE = "${etcd_client_cert}";
+      ETCD_KEY_FILE = "${etcd_client_key}";
+      ETCD_CA_FILE = "${ca_pem}";
+      ETCDCTL_ENDPOINTS = "https://127.0.0.1:2379";
+      ETCDCTL_CACERT = "${ca_pem}";
+      ETCDCTL_CERT = "${etcd_cert}";
+      ETCDCTL_KEY = "${etcd_key}";
     };
 
     networking.firewall.allowedTCPPorts = [ 2380 ];
   };
 in {
-  name = "etcd";
+  name = "etcd-cluster";
 
   meta = with pkgs.lib.maintainers; {
     maintainers = [ offline ];
@@ -134,21 +137,21 @@ in {
         node2.start()
         node1.wait_for_unit("etcd.service")
         node2.wait_for_unit("etcd.service")
-        node2.wait_until_succeeds("etcdctl cluster-health")
-        node1.succeed("etcdctl set /foo/bar 'Hello world'")
+        node2.wait_until_succeeds("etcdctl endpoint status")
+        node1.succeed("etcdctl put /foo/bar 'Hello world'")
         node2.succeed("etcdctl get /foo/bar | grep 'Hello world'")
 
     with subtest("should add another member"):
-        node1.wait_until_succeeds("etcdctl member add node3 https://node3:2380")
+        node1.wait_until_succeeds("etcdctl member add node3 --peer-urls=https://node3:2380")
         node3.start()
         node3.wait_for_unit("etcd.service")
         node3.wait_until_succeeds("etcdctl member list | grep 'node3'")
-        node3.succeed("etcdctl cluster-health")
+        node3.succeed("etcdctl endpoint status")
 
     with subtest("should survive member crash"):
         node3.crash()
-        node1.succeed("etcdctl cluster-health")
-        node1.succeed("etcdctl set /foo/bar 'Hello degraded world'")
+        node1.succeed("etcdctl endpoint status")
+        node1.succeed("etcdctl put /foo/bar 'Hello degraded world'")
         node1.succeed("etcdctl get /foo/bar | grep 'Hello degraded world'")
   '';
 })
diff --git a/nixpkgs/nixos/tests/etcd.nix b/nixpkgs/nixos/tests/etcd.nix
index 702bbb668f57..79857778ae1b 100644
--- a/nixpkgs/nixos/tests/etcd.nix
+++ b/nixpkgs/nixos/tests/etcd.nix
@@ -19,7 +19,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
         node.wait_for_unit("etcd.service")
 
     with subtest("should write and read some values to etcd"):
-        node.succeed("etcdctl set /foo/bar 'Hello world'")
+        node.succeed("etcdctl put /foo/bar 'Hello world'")
         node.succeed("etcdctl get /foo/bar | grep 'Hello world'")
   '';
 })
diff --git a/nixpkgs/nixos/tests/evcc.nix b/nixpkgs/nixos/tests/evcc.nix
new file mode 100644
index 000000000000..b445735ede98
--- /dev/null
+++ b/nixpkgs/nixos/tests/evcc.nix
@@ -0,0 +1,96 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "evcc";
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.evcc = {
+        enable = true;
+        settings = {
+          network = {
+            schema = "http";
+            host = "localhost";
+            port = 7070;
+          };
+
+          log = "info";
+
+          site = {
+            title = "NixOS Test";
+            meters = {
+              grid = "grid";
+              pv = "pv";
+            };
+          };
+
+          meters = [ {
+            type = "custom";
+            name = "grid";
+            power = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo -4500'";
+            };
+          } {
+            type = "custom";
+            name = "pv";
+            power = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo 7500'";
+            };
+          } ];
+
+          chargers = [ {
+            name = "dummy-charger";
+            type = "custom";
+            status = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo charger status F'";
+            };
+            enabled = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo charger enabled state false'";
+            };
+            enable = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo set charger enabled state true'";
+            };
+            maxcurrent = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo set charger max current 7200'";
+            };
+          } ];
+
+          loadpoints = [ {
+            title = "Dummy";
+            charger = "dummy-charger";
+          } ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("evcc.service")
+    machine.wait_for_open_port(7070)
+
+    with subtest("Check package version propagates into frontend"):
+        machine.fail(
+            "curl --fail http://localhost:7070 | grep '0.0.1-alpha'"
+        )
+        machine.succeed(
+            "curl --fail http://localhost:7070 | grep '${pkgs.evcc.version}'"
+        )
+
+    with subtest("Check journal for errors"):
+        _, output = machine.execute("journalctl -o cat -u evcc.service")
+        assert "FATAL" not in output
+
+    with subtest("Check systemd hardening"):
+        _, output = machine.execute("systemd-analyze security evcc.service | grep -v '✓'")
+        machine.log(output)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fcitx/config b/nixpkgs/nixos/tests/fcitx/config
deleted file mode 100644
index 169768994e28..000000000000
--- a/nixpkgs/nixos/tests/fcitx/config
+++ /dev/null
@@ -1,12 +0,0 @@
-[Hotkey]
-SwitchKey=Disabled
-IMSwitchHotkey=ALT_SHIFT
-TimeInterval=240
-
-[Program]
-DelayStart=5
-
-[Output]
-
-[Appearance]
-
diff --git a/nixpkgs/nixos/tests/fcitx/default.nix b/nixpkgs/nixos/tests/fcitx/default.nix
deleted file mode 100644
index c132249fcb24..000000000000
--- a/nixpkgs/nixos/tests/fcitx/default.nix
+++ /dev/null
@@ -1,142 +0,0 @@
-import ../make-test-python.nix (
-  {
-    pkgs, ...
-  }:
-    # copy_from_host works only for store paths
-    rec {
-        name = "fcitx";
-        meta.broken = true; # takes hours to time out since October 2021
-        nodes.machine =
-        {
-          pkgs,
-          ...
-        }:
-          {
-
-            imports = [
-              ../common/user-account.nix
-            ];
-
-            environment.systemPackages = [
-              # To avoid clashing with xfce4-terminal
-              pkgs.alacritty
-            ];
-
-
-            services.xserver =
-            {
-              enable = true;
-
-              displayManager = {
-                lightdm.enable = true;
-                autoLogin = {
-                  enable = true;
-                  user = "alice";
-                };
-              };
-
-              desktopManager.xfce.enable = true;
-            };
-
-            i18n = {
-              inputMethod = {
-                enabled = "fcitx";
-                fcitx.engines = [
-                  pkgs.fcitx-engines.m17n
-                  pkgs.fcitx-engines.table-extra
-                ];
-              };
-            };
-          }
-        ;
-
-        testScript = { nodes, ... }:
-        let
-            user = nodes.machine.config.users.users.alice;
-            userName      = user.name;
-            userHome      = user.home;
-            xauth         = "${userHome}/.Xauthority";
-            fcitx_confdir = "${userHome}/.config/fcitx";
-        in
-        ''
-            # We need config files before login session
-            # So copy first thing
-
-            # Point and click would be expensive,
-            # So configure using files
-            machine.copy_from_host(
-                "${./profile}",
-                "${fcitx_confdir}/profile",
-            )
-            machine.copy_from_host(
-                "${./config}",
-                "${fcitx_confdir}/config",
-            )
-
-            start_all()
-
-            machine.wait_for_file("${xauth}")
-            machine.succeed("xauth merge ${xauth}")
-
-            machine.sleep(5)
-
-            machine.succeed("su - ${userName} -c 'alacritty&'")
-            machine.succeed("su - ${userName} -c 'fcitx&'")
-            machine.sleep(10)
-
-            ### Type on terminal
-            machine.send_chars("echo ")
-            machine.sleep(1)
-
-            ### Start fcitx Unicode input
-            machine.send_key("ctrl-alt-shift-u")
-            machine.sleep(5)
-            machine.sleep(1)
-
-            ### Search for smiling face
-            machine.send_chars("smil")
-            machine.sleep(1)
-
-            ### Navigate to the second one
-            machine.send_key("tab")
-            machine.sleep(1)
-
-            ### Choose it
-            machine.send_key("\n")
-            machine.sleep(1)
-
-            ### Start fcitx language input
-            machine.send_key("ctrl-spc")
-            machine.sleep(1)
-
-            ### Default zhengma, enter 一下
-            machine.send_chars("a2")
-            machine.sleep(1)
-
-            ### Switch to Harvard Kyoto
-            machine.send_key("alt-shift")
-            machine.sleep(1)
-
-            ### Enter क
-            machine.send_chars("ka ")
-            machine.sleep(1)
-
-            machine.send_key("alt-shift")
-            machine.sleep(1)
-
-            ### Turn off Fcitx
-            machine.send_key("ctrl-spc")
-            machine.sleep(1)
-
-            ### Redirect typed characters to a file
-            machine.send_chars(" > fcitx_test.out\n")
-            machine.sleep(1)
-            machine.screenshot("terminal_chars")
-
-            ### Verify that file contents are as expected
-            file_content = machine.succeed("cat ${userHome}/fcitx_test.out")
-            assert file_content == "☺一下क\n"
-            ''
-    ;
-  }
-)
diff --git a/nixpkgs/nixos/tests/fcitx/profile b/nixpkgs/nixos/tests/fcitx/profile
deleted file mode 100644
index 77497a1496bd..000000000000
--- a/nixpkgs/nixos/tests/fcitx/profile
+++ /dev/null
@@ -1,4 +0,0 @@
-[Profile]
-IMName=zhengma-large
-EnabledIMList=fcitx-keyboard-us:True,zhengma-large:True,m17n_sa_harvard-kyoto:True
-PreeditStringInClientWindow=False
diff --git a/nixpkgs/nixos/tests/fcitx5/config b/nixpkgs/nixos/tests/fcitx5/config
new file mode 100644
index 000000000000..cf4334639f1c
--- /dev/null
+++ b/nixpkgs/nixos/tests/fcitx5/config
@@ -0,0 +1,11 @@
+[Hotkey]
+EnumerateSkipFirst=False
+
+[Hotkey/TriggerKeys]
+0=Control+space
+
+[Hotkey/EnumerateForwardKeys]
+0=Alt+Shift_L
+
+[Hotkey/EnumerateBackwardKeys]
+0=Alt+Shift_R
diff --git a/nixpkgs/nixos/tests/fcitx5/default.nix b/nixpkgs/nixos/tests/fcitx5/default.nix
new file mode 100644
index 000000000000..9b000da48eaf
--- /dev/null
+++ b/nixpkgs/nixos/tests/fcitx5/default.nix
@@ -0,0 +1,138 @@
+import ../make-test-python.nix ({ lib, ... }:
+rec {
+  name = "fcitx5";
+  meta.maintainers = with lib.maintainers; [ nevivurn ];
+
+  nodes.machine = { pkgs, ... }:
+  {
+    imports = [
+      ../common/user-account.nix
+    ];
+
+    environment.systemPackages = [
+      # To avoid clashing with xfce4-terminal
+      pkgs.alacritty
+    ];
+
+    services.xserver = {
+      enable = true;
+
+      displayManager = {
+        lightdm.enable = true;
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      desktopManager.xfce.enable = true;
+    };
+
+    i18n.inputMethod = {
+      enabled = "fcitx5";
+      fcitx5.addons = [
+        pkgs.fcitx5-chinese-addons
+        pkgs.fcitx5-hangul
+        pkgs.fcitx5-m17n
+        pkgs.fcitx5-mozc
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      xauth         = "${user.home}/.Xauthority";
+      fcitx_confdir = "${user.home}/.config/fcitx5";
+    in
+      ''
+            start_all()
+
+            machine.wait_for_x()
+            machine.wait_for_file("${xauth}")
+            machine.succeed("xauth merge ${xauth}")
+            machine.sleep(5)
+
+            machine.succeed("su - ${user.name} -c 'kill $(pgrep fcitx5)'")
+            machine.sleep(1)
+
+            machine.copy_from_host(
+                "${./profile}",
+                "${fcitx_confdir}/profile",
+            )
+            machine.copy_from_host(
+                "${./config}",
+                "${fcitx_confdir}/config",
+            )
+
+            machine.succeed("su - ${user.name} -c 'alacritty >&2 &'")
+            machine.succeed("su - ${user.name} -c 'fcitx5 >&2 &'")
+            machine.sleep(10)
+
+            ### Type on terminal
+            machine.send_chars("echo ")
+            machine.sleep(1)
+
+            ### Start fcitx Unicode input
+            machine.send_key("ctrl-alt-shift-u")
+            machine.sleep(1)
+
+            ### Search for smiling face
+            machine.send_chars("smil")
+            machine.sleep(1)
+
+            ### Navigate to the second one
+            machine.send_key("tab")
+            machine.sleep(1)
+
+            ### Choose it
+            machine.send_key("\n")
+            machine.sleep(1)
+
+            ### Start fcitx language input
+            machine.send_key("ctrl-spc")
+            machine.sleep(1)
+
+            ### Default wubi, enter 一下
+            machine.send_chars("gggh ")
+            machine.sleep(1)
+
+            ### Switch to Hangul
+            machine.send_key("alt-shift")
+            machine.sleep(1)
+
+            ### Enter 한
+            machine.send_chars("gks")
+            machine.sleep(1)
+
+            ### Switch to Harvard Kyoto
+            machine.send_key("alt-shift")
+            machine.sleep(1)
+
+            ### Enter क
+            machine.send_chars("ka")
+            machine.sleep(1)
+
+            ### Switch to Mozc
+            machine.send_key("alt-shift")
+            machine.sleep(1)
+
+            ### Enter か
+            machine.send_chars("ka\n")
+            machine.sleep(1)
+
+            ### Turn off Fcitx
+            machine.send_key("ctrl-spc")
+            machine.sleep(1)
+
+            ### Redirect typed characters to a file
+            machine.send_chars(" > fcitx_test.out\n")
+            machine.sleep(1)
+            machine.screenshot("terminal_chars")
+
+            ### Verify that file contents are as expected
+            file_content = machine.succeed("cat ${user.home}/fcitx_test.out")
+            assert file_content == "☺一下한कか\n"
+      ''
+  ;
+})
diff --git a/nixpkgs/nixos/tests/fcitx5/profile b/nixpkgs/nixos/tests/fcitx5/profile
new file mode 100644
index 000000000000..1b48c634e0eb
--- /dev/null
+++ b/nixpkgs/nixos/tests/fcitx5/profile
@@ -0,0 +1,27 @@
+[Groups/0]
+Name=NixOS_test
+Default Layout=us
+DefaultIM=wbx
+
+[Groups/0/Items/0]
+Name=keyboard-us
+Layout=
+
+[Groups/0/Items/1]
+Name=wbx
+Layout=us
+
+[Groups/0/Items/2]
+Name=hangul
+Layout=us
+
+[Groups/0/Items/3]
+Name=m17n_sa_harvard-kyoto
+Layout=us
+
+[Groups/0/Items/4]
+Name=mozc
+Layout=us
+
+[GroupOrder]
+0=NixOS_test
diff --git a/nixpkgs/nixos/tests/firewall.nix b/nixpkgs/nixos/tests/firewall.nix
index 5c434c1cb6d6..dd7551f143a5 100644
--- a/nixpkgs/nixos/tests/firewall.nix
+++ b/nixpkgs/nixos/tests/firewall.nix
@@ -1,7 +1,7 @@
 # Test the firewall module.
 
-import ./make-test-python.nix ( { pkgs, ... } : {
-  name = "firewall";
+import ./make-test-python.nix ( { pkgs, nftables, ... } : {
+  name = "firewall" + pkgs.lib.optionalString nftables "-nftables";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ eelco ];
   };
@@ -11,6 +11,7 @@ import ./make-test-python.nix ( { pkgs, ... } : {
         { ... }:
         { networking.firewall.enable = true;
           networking.firewall.logRefusedPackets = true;
+          networking.nftables.enable = nftables;
           services.httpd.enable = true;
           services.httpd.adminAddr = "foo@example.org";
         };
@@ -23,6 +24,7 @@ import ./make-test-python.nix ( { pkgs, ... } : {
         { ... }:
         { networking.firewall.enable = true;
           networking.firewall.rejectPackets = true;
+          networking.nftables.enable = nftables;
         };
 
       attacker =
@@ -35,10 +37,11 @@ import ./make-test-python.nix ( { pkgs, ... } : {
 
   testScript = { nodes, ... }: let
     newSystem = nodes.walled2.config.system.build.toplevel;
+    unit = if nftables then "nftables" else "firewall";
   in ''
     start_all()
 
-    walled.wait_for_unit("firewall")
+    walled.wait_for_unit("${unit}")
     walled.wait_for_unit("httpd")
     attacker.wait_for_unit("network.target")
 
@@ -54,12 +57,12 @@ import ./make-test-python.nix ( { pkgs, ... } : {
     walled.succeed("ping -c 1 attacker >&2")
 
     # If we stop the firewall, then connections should succeed.
-    walled.stop_job("firewall")
+    walled.stop_job("${unit}")
     attacker.succeed("curl -v http://walled/ >&2")
 
     # Check whether activation of a new configuration reloads the firewall.
     walled.succeed(
-        "${newSystem}/bin/switch-to-configuration test 2>&1 | grep -qF firewall.service"
+        "${newSystem}/bin/switch-to-configuration test 2>&1 | grep -qF ${unit}.service"
     )
   '';
 })
diff --git a/nixpkgs/nixos/tests/fluidd.nix b/nixpkgs/nixos/tests/fluidd.nix
index f49a4110d714..82a2c1e4049f 100644
--- a/nixpkgs/nixos/tests/fluidd.nix
+++ b/nixpkgs/nixos/tests/fluidd.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "fluidd";
-  meta.maintainers = with maintainers; [ vtuan10 ];
+  meta.maintainers = with lib.maintainers; [ vtuan10 ];
 
   nodes.machine = { pkgs, ... }: {
     services.fluidd = {
diff --git a/nixpkgs/nixos/tests/freenet.nix b/nixpkgs/nixos/tests/freenet.nix
new file mode 100644
index 000000000000..96dbb4caa129
--- /dev/null
+++ b/nixpkgs/nixos/tests/freenet.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "freenet";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nagy ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.freenet.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("freenet.service")
+    machine.wait_for_open_port(8888)
+    machine.wait_until_succeeds("curl -sfL http://localhost:8888/ | grep Freenet")
+    machine.succeed("systemctl stop freenet")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/freshrss-pgsql.nix b/nixpkgs/nixos/tests/freshrss-pgsql.nix
new file mode 100644
index 000000000000..055bd51ed43d
--- /dev/null
+++ b/nixpkgs/nixos/tests/freshrss-pgsql.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "freshrss";
+  meta.maintainers = with lib.maintainers; [ etu stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.freshrss = {
+      enable = true;
+      baseUrl = "http://localhost";
+      passwordFile = pkgs.writeText "password" "secret";
+      dataDir = "/srv/freshrss";
+      database = {
+        type = "pgsql";
+        port = 5432;
+        user = "freshrss";
+        passFile = pkgs.writeText "db-password" "db-secret";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "freshrss" ];
+      ensureUsers = [
+        {
+          name = "freshrss";
+          ensurePermissions = {
+            "DATABASE freshrss" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+      initialScript = pkgs.writeText "postgresql-password" ''
+        CREATE ROLE freshrss WITH LOGIN PASSWORD 'db-secret' CREATEDB;
+      '';
+    };
+
+    systemd.services."freshrss-config" = {
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(5432)
+    machine.wait_for_open_port(80)
+    response = machine.succeed("curl -vvv -s -H 'Host: freshrss' http://127.0.0.1:80/i/")
+    assert '<title>Login · FreshRSS</title>' in response, "Login page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/freshrss-sqlite.nix b/nixpkgs/nixos/tests/freshrss-sqlite.nix
new file mode 100644
index 000000000000..b821c98a7e7a
--- /dev/null
+++ b/nixpkgs/nixos/tests/freshrss-sqlite.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "freshrss";
+  meta.maintainers = with lib.maintainers; [ etu stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.freshrss = {
+      enable = true;
+      baseUrl = "http://localhost";
+      passwordFile = pkgs.writeText "password" "secret";
+      dataDir = "/srv/freshrss";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(80)
+    response = machine.succeed("curl -vvv -s -H 'Host: freshrss' http://127.0.0.1:80/i/")
+    assert '<title>Login · FreshRSS</title>' in response, "Login page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/frigate.nix b/nixpkgs/nixos/tests/frigate.nix
new file mode 100644
index 000000000000..836fe0d063f8
--- /dev/null
+++ b/nixpkgs/nixos/tests/frigate.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "frigate";
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.frigate = {
+        enable = true;
+
+        hostname = "localhost";
+
+        settings = {
+          mqtt.enabled = false;
+
+          cameras.test = {
+            ffmpeg = {
+              input_args = "-fflags nobuffer -strict experimental -fflags +genpts+discardcorrupt -r 10 -use_wallclock_as_timestamps 1";
+              inputs = [ {
+                path = "http://127.0.0.1:8080";
+                roles = [
+                  "record"
+                ];
+              } ];
+            };
+          };
+
+          record.enabled = true;
+        };
+      };
+
+      systemd.services.video-stream = {
+        description = "Start a test stream that frigate can capture";
+        before = [
+          "frigate.service"
+        ];
+        wantedBy = [
+          "multi-user.target"
+        ];
+        serviceConfig = {
+          DynamicUser = true;
+          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -f mpegts -listen 1 http://0.0.0.0:8080";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("frigate.service")
+
+    machine.wait_for_open_port(5001)
+
+    machine.succeed("curl http://localhost:5001")
+
+    machine.wait_for_file("/var/cache/frigate/test-*.mp4")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fsck.nix b/nixpkgs/nixos/tests/fsck.nix
index 5b8b09f433a2..ec6bfa69ae84 100644
--- a/nixpkgs/nixos/tests/fsck.nix
+++ b/nixpkgs/nixos/tests/fsck.nix
@@ -1,3 +1,9 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
 import ./make-test-python.nix {
   name = "fsck";
 
@@ -11,16 +17,20 @@ import ./make-test-python.nix {
         autoFormat = true;
       };
     };
+
+    boot.initrd.systemd.enable = systemdStage1;
   };
 
   testScript = ''
     machine.wait_for_unit("default.target")
 
     with subtest("root fs is fsckd"):
-        machine.succeed("journalctl -b | grep 'fsck.ext4.*/dev/vda'")
+        machine.succeed("journalctl -b | grep '${if systemdStage1
+          then "fsck.*vda.*clean"
+          else "fsck.ext4.*/dev/vda"}'")
 
     with subtest("mnt fs is fsckd"):
-        machine.succeed("journalctl -b | grep 'fsck.*/dev/vdb.*clean'")
+        machine.succeed("journalctl -b | grep 'fsck.*vdb.*clean'")
         machine.succeed(
             "grep 'Requires=systemd-fsck@dev-vdb.service' /run/systemd/generator/mnt.mount"
         )
diff --git a/nixpkgs/nixos/tests/fscrypt.nix b/nixpkgs/nixos/tests/fscrypt.nix
new file mode 100644
index 000000000000..03367979359b
--- /dev/null
+++ b/nixpkgs/nixos/tests/fscrypt.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ ... }:
+{
+  name = "fscrypt";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    security.pam.enableFscrypt = true;
+  };
+
+  testScript = ''
+    def login_as_alice():
+        machine.wait_until_tty_matches("1", "login: ")
+        machine.send_chars("alice\n")
+        machine.wait_until_tty_matches("1", "Password: ")
+        machine.send_chars("foobar\n")
+        machine.wait_until_tty_matches("1", "alice\@machine")
+
+
+    def logout():
+        machine.send_chars("logout\n")
+        machine.wait_until_tty_matches("1", "login: ")
+
+
+    machine.wait_for_unit("default.target")
+
+    with subtest("Enable fscrypt on filesystem"):
+        machine.succeed("tune2fs -O encrypt /dev/vda")
+        machine.succeed("fscrypt setup --quiet --force --time=1ms")
+
+    with subtest("Set up alice with an fscrypt-enabled home directory"):
+        machine.succeed("(echo foobar; echo foobar) | passwd alice")
+        machine.succeed("chown -R alice.users ~alice")
+        machine.succeed("echo foobar | fscrypt encrypt --skip-unlock --source=pam_passphrase --user=alice /home/alice")
+
+    with subtest("Create file as alice"):
+      login_as_alice()
+      machine.succeed("echo hello > /home/alice/world")
+      logout()
+      # Wait for logout to be processed
+      machine.sleep(1)
+
+    with subtest("File should not be readable without being logged in as alice"):
+      machine.fail("cat /home/alice/world")
+
+    with subtest("File should be readable again as alice"):
+      login_as_alice()
+      machine.succeed("cat /home/alice/world")
+      logout()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ft2-clone.nix b/nixpkgs/nixos/tests/ft2-clone.nix
index 3c90b3d3fa20..a8395d4ebaa6 100644
--- a/nixpkgs/nixos/tests/ft2-clone.nix
+++ b/nixpkgs/nixos/tests/ft2-clone.nix
@@ -26,9 +26,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
       machine.wait_for_window(r"Fasttracker")
       machine.sleep(5)
-      # One of the few words that actually get recognized
-      if "Songlen" not in machine.get_screen_text():
-          raise Exception("Program did not start successfully")
+      machine.wait_for_text(r"(Songlen|Repstart|Time|About|Nibbles|Help)")
       machine.screenshot("screen")
     '';
 })
diff --git a/nixpkgs/nixos/tests/garage/basic.nix b/nixpkgs/nixos/tests/garage/basic.nix
new file mode 100644
index 000000000000..b6df1e72af98
--- /dev/null
+++ b/nixpkgs/nixos/tests/garage/basic.nix
@@ -0,0 +1,98 @@
+args@{ mkNode, ... }:
+(import ../make-test-python.nix ({ pkgs, ...} : {
+  name = "garage-basic";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  nodes = {
+    single_node = mkNode { replicationMode = "none"; };
+  };
+
+  testScript = ''
+    from typing import List
+    from dataclasses import dataclass
+    import re
+
+    start_all()
+
+    cur_version_regex = re.compile('Current cluster layout version: (?P<ver>\d*)')
+    key_creation_regex = re.compile('Key name: (?P<key_name>.*)\nKey ID: (?P<key_id>.*)\nSecret key: (?P<secret_key>.*)')
+
+    @dataclass
+    class S3Key:
+       key_name: str
+       key_id: str
+       secret_key: str
+
+    @dataclass
+    class GarageNode:
+       node_id: str
+       host: str
+
+    def get_node_fqn(machine: Machine) -> GarageNode:
+      node_id, host = machine.succeed("garage node id").split('@')
+      return GarageNode(node_id=node_id, host=host)
+
+    def get_node_id(machine: Machine) -> str:
+      return get_node_fqn(machine).node_id
+
+    def get_layout_version(machine: Machine) -> int:
+      version_data = machine.succeed("garage layout show")
+      m = cur_version_regex.search(version_data)
+      if m and m.group('ver') is not None:
+        return int(m.group('ver')) + 1
+      else:
+        raise ValueError('Cannot find current layout version')
+
+    def apply_garage_layout(machine: Machine, layouts: List[str]):
+       for layout in layouts:
+          machine.succeed(f"garage layout assign {layout}")
+       version = get_layout_version(machine)
+       machine.succeed(f"garage layout apply --version {version}")
+
+    def create_api_key(machine: Machine, key_name: str) -> S3Key:
+       output = machine.succeed(f"garage key new --name {key_name}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_id') or not m.group('secret_key'):
+          raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=key_name, key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def get_api_key(machine: Machine, key_pattern: str) -> S3Key:
+       output = machine.succeed(f"garage key info {key_pattern}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_name') or not m.group('key_id') or not m.group('secret_key'):
+           raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=m.group('key_name'), key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def test_bucket_writes(node):
+      node.succeed("garage bucket create test-bucket")
+      s3_key = create_api_key(node, "test-api-key")
+      node.succeed("garage bucket allow --read --write test-bucket --key test-api-key")
+      other_s3_key = get_api_key(node, 'test-api-key')
+      assert other_s3_key.secret_key == other_s3_key.secret_key
+      node.succeed(
+        f"mc alias set test-garage http://[::1]:3900 {s3_key.key_id} {s3_key.secret_key} --api S3v4"
+      )
+      node.succeed("echo test | mc pipe test-garage/test-bucket/test.txt")
+      assert node.succeed("mc cat test-garage/test-bucket/test.txt").strip() == "test"
+
+    def test_bucket_over_http(node, bucket='test-bucket', url=None):
+      if url is None:
+         url = f"{bucket}.web.garage"
+
+      node.succeed(f'garage bucket website --allow {bucket}')
+      node.succeed(f'echo hello world | mc pipe test-garage/{bucket}/index.html')
+      assert (node.succeed(f"curl -H 'Host: {url}' http://localhost:3902")).strip() == 'hello world'
+
+    with subtest("Garage works as a single-node S3 storage"):
+      single_node.wait_for_unit("garage.service")
+      single_node.wait_for_open_port(3900)
+      # Now Garage is initialized.
+      single_node_id = get_node_id(single_node)
+      apply_garage_layout(single_node, [f'-z qemutest -c 1 "{single_node_id}"'])
+      # Now Garage is operational.
+      test_bucket_writes(single_node)
+      test_bucket_over_http(single_node)
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/garage/default.nix b/nixpkgs/nixos/tests/garage/default.nix
new file mode 100644
index 000000000000..0a1ccde056b2
--- /dev/null
+++ b/nixpkgs/nixos/tests/garage/default.nix
@@ -0,0 +1,53 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+with pkgs.lib;
+
+let
+    mkNode = package: { replicationMode, publicV6Address ? "::1" }: { pkgs, ... }: {
+      networking.interfaces.eth1.ipv6.addresses = [{
+        address = publicV6Address;
+        prefixLength = 64;
+      }];
+
+      networking.firewall.allowedTCPPorts = [ 3901 3902 ];
+
+      services.garage = {
+        enable = true;
+        inherit package;
+        settings = {
+          replication_mode = replicationMode;
+
+          rpc_bind_addr = "[::]:3901";
+          rpc_public_addr = "[${publicV6Address}]:3901";
+          rpc_secret = "5c1915fa04d0b6739675c61bf5907eb0fe3d9c69850c83820f51b4d25d13868c";
+
+          s3_api = {
+            s3_region = "garage";
+            api_bind_addr = "[::]:3900";
+            root_domain = ".s3.garage";
+          };
+
+          s3_web = {
+            bind_addr = "[::]:3902";
+            root_domain = ".web.garage";
+            index = "index.html";
+          };
+        };
+      };
+      environment.systemPackages = [ pkgs.minio-client ];
+
+      # Garage requires at least 1GiB of free disk space to run.
+      virtualisation.diskSize = 2 * 1024;
+    };
+in
+  foldl
+  (matrix: ver: matrix // {
+    "basic${toString ver}" = import ./basic.nix { inherit system pkgs; mkNode = mkNode pkgs."garage_${ver}"; };
+    "with-3node-replication${toString ver}" = import ./with-3node-replication.nix { inherit system pkgs; mkNode = mkNode pkgs."garage_${ver}"; };
+  })
+  {}
+  [
+    "0_8"
+  ]
diff --git a/nixpkgs/nixos/tests/garage/with-3node-replication.nix b/nixpkgs/nixos/tests/garage/with-3node-replication.nix
new file mode 100644
index 000000000000..d372ad1aa000
--- /dev/null
+++ b/nixpkgs/nixos/tests/garage/with-3node-replication.nix
@@ -0,0 +1,121 @@
+args@{ mkNode, ... }:
+(import ../make-test-python.nix ({ pkgs, ...} :
+{
+  name = "garage-3node-replication";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  nodes = {
+    node1 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::1"; };
+    node2 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::2"; };
+    node3 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::3"; };
+    node4 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::4"; };
+  };
+
+  testScript = ''
+    from typing import List
+    from dataclasses import dataclass
+    import re
+    start_all()
+
+    cur_version_regex = re.compile('Current cluster layout version: (?P<ver>\d*)')
+    key_creation_regex = re.compile('Key name: (?P<key_name>.*)\nKey ID: (?P<key_id>.*)\nSecret key: (?P<secret_key>.*)')
+
+    @dataclass
+    class S3Key:
+       key_name: str
+       key_id: str
+       secret_key: str
+
+    @dataclass
+    class GarageNode:
+       node_id: str
+       host: str
+
+    def get_node_fqn(machine: Machine) -> GarageNode:
+      node_id, host = machine.succeed("garage node id").split('@')
+      return GarageNode(node_id=node_id, host=host)
+
+    def get_node_id(machine: Machine) -> str:
+      return get_node_fqn(machine).node_id
+
+    def get_layout_version(machine: Machine) -> int:
+      version_data = machine.succeed("garage layout show")
+      m = cur_version_regex.search(version_data)
+      if m and m.group('ver') is not None:
+        return int(m.group('ver')) + 1
+      else:
+        raise ValueError('Cannot find current layout version')
+
+    def apply_garage_layout(machine: Machine, layouts: List[str]):
+       for layout in layouts:
+          machine.succeed(f"garage layout assign {layout}")
+       version = get_layout_version(machine)
+       machine.succeed(f"garage layout apply --version {version}")
+
+    def create_api_key(machine: Machine, key_name: str) -> S3Key:
+       output = machine.succeed(f"garage key new --name {key_name}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_id') or not m.group('secret_key'):
+          raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=key_name, key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def get_api_key(machine: Machine, key_pattern: str) -> S3Key:
+       output = machine.succeed(f"garage key info {key_pattern}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_name') or not m.group('key_id') or not m.group('secret_key'):
+           raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=m.group('key_name'), key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def test_bucket_writes(node):
+      node.succeed("garage bucket create test-bucket")
+      s3_key = create_api_key(node, "test-api-key")
+      node.succeed("garage bucket allow --read --write test-bucket --key test-api-key")
+      other_s3_key = get_api_key(node, 'test-api-key')
+      assert other_s3_key.secret_key == other_s3_key.secret_key
+      node.succeed(
+        f"mc alias set test-garage http://[::1]:3900 {s3_key.key_id} {s3_key.secret_key} --api S3v4"
+      )
+      node.succeed("echo test | mc pipe test-garage/test-bucket/test.txt")
+      assert node.succeed("mc cat test-garage/test-bucket/test.txt").strip() == "test"
+
+    def test_bucket_over_http(node, bucket='test-bucket', url=None):
+      if url is None:
+         url = f"{bucket}.web.garage"
+
+      node.succeed(f'garage bucket website --allow {bucket}')
+      node.succeed(f'echo hello world | mc pipe test-garage/{bucket}/index.html')
+      assert (node.succeed(f"curl -H 'Host: {url}' http://localhost:3902")).strip() == 'hello world'
+
+    with subtest("Garage works as a multi-node S3 storage"):
+      nodes = ('node1', 'node2', 'node3', 'node4')
+      rev_machines = {m.name: m for m in machines}
+      def get_machine(key): return rev_machines[key]
+      for key in nodes:
+        node = get_machine(key)
+        node.wait_for_unit("garage.service")
+        node.wait_for_open_port(3900)
+
+      # Garage is initialized on all nodes.
+      node_ids = {key: get_node_fqn(get_machine(key)) for key in nodes}
+
+      for key in nodes:
+        for other_key in nodes:
+          if other_key != key:
+            other_id = node_ids[other_key]
+            get_machine(key).succeed(f"garage node connect {other_id.node_id}@{other_id.host}")
+
+      # Provide multiple zones for the nodes.
+      zones = ["nixcon", "nixcon", "paris_meetup", "fosdem"]
+      apply_garage_layout(node1,
+      [
+        f'{ndata.node_id} -z {zones[index]} -c 1'
+        for index, ndata in enumerate(node_ids.values())
+      ])
+      # Now Garage is operational.
+      test_bucket_writes(node1)
+      for node in nodes:
+         test_bucket_over_http(get_machine(node))
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/gemstash.nix b/nixpkgs/nixos/tests/gemstash.nix
new file mode 100644
index 000000000000..bc152e42e92e
--- /dev/null
+++ b/nixpkgs/nixos/tests/gemstash.nix
@@ -0,0 +1,51 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let common_meta = { maintainers = [ maintainers.viraptor ]; };
+in
+{
+  gemstash_works = makeTest {
+    name = "gemstash-works";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(9292)
+      machine.succeed("curl http://localhost:9292")
+    '';
+  };
+
+  gemstash_custom_port = makeTest {
+    name = "gemstash-custom-port";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+        openFirewall = true;
+        settings = {
+          bind = "tcp://0.0.0.0:12345";
+        };
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(12345)
+      machine.succeed("curl http://localhost:12345")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/geth.nix b/nixpkgs/nixos/tests/geth.nix
index 11ad1ed2ea66..dc6490db57c9 100644
--- a/nixpkgs/nixos/tests/geth.nix
+++ b/nixpkgs/nixos/tests/geth.nix
@@ -19,6 +19,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         enable = true;
         port = 18545;
       };
+      authrpc = {
+        enable = true;
+        port = 18551;
+      };
     };
   };
 
@@ -31,11 +35,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.wait_for_open_port(18545)
 
     machine.succeed(
-        'geth attach --exec eth.blockNumber http://localhost:8545 | grep \'^0$\' '
+        'geth attach --exec "eth.blockNumber" http://localhost:8545 | grep \'^0$\' '
     )
 
     machine.succeed(
-        'geth attach --exec "eth.chainId()" http://localhost:18545 | grep \'"0x5"\' '
+        'geth attach --exec "eth.blockNumber" http://localhost:18545 | grep \'^0$\' '
     )
   '';
 })
diff --git a/nixpkgs/nixos/tests/ghostunnel.nix b/nixpkgs/nixos/tests/ghostunnel.nix
index 8bea64854021..91a7b7085f67 100644
--- a/nixpkgs/nixos/tests/ghostunnel.nix
+++ b/nixpkgs/nixos/tests/ghostunnel.nix
@@ -1,4 +1,5 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ghostunnel";
   nodes = {
     backend = { pkgs, ... }: {
       services.nginx.enable = true;
diff --git a/nixpkgs/nixos/tests/gitea.nix b/nixpkgs/nixos/tests/gitea.nix
index 037fc7b31bfa..2285bb5a4252 100644
--- a/nixpkgs/nixos/tests/gitea.nix
+++ b/nixpkgs/nixos/tests/gitea.nix
@@ -1,5 +1,6 @@
 { system ? builtins.currentSystem,
   config ? {},
+  giteaPackage ? pkgs.gitea,
   pkgs ? import ../.. { inherit system config; }
 }:
 
@@ -7,20 +8,37 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
+  ## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
+  signingPrivateKey = ''
+    -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+    lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
+    5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
+    ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
+    ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
+    TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
+    GCqGCRf9O/hzBA==
+    =9Uy3
+    -----END PGP PRIVATE KEY BLOCK-----
+  '';
+  signingPrivateKeyId = "4D642DE8B678C79D";
+
   supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
   makeGiteaTest = type: nameValuePair type (makeTest {
-    name = "gitea-${type}";
-    meta.maintainers = with maintainers; [ aanderse kolaente ma27 ];
+    name = "${giteaPackage.pname}-${type}";
+    meta.maintainers = with maintainers; [ aanderse emilylange kolaente ma27 ];
 
     nodes = {
       server = { config, pkgs, ... }: {
-        virtualisation.memorySize = 2048;
+        virtualisation.memorySize = 2047;
         services.gitea = {
           enable = true;
           database = { inherit type; };
-          disableRegistration = true;
+          package = giteaPackage;
+          settings.service.DISABLE_REGISTRATION = true;
+          settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
         };
-        environment.systemPackages = [ pkgs.gitea pkgs.jq ];
+        environment.systemPackages = [ giteaPackage pkgs.gnupg pkgs.jq ];
         services.openssh.enable = true;
       };
       client1 = { config, pkgs, ... }: {
@@ -54,9 +72,17 @@ let
 
       server.wait_for_unit("gitea.service")
       server.wait_for_open_port(3000)
+      server.wait_for_open_port(22)
       server.succeed("curl --fail http://localhost:3000/")
 
       server.succeed(
+          "su -l gitea -c 'gpg --homedir /var/lib/gitea/data/home/.gnupg "
+          + "--import ${toString (pkgs.writeText "gitea.key" signingPrivateKey)}'"
+      )
+
+      assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
+
+      server.succeed(
           "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
           + "Please contact your site administrator.'"
       )
@@ -68,7 +94,7 @@ let
       api_token = server.succeed(
           "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
           + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
-          + "'{\"name\":\"token\"}' | jq '.sha1' | xargs echo -n"
+          + "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
       )
 
       server.succeed(
diff --git a/nixpkgs/nixos/tests/github-runner.nix b/nixpkgs/nixos/tests/github-runner.nix
new file mode 100644
index 000000000000..033365d6925c
--- /dev/null
+++ b/nixpkgs/nixos/tests/github-runner.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "github-runner";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    services.github-runners.test = {
+      enable = true;
+      url = "https://github.com/yaxitech";
+      tokenFile = builtins.toFile "github-runner.token" "not-so-secret";
+    };
+
+    systemd.services.dummy-github-com = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "github-runner-test.service" ];
+      script = "${pkgs.netcat}/bin/nc -Fl 443 | true && touch /tmp/registration-connect";
+    };
+    networking.hosts."127.0.0.1" = [ "api.github.com" ];
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("dummy-github-com")
+
+    try:
+      machine.wait_for_unit("github-runner-test")
+    except Exception:
+      pass
+
+    out = machine.succeed("journalctl -u github-runner-test")
+    assert "Self-hosted runner registration" in out, "did not read runner registration header"
+
+    machine.wait_until_succeeds("test -f /tmp/registration-connect")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gitlab.nix b/nixpkgs/nixos/tests/gitlab.nix
index d9d75d1cbd89..88cd774f815a 100644
--- a/nixpkgs/nixos/tests/gitlab.nix
+++ b/nixpkgs/nixos/tests/gitlab.nix
@@ -6,9 +6,10 @@
 # - Creating Merge Requests and merging them
 # - Opening and closing issues.
 # - Downloading repository archives as tar.gz and tar.bz2
-import ./make-test-python.nix ({ pkgs, lib, ... }:
+# Run with
+# [nixpkgs]$ nix-build -A nixosTests.gitlab
 
-with lib;
+{ pkgs, lib, ... }:
 
 let
   inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
@@ -17,19 +18,17 @@ let
 
   aliceUsername = "alice";
   aliceUserId = "2";
-  alicePassword = "alicepassword";
-  aliceProjectId = "2";
+  alicePassword = "R5twyCgU0uXC71wT9BBTCqLs6HFZ7h3L";
+  aliceProjectId = "1";
   aliceProjectName = "test-alice";
 
   bobUsername = "bob";
   bobUserId = "3";
-  bobPassword = "bobpassword";
-  bobProjectId = "3";
+  bobPassword = "XwkkBbl2SiIwabQzgcoaTbhsotijEEtF";
+  bobProjectId = "2";
 in {
   name = "gitlab";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ globin yayayayaka ];
-  };
+  meta.maintainers = with lib.maintainers; [ globin yayayayaka ];
 
   nodes = {
     gitlab = { ... }: {
@@ -40,10 +39,10 @@ in {
       virtualisation.useNixStoreImage = true;
       virtualisation.writableStore = false;
 
-      systemd.services.gitlab.serviceConfig.Restart = mkForce "no";
-      systemd.services.gitlab-workhorse.serviceConfig.Restart = mkForce "no";
-      systemd.services.gitaly.serviceConfig.Restart = mkForce "no";
-      systemd.services.gitlab-sidekiq.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitlab.serviceConfig.Restart = lib.mkForce "no";
+      systemd.services.gitlab-workhorse.serviceConfig.Restart = lib.mkForce "no";
+      systemd.services.gitaly.serviceConfig.Restart = lib.mkForce "no";
+      systemd.services.gitlab-sidekiq.serviceConfig.Restart = lib.mkForce "no";
 
       services.nginx = {
         enable = true;
@@ -69,6 +68,10 @@ in {
         databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
         initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
         smtp.enable = true;
+        pages = {
+          enable = true;
+          settings.pages-domain = "localhost";
+        };
         extraConfig = {
           incoming_email = {
             enabled = true;
@@ -79,11 +82,6 @@ in {
             host = "localhost";
             port = 143;
           };
-          # https://github.com/NixOS/nixpkgs/issues/132295
-          # pages = {
-          #   enabled = true;
-          #   host = "localhost";
-          # };
         };
         secrets = {
           secretFile = pkgs.writeText "secret" "Aig5zaic";
@@ -171,12 +169,11 @@ in {
       waitForServices = ''
         gitlab.wait_for_unit("gitaly.service")
         gitlab.wait_for_unit("gitlab-workhorse.service")
-        # https://github.com/NixOS/nixpkgs/issues/132295
-        # gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-mailroom.service")
         gitlab.wait_for_unit("gitlab.service")
+        gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-sidekiq.service")
-        gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
+        gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
         gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
       '';
 
@@ -194,7 +191,7 @@ in {
         gitlab.succeed(
             "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${auth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers"
         )
-      '' + optionalString doSetup ''
+      '' + lib.optionalString doSetup ''
         with subtest("Create user Alice"):
             gitlab.succeed(
                 """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createUserAlice} http://gitlab/api/v4/users)" = "201" ]"""
@@ -421,15 +418,15 @@ in {
     + ''
       gitlab.systemctl("start gitlab-backup.service")
       gitlab.wait_for_unit("gitlab-backup.service")
-      gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
+      gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
       gitlab.systemctl("stop postgresql.service gitlab.target")
       gitlab.succeed(
-          "find ${nodes.gitlab.config.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
+          "find ${nodes.gitlab.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
       )
       gitlab.succeed("systemd-tmpfiles --create")
-      gitlab.succeed("rm -rf ${nodes.gitlab.config.services.postgresql.dataDir}")
+      gitlab.succeed("rm -rf ${nodes.gitlab.services.postgresql.dataDir}")
       gitlab.systemctl("start gitlab-config.service gitaly.service gitlab-postgresql.service")
-      gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
+      gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
       gitlab.succeed(
           "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
       )
@@ -437,4 +434,4 @@ in {
     ''
     + waitForServices
     + test false;
-})
+}
diff --git a/nixpkgs/nixos/tests/gnome-flashback.nix b/nixpkgs/nixos/tests/gnome-flashback.nix
new file mode 100644
index 000000000000..70569db797ec
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome-flashback.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "gnome-flashback";
+  meta.maintainers = lib.teams.gnome.members ++ [ lib.maintainers.chpatrick ];
+
+  nodes.machine = { nodes, ... }:
+    let
+      user = nodes.machine.config.users.users.alice;
+    in
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
+        autoLogin = {
+          enable = true;
+          user = user.name;
+        };
+      };
+
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+      services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
+      services.xserver.displayManager.defaultSession = "gnome-flashback-metacity";
+    };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+    uid = toString user.uid;
+    xauthority = "/run/user/${uid}/gdm/Xauthority";
+  in ''
+      with subtest("Login to GNOME Flashback with GDM"):
+          machine.wait_for_x()
+          # Wait for alice to be logged in"
+          machine.wait_for_unit("default.target", "${user.name}")
+          machine.wait_for_file("${xauthority}")
+          machine.succeed("xauth merge ${xauthority}")
+          # Check that logging in has given the user ownership of devices
+          assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+      with subtest("Wait for Metacity"):
+          machine.wait_until_succeeds(
+              "pgrep metacity"
+          )
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gnome-xorg.nix b/nixpkgs/nixos/tests/gnome-xorg.nix
index 618458b1f6b5..7762fff5c3a2 100644
--- a/nixpkgs/nixos/tests/gnome-xorg.nix
+++ b/nixpkgs/nixos/tests/gnome-xorg.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ...} : {
   name = "gnome-xorg";
-  meta = with lib; {
-    maintainers = teams.gnome.members;
+  meta = {
+    maintainers = lib.teams.gnome.members;
   };
 
   nodes.machine = { nodes, ... }: let
@@ -24,7 +24,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       services.xserver.desktopManager.gnome.enable = true;
       services.xserver.desktopManager.gnome.debug = true;
       services.xserver.displayManager.defaultSession = "gnome-xorg";
-      programs.gnome-terminal.enable = true;
 
       systemd.user.services = {
         "org.gnome.Shell@x11" = {
@@ -61,10 +60,10 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     # False when startup is done
     startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
 
-    # Start gnome-terminal
-    gnomeTerminalCommand = su "gnome-terminal";
+    # Start Console
+    launchConsole = su "${bus} gapplication launch org.gnome.Console";
 
-    # Hopefully gnome-terminal's wm class
+    # Hopefully Console's wm class
     wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
   in ''
       with subtest("Login to GNOME Xorg with GDM"):
@@ -82,13 +81,17 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
               "${startingUp} | grep -q 'true,..false'"
           )
 
-      with subtest("Open Gnome Terminal"):
+      with subtest("Open Console"):
+          # Close the Activities view so that Shell can correctly track the focused window.
+          machine.send_key("esc")
+
           machine.succeed(
-              "${gnomeTerminalCommand}"
+              "${launchConsole}"
           )
-          # correct output should be (true, '"Gnome-terminal"')
+          # correct output should be (true, '"kgx"')
+          # For some reason, this deviates from Wayland.
           machine.wait_until_succeeds(
-              "${wmClass} | grep -q  'true,...Gnome-terminal'"
+              "${wmClass} | grep -q  'true,...kgx'"
           )
           machine.sleep(20)
           machine.screenshot("screen")
diff --git a/nixpkgs/nixos/tests/gnome.nix b/nixpkgs/nixos/tests/gnome.nix
index 05619cbd7d82..448a3350240c 100644
--- a/nixpkgs/nixos/tests/gnome.nix
+++ b/nixpkgs/nixos/tests/gnome.nix
@@ -1,8 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ...} : {
   name = "gnome";
-  meta = with lib; {
-    maintainers = teams.gnome.members;
-  };
+  meta.maintainers = lib.teams.gnome.members;
 
   nodes.machine =
     { ... }:
@@ -22,14 +20,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
       services.xserver.desktopManager.gnome.enable = true;
       services.xserver.desktopManager.gnome.debug = true;
-      programs.gnome-terminal.enable = true;
-
-      environment.systemPackages = [
-        (pkgs.makeAutostartItem {
-          name = "org.gnome.Terminal";
-          package = pkgs.gnome.gnome-terminal;
-        })
-      ];
 
       systemd.user.services = {
         "org.gnome.Shell@wayland" = {
@@ -49,7 +39,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     };
 
   testScript = { nodes, ... }: let
-    # Keep line widths somewhat managable
+    # Keep line widths somewhat manageable
     user = nodes.machine.config.users.users.alice;
     uid = toString user.uid;
     bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
@@ -64,10 +54,10 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     # False when startup is done
     startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
 
-    # Start gnome-terminal
-    gnomeTerminalCommand = su "${bus} gnome-terminal";
+    # Start Console
+    launchConsole = su "${bus} gapplication launch org.gnome.Console";
 
-    # Hopefully gnome-terminal's wm class
+    # Hopefully Console's wm class
     wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
   in ''
       with subtest("Login to GNOME with GDM"):
@@ -86,10 +76,16 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
               "${startingUp} | grep -q 'true,..false'"
           )
 
-      with subtest("Open Gnome Terminal"):
-          # correct output should be (true, '"gnome-terminal-server"')
+      with subtest("Open Console"):
+          # Close the Activities view so that Shell can correctly track the focused window.
+          machine.send_key("esc")
+
+          machine.succeed(
+              "${launchConsole}"
+          )
+          # correct output should be (true, '"org.gnome.Console"')
           machine.wait_until_succeeds(
-              "${wmClass} | grep -q 'gnome-terminal-server'"
+              "${wmClass} | grep -q 'true,...org.gnome.Console'"
           )
           machine.sleep(20)
           machine.screenshot("screen")
diff --git a/nixpkgs/nixos/tests/gnupg.nix b/nixpkgs/nixos/tests/gnupg.nix
new file mode 100644
index 000000000000..65a9a93007fd
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnupg.nix
@@ -0,0 +1,118 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+
+{
+  name = "gnupg";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  # server for testing SSH
+  nodes.server = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    users.users.alice.isNormalUser = true;
+    services.openssh.enable = true;
+  };
+
+  # machine for testing GnuPG
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    users.users.alice.isNormalUser = true;
+    services.getty.autologinUser = "alice";
+
+    environment.shellInit = ''
+      # preset a key passphrase in gpg-agent
+      preset_key() {
+        # find all keys
+        case "$1" in
+          ssh) grips=$(awk '/^[0-9A-F]/{print $1}' "''${GNUPGHOME:-$HOME/.gnupg}/sshcontrol") ;;
+          pgp) grips=$(gpg --with-keygrip --list-secret-keys | awk '/Keygrip/{print $3}') ;;
+        esac
+
+        # try to preset the passphrase for each key found
+        for grip in $grips; do
+          "$(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase" -c -P "$2" "$grip"
+        done
+      }
+    '';
+
+    programs.gnupg.agent.enable = true;
+    programs.gnupg.agent.enableSSHSupport = true;
+  };
+
+  testScript =
+    ''
+      import shlex
+
+
+      def as_alice(command: str) -> str:
+          """
+          Wraps a command to run it as Alice in a login shell
+          """
+          quoted = shlex.quote(command)
+          return "su --login alice --command " + quoted
+
+
+      start_all()
+
+      with subtest("Wait for the autologin"):
+          machine.wait_until_tty_matches("1", "alice@machine")
+
+      with subtest("Can generate a PGP key"):
+          # Note: this needs a tty because of pinentry
+          machine.send_chars("gpg --gen-key\n")
+          machine.wait_until_tty_matches("1", "Real name:")
+          machine.send_chars("Alice\n")
+          machine.wait_until_tty_matches("1", "Email address:")
+          machine.send_chars("alice@machine\n")
+          machine.wait_until_tty_matches("1", "Change")
+          machine.send_chars("O\n")
+          machine.wait_until_tty_matches("1", "Please enter")
+          machine.send_chars("pgp_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "Please re-enter")
+          machine.send_chars("pgp_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "public and secret key created")
+
+      with subtest("Confirm the key is in the keyring"):
+          machine.wait_until_succeeds(as_alice("gpg --list-secret-keys | grep -q alice@machine"))
+
+      with subtest("Can generate and add an SSH key"):
+          machine.succeed(as_alice("ssh-keygen -t ed25519 -f alice -N ssh_p4ssphrase"))
+
+          # Note: apparently this must be run before using the OpenSSH agent
+          # socket for the first time in a tty. It's not needed for `ssh`
+          # because there's a hook that calls it automatically (only in NixOS).
+          machine.send_chars("gpg-connect-agent updatestartuptty /bye\n")
+
+          # Note: again, this needs a tty because of pinentry
+          machine.send_chars("ssh-add alice\n")
+          machine.wait_until_tty_matches("1", "Enter passphrase")
+          machine.send_chars("ssh_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "Please enter")
+          machine.send_chars("ssh_agent_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "Please re-enter")
+          machine.send_chars("ssh_agent_p4ssphrase\n")
+
+      with subtest("Confirm the SSH key has been registered"):
+          machine.wait_until_succeeds(as_alice("ssh-add -l | grep -q alice@machine"))
+
+      with subtest("Can preset the key passphrases in the agent"):
+          machine.succeed(as_alice("echo allow-preset-passphrase > .gnupg/gpg-agent.conf"))
+          machine.succeed(as_alice("pkill gpg-agent"))
+          machine.succeed(as_alice("preset_key pgp pgp_p4ssphrase"))
+          machine.succeed(as_alice("preset_key ssh ssh_agent_p4ssphrase"))
+
+      with subtest("Can encrypt and decrypt a message"):
+          machine.succeed(as_alice("echo Hello | gpg -e -r alice | gpg -d | grep -q Hello"))
+
+      with subtest("Can log into the server"):
+          # Install Alice's public key
+          public_key = machine.succeed(as_alice("cat alice.pub"))
+          server.succeed("mkdir /etc/ssh/authorized_keys.d")
+          server.succeed(f"printf '{public_key}' > /etc/ssh/authorized_keys.d/alice")
+
+          server.wait_for_open_port(22)
+          machine.succeed(as_alice("ssh -i alice -o StrictHostKeyChecking=no server exit"))
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gollum.nix b/nixpkgs/nixos/tests/gollum.nix
new file mode 100644
index 000000000000..44d373e35262
--- /dev/null
+++ b/nixpkgs/nixos/tests/gollum.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "gollum";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.gollum.enable = true;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    webserver.wait_for_unit("gollum")
+    webserver.wait_for_open_port(${toString nodes.webserver.services.gollum.port})
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gonic.nix b/nixpkgs/nixos/tests/gonic.nix
new file mode 100644
index 000000000000..726d7da0970f
--- /dev/null
+++ b/nixpkgs/nixos/tests/gonic.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "gonic";
+
+  nodes.machine = { ... }: {
+    services.gonic = {
+      enable = true;
+      settings = {
+        music-path = [ "/tmp" ];
+        podcast-path = "/tmp";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("gonic")
+    machine.wait_for_open_port(4747)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/google-oslogin/server.nix b/nixpkgs/nixos/tests/google-oslogin/server.nix
index faf5e847d7e9..3df41155c92d 100644
--- a/nixpkgs/nixos/tests/google-oslogin/server.nix
+++ b/nixpkgs/nixos/tests/google-oslogin/server.nix
@@ -17,8 +17,8 @@ in {
   };
 
   services.openssh.enable = true;
-  services.openssh.kbdInteractiveAuthentication = false;
-  services.openssh.passwordAuthentication = false;
+  services.openssh.settings.KbdInteractiveAuthentication = false;
+  services.openssh.settings.PasswordAuthentication = false;
 
   security.googleOsLogin.enable = true;
 
diff --git a/nixpkgs/nixos/tests/google-oslogin/server.py b/nixpkgs/nixos/tests/google-oslogin/server.py
index 5ea9bbd2c96b..622cd86b2619 100755
--- a/nixpkgs/nixos/tests/google-oslogin/server.py
+++ b/nixpkgs/nixos/tests/google-oslogin/server.py
@@ -103,6 +103,16 @@ class ReqHandler(BaseHTTPRequestHandler):
             self._send_json_ok(gen_mockuser(username=username, uid=uid, gid=uid, home_directory=f"/home/{username}", snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
             return
 
+        # we need to provide something at the groups endpoint.
+        # the nss module does segfault if we don't.
+        elif pu.path == "/computeMetadata/v1/oslogin/groups":
+            self._send_json_ok({
+                "posixGroups": [
+                    {"name" : "demo", "gid" : 4294967295}
+                ],
+            })
+            return
+
         # authorize endpoint
         elif pu.path == "/computeMetadata/v1/oslogin/authorize":
             # is user allowed to login?
diff --git a/nixpkgs/nixos/tests/gotify-server.nix b/nixpkgs/nixos/tests/gotify-server.nix
index e7942b76d8e5..d004f542b39a 100644
--- a/nixpkgs/nixos/tests/gotify-server.nix
+++ b/nixpkgs/nixos/tests/gotify-server.nix
@@ -42,7 +42,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
     assert title == "Gotify"
 
-    # Ensure that the UI responds with a successfuly code and that the
+    # Ensure that the UI responds with a successful code and that the
     # response is not empty
     result = machine.succeed("curl -fsS localhost:3000")
     assert result, "HTTP response from localhost:3000 must not be empty!"
diff --git a/nixpkgs/nixos/tests/grafana.nix b/nixpkgs/nixos/tests/grafana/basic.nix
index 174d664d8772..8bf4caad7fbf 100644
--- a/nixpkgs/nixos/tests/grafana.nix
+++ b/nixpkgs/nixos/tests/grafana/basic.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ lib, pkgs, ... }:
+import ../make-test-python.nix ({ lib, pkgs, ... }:
 
 let
   inherit (lib) mkMerge nameValuePair maintainers;
@@ -6,23 +6,47 @@ let
   baseGrafanaConf = {
     services.grafana = {
       enable = true;
-      addr = "localhost";
-      analytics.reporting.enable = false;
-      domain = "localhost";
-      security = {
-        adminUser = "testadmin";
-        adminPassword = "snakeoilpwd";
+      settings = {
+        analytics.reporting_enabled = false;
+
+        server = {
+          http_addr = "localhost";
+          domain = "localhost";
+        };
+
+        security = {
+          admin_user = "testadmin";
+          admin_password = "snakeoilpwd";
+        };
       };
     };
   };
 
   extraNodeConfs = {
+    sqlite = {};
+
+    socket = { config, ... }: {
+      services.grafana.settings.server = {
+        protocol = "socket";
+        socket = "/run/grafana/sock";
+        socket_gid = config.users.groups.nginx.gid;
+      };
+
+      users.users.grafana.extraGroups = [ "nginx" ];
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts."_".locations."/".proxyPass = "http://unix:/run/grafana/sock";
+      };
+    };
+
     declarativePlugins = {
       services.grafana.declarativePlugins = [ pkgs.grafanaPlugins.grafana-clock-panel ];
     };
 
     postgresql = {
-      services.grafana.database = {
+      services.grafana.settings.database = {
         host = "127.0.0.1:5432";
         user = "grafana";
       };
@@ -38,7 +62,7 @@ let
     };
 
     mysql = {
-      services.grafana.database.user = "grafana";
+      services.grafana.settings.database.user = "grafana";
       services.mysql = {
         enable = true;
         ensureDatabases = [ "grafana" ];
@@ -52,14 +76,9 @@ let
     };
   };
 
-  nodes = builtins.listToAttrs (map (dbName:
-    nameValuePair dbName (mkMerge [
-    baseGrafanaConf
-    (extraNodeConfs.${dbName} or {})
-  ])) [ "sqlite" "declarativePlugins" "postgresql" "mysql" ]);
-
+  nodes = builtins.mapAttrs (_: val: mkMerge [ val baseGrafanaConf ]) extraNodeConfs;
 in {
-  name = "grafana";
+  name = "grafana-basic";
 
   meta = with maintainers; {
     maintainers = [ willibutz ];
@@ -81,18 +100,32 @@ in {
     with subtest("Successful API query as admin user with sqlite db"):
         sqlite.wait_for_unit("grafana.service")
         sqlite.wait_for_open_port(3000)
+        print(sqlite.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users -i"
+        ))
         sqlite.succeed(
-            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep testadmin\@localhost"
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
         )
         sqlite.shutdown()
 
+    with subtest("Successful API query as admin user with sqlite db listening on socket"):
+        socket.wait_for_unit("grafana.service")
+        socket.wait_for_open_port(80)
+        print(socket.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1/api/org/users -i"
+        ))
+        socket.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1/api/org/users | grep admin\@localhost"
+        )
+        socket.shutdown()
+
     with subtest("Successful API query as admin user with postgresql db"):
         postgresql.wait_for_unit("grafana.service")
         postgresql.wait_for_unit("postgresql.service")
         postgresql.wait_for_open_port(3000)
         postgresql.wait_for_open_port(5432)
         postgresql.succeed(
-            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep testadmin\@localhost"
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
         )
         postgresql.shutdown()
 
@@ -102,7 +135,7 @@ in {
         mysql.wait_for_open_port(3000)
         mysql.wait_for_open_port(3306)
         mysql.succeed(
-            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep testadmin\@localhost"
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
         )
         mysql.shutdown()
   '';
diff --git a/nixpkgs/nixos/tests/grafana/default.nix b/nixpkgs/nixos/tests/grafana/default.nix
new file mode 100644
index 000000000000..9c2622571800
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+{
+  basic = import ./basic.nix { inherit system pkgs; };
+  provision = import ./provision { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/grafana/provision/contact-points.yaml b/nixpkgs/nixos/tests/grafana/provision/contact-points.yaml
new file mode 100644
index 000000000000..2a5f14e75e2d
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/contact-points.yaml
@@ -0,0 +1,9 @@
+apiVersion: 1
+
+contactPoints:
+  - name: "Test Contact Point"
+    receivers:
+      - uid: "test_contact_point"
+        type: prometheus-alertmanager
+        settings:
+          url: http://localhost:9000
diff --git a/nixpkgs/nixos/tests/grafana/provision/dashboards.yaml b/nixpkgs/nixos/tests/grafana/provision/dashboards.yaml
new file mode 100644
index 000000000000..dc83fe6b892d
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/dashboards.yaml
@@ -0,0 +1,6 @@
+apiVersion: 1
+
+providers:
+  - name: 'default'
+    options:
+      path: /var/lib/grafana/dashboards
diff --git a/nixpkgs/nixos/tests/grafana/provision/datasources.yaml b/nixpkgs/nixos/tests/grafana/provision/datasources.yaml
new file mode 100644
index 000000000000..ccf9481db7f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/datasources.yaml
@@ -0,0 +1,7 @@
+apiVersion: 1
+
+datasources:
+  - name: 'Test Datasource'
+    type: 'testdata'
+    access: 'proxy'
+    uid: 'test_datasource'
diff --git a/nixpkgs/nixos/tests/grafana/provision/default.nix b/nixpkgs/nixos/tests/grafana/provision/default.nix
new file mode 100644
index 000000000000..96378452ade3
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/default.nix
@@ -0,0 +1,257 @@
+import ../../make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  inherit (lib) mkMerge nameValuePair maintainers;
+
+  baseGrafanaConf = {
+    services.grafana = {
+      enable = true;
+      provision.enable = true;
+      settings = {
+        analytics.reporting_enabled = false;
+
+        server = {
+          http_addr = "localhost";
+          domain = "localhost";
+        };
+
+        security = {
+          admin_user = "testadmin";
+          admin_password = "$__file{${pkgs.writeText "pwd" "snakeoilpwd"}}";
+        };
+      };
+    };
+
+    system.activationScripts.setup-grafana = {
+      deps = [ "users" ];
+      text = ''
+        mkdir -p /var/lib/grafana/dashboards
+        chown -R grafana:grafana /var/lib/grafana
+        chmod 0700 -R /var/lib/grafana/dashboards
+        cp ${pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json)} /var/lib/grafana/dashboards/
+      '';
+    };
+  };
+
+  extraNodeConfs = {
+    provisionLegacyNotifiers = {
+      services.grafana.provision = {
+        datasources.settings = {
+          apiVersion = 1;
+          datasources = [{
+            name = "Test Datasource";
+            type = "testdata";
+            access = "proxy";
+            uid = "test_datasource";
+          }];
+        };
+        dashboards.settings = {
+          apiVersion = 1;
+          providers = [{
+            name = "default";
+            options.path = "/var/lib/grafana/dashboards";
+          }];
+        };
+        notifiers = [{
+          uid = "test_notifiers";
+          name = "Test Notifiers";
+          type = "email";
+          settings = {
+            singleEmail = true;
+            addresses = "test@test.com";
+          };
+        }];
+      };
+    };
+    provisionNix = {
+      services.grafana.provision = {
+        datasources.settings = {
+          apiVersion = 1;
+          datasources = [{
+            name = "Test Datasource";
+            type = "testdata";
+            access = "proxy";
+            uid = "test_datasource";
+          }];
+        };
+
+        dashboards.settings = {
+          apiVersion = 1;
+          providers = [{
+            name = "default";
+            options.path = "/var/lib/grafana/dashboards";
+          }];
+        };
+
+        alerting = {
+          rules.settings = {
+            groups = [{
+              name = "test_rule_group";
+              folder = "test_folder";
+              interval = "60s";
+              rules = [{
+                uid = "test_rule";
+                title = "Test Rule";
+                condition = "A";
+                data = [{
+                  refId = "A";
+                  datasourceUid = "-100";
+                  model = {
+                    conditions = [{
+                      evaluator = {
+                        params = [ 3 ];
+                        type = "git";
+                      };
+                      operator.type = "and";
+                      query.params = [ "A" ];
+                      reducer.type = "last";
+                      type = "query";
+                    }];
+                    datasource = {
+                      type = "__expr__";
+                      uid = "-100";
+                    };
+                    expression = "1==0";
+                    intervalMs = 1000;
+                    maxDataPoints = 43200;
+                    refId = "A";
+                    type = "math";
+                  };
+                }];
+                for = "60s";
+              }];
+            }];
+          };
+
+          contactPoints.settings = {
+            contactPoints = [{
+              name = "Test Contact Point";
+              receivers = [{
+                uid = "test_contact_point";
+                type = "prometheus-alertmanager";
+                settings.url = "http://localhost:9000";
+              }];
+            }];
+          };
+
+          policies.settings = {
+            policies = [{
+              receiver = "Test Contact Point";
+            }];
+          };
+
+          templates.settings = {
+            templates = [{
+              name = "Test Template";
+              template = "Test message";
+            }];
+          };
+
+          muteTimings.settings = {
+            muteTimes = [{
+              name = "Test Mute Timing";
+            }];
+          };
+        };
+      };
+    };
+
+    provisionYaml = {
+      services.grafana.provision = {
+        datasources.path = ./datasources.yaml;
+        dashboards.path = ./dashboards.yaml;
+        alerting = {
+          rules.path = ./rules.yaml;
+          contactPoints.path = ./contact-points.yaml;
+          policies.path = ./policies.yaml;
+          templates.path = ./templates.yaml;
+          muteTimings.path = ./mute-timings.yaml;
+        };
+      };
+    };
+
+    provisionYamlDirs = let
+      mkdir = p: pkgs.writeTextDir (baseNameOf p) (builtins.readFile p);
+    in {
+      services.grafana.provision = {
+        datasources.path = mkdir ./datasources.yaml;
+        dashboards.path = mkdir ./dashboards.yaml;
+        alerting = {
+          rules.path = mkdir ./rules.yaml;
+          contactPoints.path = mkdir ./contact-points.yaml;
+          policies.path = mkdir ./policies.yaml;
+          templates.path = mkdir ./templates.yaml;
+          muteTimings.path = mkdir ./mute-timings.yaml;
+        };
+      };
+    };
+  };
+
+  nodes = builtins.mapAttrs (_: val: mkMerge [ val baseGrafanaConf ]) extraNodeConfs;
+in {
+  name = "grafana-provision";
+
+  meta = with maintainers; {
+    maintainers = [ kfears willibutz ];
+  };
+
+  inherit nodes;
+
+  testScript = ''
+    start_all()
+
+    nodeNix = ("Nix (new format)", provisionNix)
+    nodeYaml = ("Nix (YAML)", provisionYaml)
+    nodeYamlDir = ("Nix (YAML in dirs)", provisionYamlDirs)
+
+    for description, machine in [nodeNix, nodeYaml, nodeYamlDir]:
+        with subtest(f"Should start provision node: {description}"):
+            machine.wait_for_unit("grafana.service")
+            machine.wait_for_open_port(3000)
+
+        with subtest(f"Successful datasource provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
+            )
+
+        with subtest(f"Successful dashboard provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/dashboards/uid/test_dashboard | grep Test\ Dashboard"
+            )
+
+        with subtest(f"Successful rule provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule"
+            )
+
+        with subtest(f"Successful contact point provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point"
+            )
+
+        with subtest(f"Successful policy provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/policies | grep Test\ Contact\ Point"
+            )
+
+        with subtest(f"Successful template provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/templates | grep Test\ Template"
+            )
+
+        with subtest("Successful mute timings provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing"
+            )
+
+    with subtest("Successful notifiers provision"):
+        provisionLegacyNotifiers.wait_for_unit("grafana.service")
+        provisionLegacyNotifiers.wait_for_open_port(3000)
+        print(provisionLegacyNotifiers.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers"
+        ))
+        provisionLegacyNotifiers.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers | grep Test\ Notifiers"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml b/nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml
new file mode 100644
index 000000000000..1f47f7c18f0c
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml
@@ -0,0 +1,4 @@
+apiVersion: 1
+
+muteTimes:
+  - name: "Test Mute Timing"
diff --git a/nixpkgs/nixos/tests/grafana/provision/policies.yaml b/nixpkgs/nixos/tests/grafana/provision/policies.yaml
new file mode 100644
index 000000000000..eb31126c4ba5
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/policies.yaml
@@ -0,0 +1,4 @@
+apiVersion: 1
+
+policies:
+  - receiver: "Test Contact Point"
diff --git a/nixpkgs/nixos/tests/grafana/provision/rules.yaml b/nixpkgs/nixos/tests/grafana/provision/rules.yaml
new file mode 100644
index 000000000000..946539c8cb69
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/rules.yaml
@@ -0,0 +1,36 @@
+apiVersion: 1
+
+groups:
+  - name: "test_rule_group"
+    folder: "test_group"
+    interval: 60s
+    rules:
+      - uid: "test_rule"
+        title: "Test Rule"
+        condition: A
+        data:
+          - refId: A
+            datasourceUid: '-100'
+            model:
+              conditions:
+                - evaluator:
+                    params:
+                      - 3
+                    type: gt
+                  operator:
+                    type: and
+                  query:
+                    params:
+                      - A
+                  reducer:
+                    type: last
+                  type: query
+              datasource:
+                type: __expr__
+                uid: '-100'
+              expression: 1==0
+              intervalMs: 1000
+              maxDataPoints: 43200
+              refId: A
+              type: math
+        for: 60s
diff --git a/nixpkgs/nixos/tests/grafana/provision/templates.yaml b/nixpkgs/nixos/tests/grafana/provision/templates.yaml
new file mode 100644
index 000000000000..09df247b3451
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/templates.yaml
@@ -0,0 +1,5 @@
+apiVersion: 1
+
+templates:
+  - name: "Test Template"
+    template: "Test message"
diff --git a/nixpkgs/nixos/tests/grafana/provision/test_dashboard.json b/nixpkgs/nixos/tests/grafana/provision/test_dashboard.json
new file mode 100644
index 000000000000..6e7a5b37f22b
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/test_dashboard.json
@@ -0,0 +1,47 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": {
+          "type": "grafana",
+          "uid": "-- Grafana --"
+        },
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "graphTooltip": 0,
+  "id": 28,
+  "links": [],
+  "liveNow": false,
+  "panels": [],
+  "schemaVersion": 37,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "Test Dashboard",
+  "uid": "test_dashboard",
+  "version": 1,
+  "weekStart": ""
+}
diff --git a/nixpkgs/nixos/tests/graphite.nix b/nixpkgs/nixos/tests/graphite.nix
index c534d45428e1..de6cd8a50e17 100644
--- a/nixpkgs/nixos/tests/graphite.nix
+++ b/nixpkgs/nixos/tests/graphite.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ... } :
             '';
           };
           carbon.enableCache = true;
-          seyren.enable = false;  # Implicitely requires openssl-1.0.2u which is marked insecure
+          seyren.enable = false;  # Implicitly requires openssl-1.0.2u which is marked insecure
         };
       };
   };
diff --git a/nixpkgs/nixos/tests/graylog.nix b/nixpkgs/nixos/tests/graylog.nix
index 23f426fc7af9..3f7cc3a91439 100644
--- a/nixpkgs/nixos/tests/graylog.nix
+++ b/nixpkgs/nixos/tests/graylog.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
     services.mongodb.enable = true;
     services.elasticsearch.enable = true;
-    services.elasticsearch.package = pkgs.elasticsearch-oss;
     services.elasticsearch.extraConf = ''
       network.publish_host: 127.0.0.1
       network.bind_host: 127.0.0.1
diff --git a/nixpkgs/nixos/tests/grocy.nix b/nixpkgs/nixos/tests/grocy.nix
index fe0ddd341486..48bbc9f7d3fa 100644
--- a/nixpkgs/nixos/tests/grocy.nix
+++ b/nixpkgs/nixos/tests/grocy.nix
@@ -14,6 +14,9 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   };
 
   testScript = ''
+    from base64 import b64encode
+    from urllib.parse import quote
+
     machine.start()
     machine.wait_for_open_port(80)
     machine.wait_for_unit("multi-user.target")
@@ -42,6 +45,29 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     machine.succeed("curl -sSI http://localhost/api/tasks 2>&1 | grep '401 Unauthorized'")
 
+    file_name = "test.txt"
+    file_name_base64 = b64encode(file_name.encode('ascii')).decode('ascii')
+    file_name_base64_urlencode = quote(file_name_base64)
+
+    machine.succeed(
+        f"echo Sample equipment manual > /tmp/{file_name}"
+    )
+
+    machine.succeed(
+        f"curl -sSf -X 'PUT' -b 'grocy_session={cookie}' "
+        + f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
+        + "  --header 'Accept: */*' "
+        + "  --header 'Content-Type: application/octet-stream' "
+        + f" --data-binary '@/tmp/{file_name}' "
+    )
+
+    machine.succeed(
+        f"curl -sSf -X 'GET' -b 'grocy_session={cookie}' "
+        + f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
+        + "  --header 'Accept: application/octet-stream' "
+        + f" | cmp /tmp/{file_name}"
+    )
+
     machine.shutdown()
   '';
 })
diff --git a/nixpkgs/nixos/tests/hadoop/hbase.nix b/nixpkgs/nixos/tests/hadoop/hbase.nix
index d9d2dac0f658..0416345682a8 100644
--- a/nixpkgs/nixos/tests/hadoop/hbase.nix
+++ b/nixpkgs/nixos/tests/hadoop/hbase.nix
@@ -53,6 +53,24 @@ with pkgs.lib;
         };
       };
     };
+    thrift = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          thrift = defOpts;
+        };
+      };
+    };
+    rest = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          rest = defOpts;
+        };
+      };
+    };
   };
 
   testScript = ''
@@ -80,5 +98,12 @@ with pkgs.lib;
     assert "1 active master, 0 backup masters, 1 servers" in master.succeed("echo status | HADOOP_USER_NAME=hbase hbase shell -n")
     regionserver.wait_until_succeeds("echo \"create 't1','f1'\" | HADOOP_USER_NAME=hbase hbase shell -n")
     assert "NAME => 'f1'" in regionserver.succeed("echo \"describe 't1'\" | HADOOP_USER_NAME=hbase hbase shell -n")
+
+    rest.wait_for_open_port(8080)
+    assert "${hbase.version}" in regionserver.succeed("curl http://rest:8080/version/cluster")
+
+    thrift.wait_for_open_port(9090)
   '';
+
+  meta.maintainers = with maintainers; [ illustris ];
 })
diff --git a/nixpkgs/nixos/tests/hadoop/hdfs.nix b/nixpkgs/nixos/tests/hadoop/hdfs.nix
index 9415500463de..429d4bf6b538 100644
--- a/nixpkgs/nixos/tests/hadoop/hdfs.nix
+++ b/nixpkgs/nixos/tests/hadoop/hdfs.nix
@@ -1,6 +1,5 @@
 # Test a minimal HDFS cluster with no HA
 import ../make-test-python.nix ({ package, lib, ... }:
-with lib;
 {
   name = "hadoop-hdfs";
 
@@ -22,7 +21,7 @@ with lib;
           };
           httpfs = {
             # The NixOS hadoop module only support webHDFS on 3.3 and newer
-            enable = mkIf (versionAtLeast package.version "3.3") true;
+            enable = lib.mkIf (lib.versionAtLeast package.version "3.3") true;
             openFirewall = true;
           };
         };
@@ -57,7 +56,7 @@ with lib;
 
     datanode.wait_for_unit("hdfs-datanode")
     datanode.wait_for_unit("network.target")
-  '' + ( if versionAtLeast package.version "3" then ''
+  '' + (if lib.versionAtLeast package.version "3" then ''
     datanode.wait_for_open_port(9864)
     datanode.wait_for_open_port(9866)
     datanode.wait_for_open_port(9867)
@@ -76,7 +75,7 @@ with lib;
     datanode.succeed("echo testfilecontents | sudo -u hdfs hdfs dfs -put - /testfile")
     assert "testfilecontents" in datanode.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
 
-  '' + optionalString ( versionAtLeast package.version "3.3" ) ''
+  '' + lib.optionalString (lib.versionAtLeast package.version "3.3" ) ''
     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/haproxy.nix b/nixpkgs/nixos/tests/haproxy.nix
index b6ff4102fe68..555474d7f299 100644
--- a/nixpkgs/nixos/tests/haproxy.nix
+++ b/nixpkgs/nixos/tests/haproxy.nix
@@ -2,7 +2,6 @@ import ./make-test-python.nix ({ pkgs, ...}: {
   name = "haproxy";
   nodes = {
     machine = { ... }: {
-      imports = [ ../modules/profiles/minimal.nix ];
       services.haproxy = {
         enable = true;
         config = ''
diff --git a/nixpkgs/nixos/tests/hardened.nix b/nixpkgs/nixos/tests/hardened.nix
index ccb858168547..f54506224e51 100644
--- a/nixpkgs/nixos/tests/hardened.nix
+++ b/nixpkgs/nixos/tests/hardened.nix
@@ -6,7 +6,6 @@ import ./make-test-python.nix ({ pkgs, ... } : {
 
   nodes.machine =
     { lib, pkgs, config, ... }:
-    with lib;
     { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
       imports = [ ../modules/profiles/hardened.nix ];
diff --git a/nixpkgs/nixos/tests/harmonia.nix b/nixpkgs/nixos/tests/harmonia.nix
new file mode 100644
index 000000000000..6cf9ad4d2335
--- /dev/null
+++ b/nixpkgs/nixos/tests/harmonia.nix
@@ -0,0 +1,37 @@
+{ pkgs, lib, ... }:
+
+{
+  name = "harmonia";
+
+  nodes = {
+    harmonia = {
+      services.harmonia = {
+        enable = true;
+        signKeyPath = pkgs.writeText "cache-key" "cache.example.com-1:9FhO0w+7HjZrhvmzT1VlAZw4OSAlFGTgC24Seg3tmPl4gZBdwZClzTTHr9cVzJpwsRSYLTu7hEAQe3ljy92CWg==";
+        settings.priority = 35;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 5000 ];
+      system.extraDependencies = [ pkgs.emptyFile ];
+    };
+
+    client01 = {
+      nix.settings = {
+        substituters = lib.mkForce [ "http://harmonia:5000" ];
+        trusted-public-keys = lib.mkForce [ "cache.example.com-1:eIGQXcGQpc00x6/XFcyacLEUmC07u4RAEHt5Y8vdglo=" ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    harmonia.wait_for_unit("harmonia.service")
+
+    client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.settings.priority}' >&2")
+    client01.succeed("curl -f http://harmonia:5000/version | grep '${nodes.harmonia.services.harmonia.package.version}' >&2")
+
+    client01.succeed("cat /etc/nix/nix.conf >&2")
+    client01.succeed("nix-store --realise ${pkgs.emptyFile} --store /root/other-store")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/headscale.nix b/nixpkgs/nixos/tests/headscale.nix
new file mode 100644
index 000000000000..80188b65dbfc
--- /dev/null
+++ b/nixpkgs/nixos/tests/headscale.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    tls-cert =
+      pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+        openssl req \
+          -x509 -newkey rsa:4096 -sha256 -days 365 \
+          -nodes -out cert.pem -keyout key.pem \
+          -subj '/CN=headscale' -addext "subjectAltName=DNS:headscale"
+
+        mkdir -p $out
+        cp key.pem cert.pem $out
+      '';
+  in {
+    name = "headscale";
+    meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+    nodes = let
+      headscalePort = 8080;
+      stunPort = 3478;
+      peer = {
+        services.tailscale.enable = true;
+        security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+      };
+    in {
+      peer1 = peer;
+      peer2 = peer;
+
+      headscale = {
+        services = {
+          headscale = {
+            enable = true;
+            port = headscalePort;
+            settings = {
+              server_url = "https://headscale";
+              ip_prefixes = [ "100.64.0.0/10" ];
+              derp.server = {
+                enabled = true;
+                region_id = 999;
+                stun_listen_addr = "0.0.0.0:${toString stunPort}";
+              };
+            };
+          };
+          nginx = {
+            enable = true;
+            virtualHosts.headscale = {
+              addSSL = true;
+              sslCertificate = "${tls-cert}/cert.pem";
+              sslCertificateKey = "${tls-cert}/key.pem";
+              locations."/" = {
+                proxyPass = "http://127.0.0.1:${toString headscalePort}";
+                proxyWebsockets = true;
+              };
+            };
+          };
+        };
+        networking.firewall = {
+          allowedTCPPorts = [ 80 443 ];
+          allowedUDPPorts = [ stunPort ];
+        };
+        environment.systemPackages = [ pkgs.headscale ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+      headscale.wait_for_unit("headscale")
+      headscale.wait_for_open_port(443)
+
+      # Create headscale user and preauth-key
+      headscale.succeed("headscale users create test")
+      authkey = headscale.succeed("headscale preauthkeys -u test create --reusable")
+
+      # Connect peers
+      up_cmd = f"tailscale up --login-server 'https://headscale' --auth-key {authkey}"
+      peer1.execute(up_cmd)
+      peer2.execute(up_cmd)
+
+      # Check that they are reachable from the tailnet
+      peer1.wait_until_succeeds("tailscale ping peer2")
+      peer2.wait_until_succeeds("tailscale ping peer1")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/hibernate.nix b/nixpkgs/nixos/tests/hibernate.nix
index 7a4b331169a3..24b4ed382c12 100644
--- a/nixpkgs/nixos/tests/hibernate.nix
+++ b/nixpkgs/nixos/tests/hibernate.nix
@@ -26,8 +26,9 @@ let
 
     powerManagement.resumeCommands = "systemctl --no-block restart backdoor.service";
 
-    fileSystems = {
-      "/".device = "/dev/vda2";
+    fileSystems."/" = {
+      device = "/dev/vda2";
+      fsType = "ext3";
     };
     swapDevices = mkOverride 0 [ { device = "/dev/vda1"; } ];
     boot.resumeDevice = mkIf systemdStage1 "/dev/vda1";
@@ -45,14 +46,14 @@ in makeTest {
 
   nodes = {
     # System configuration used for installing the installedConfig from above.
-    machine = { config, lib, pkgs, ... }: with lib; {
+    machine = { config, lib, pkgs, ... }: {
       imports = [
         ../modules/profiles/installation-device.nix
         ../modules/profiles/base.nix
       ];
 
       nix.settings = {
-        substituters = mkForce [];
+        substituters = lib.mkForce [];
         hashed-mirrors = null;
         connect-timeout = 1;
       };
@@ -62,7 +63,7 @@ in makeTest {
         # Small root disk for installer
         512
       ];
-      virtualisation.bootDevice = "/dev/vdb";
+      virtualisation.rootDevice = "/dev/vdb";
     };
   };
 
diff --git a/nixpkgs/nixos/tests/hockeypuck.nix b/nixpkgs/nixos/tests/hockeypuck.nix
index d1ef4cbf588a..2b9dba8720ab 100644
--- a/nixpkgs/nixos/tests/hockeypuck.nix
+++ b/nixpkgs/nixos/tests/hockeypuck.nix
@@ -57,7 +57,7 @@ in {
     # Send the key to our local keyserver
     machine.succeed("GNUPGHOME=/tmp/GNUPGHOME gpg --keyserver hkp://127.0.0.1:11371 --send-keys " + keyId)
 
-    # Recieve the key from our local keyserver to a separate directory
+    # Receive the key from our local keyserver to a separate directory
     machine.succeed("GNUPGHOME=$(mktemp -d) gpg --keyserver hkp://127.0.0.1:11371 --recv-keys " + keyId)
   '';
 })
diff --git a/nixpkgs/nixos/tests/home-assistant.nix b/nixpkgs/nixos/tests/home-assistant.nix
index 0bbeffd18cf0..e06c52a5f41c 100644
--- a/nixpkgs/nixos/tests/home-assistant.nix
+++ b/nixpkgs/nixos/tests/home-assistant.nix
@@ -22,22 +22,23 @@ in {
       enable = true;
       inherit configDir;
 
-      # tests loading components by overriding the package
+      # provide dependencies through package overrides
       package = (pkgs.home-assistant.override {
         extraPackages = ps: with ps; [
           colorama
         ];
-        extraComponents = [ "zha" ];
-      }).overrideAttrs (oldAttrs: {
-        doInstallCheck = false;
+        extraComponents = [
+          # test char-tty device allow propagation into the service
+          "zha"
+         ];
       });
 
-      # tests loading components from the module
+      # provide component dependencies explicitly from the module
       extraComponents = [
-        "wake_on_lan"
+        "mqtt"
       ];
 
-      # test extra package passing from the module
+      # provide package for postgresql support
       extraPackages = python3Packages: with python3Packages; [
         psycopg2
       ];
@@ -60,6 +61,11 @@ in {
         # https://www.home-assistant.io/integrations/frontend/
         frontend = {};
 
+        # include some popular integrations, that absolutely shouldn't break
+        knx = {};
+        shelly = {};
+        zha = {};
+
         # set up a wake-on-lan switch to test capset capability required
         # for the ping suid wrapper
         # https://www.home-assistant.io/integrations/wake_on_lan/
@@ -106,41 +112,43 @@ in {
     # Cause a configuration change that requires a service restart as we added a new runtime dependency
     specialisation.newFeature = {
       inheritParentConfig = true;
-      configuration.services.home-assistant.config.esphome = {};
+      configuration.services.home-assistant.config.backup = {};
     };
   };
 
   testScript = { nodes, ... }: let
-    system = nodes.hass.config.system.build.toplevel;
+    system = nodes.hass.system.build.toplevel;
   in
   ''
-    import re
     import json
 
     start_all()
 
-    # Parse the package path out of the systemd unit, as we cannot
-    # access the final package, that is overriden inside the module,
-    # by any other means.
-    pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass")
-    response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1]
-    match = pattern.search(response)
-    assert match
-    package = match.group('path')
-
 
-    def get_journal_cursor(host) -> str:
-        exit, out = host.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
+    def get_journal_cursor() -> str:
+        exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
         assert exit == 0
         return json.loads(out)["__CURSOR"]
 
 
-    def wait_for_homeassistant(host, cursor):
-        host.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
+    def get_journal_since(cursor) -> str:
+        exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service")
+        assert exit == 0
+        return out
+
+
+    def get_unit_property(property) -> str:
+        exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service")
+        assert exit == 0
+        return out
+
+
+    def wait_for_homeassistant(cursor):
+        hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
 
 
     hass.wait_for_unit("home-assistant.service")
-    cursor = get_journal_cursor(hass)
+    cursor = get_journal_cursor()
 
     with subtest("Check that YAML configuration file is in place"):
         hass.succeed("test -L ${configDir}/configuration.yaml")
@@ -148,19 +156,22 @@ in {
     with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
         hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
 
-    with subtest("Check extraComponents and extraPackages are considered from the package"):
-        hass.succeed(f"grep -q 'colorama' {package}/extra_packages")
-        hass.succeed(f"grep -q 'zha' {package}/extra_components")
-
-    with subtest("Check extraComponents and extraPackages are considered from the module"):
-        hass.succeed(f"grep -q 'psycopg2' {package}/extra_packages")
-        hass.succeed(f"grep -q 'wake_on_lan' {package}/extra_components")
-
     with subtest("Check that Home Assistant's web interface and API can be reached"):
-        wait_for_homeassistant(hass, cursor)
+        wait_for_homeassistant(cursor)
         hass.wait_for_open_port(8123)
         hass.succeed("curl --fail http://localhost:8123/lovelace")
 
+    with subtest("Check that optional dependencies are in the PYTHONPATH"):
+        env = get_unit_property("Environment")
+        python_path = env.split("PYTHONPATH=")[1].split()[0]
+        for package in ["colorama", "paho-mqtt", "psycopg2"]:
+            assert package in python_path, f"{package} not in PYTHONPATH"
+
+    with subtest("Check that declaratively configured components get setup"):
+        journal = get_journal_since(cursor)
+        for domain in ["emulated_hue", "wake_on_lan"]:
+            assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
+
     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")
@@ -169,25 +180,28 @@ in {
         hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
 
     with subtest("Check service reloads when configuration changes"):
-      # store the old pid of the process
-      pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
-      cursor = get_journal_cursor(hass)
-      hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
-      new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
-      assert pid == new_pid, "The PID of the process should not change between process reloads"
-      wait_for_homeassistant(hass, cursor)
-
-    with subtest("check service restarts when package changes"):
-      pid = new_pid
-      cursor = get_journal_cursor(hass)
-      hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
-      new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
-      assert pid != new_pid, "The PID of the process shoudl change when the HA binary changes"
-      wait_for_homeassistant(hass, cursor)
+        pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
+        new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        assert pid == new_pid, "The PID of the process should not change between process reloads"
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check service restarts when dependencies change"):
+        pid = new_pid
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
+        new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        assert pid != new_pid, "The PID of the process should change when its PYTHONPATH changess"
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check that new components get setup after restart"):
+        journal = get_journal_since(cursor)
+        for domain in ["backup"]:
+            assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
 
     with subtest("Check that no errors were logged"):
-        output_log = hass.succeed("cat ${configDir}/home-assistant.log")
-        assert "ERROR" not in output_log
+        hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
 
     with subtest("Check systemd unit hardening"):
         hass.log(hass.succeed("systemctl cat home-assistant.service"))
diff --git a/nixpkgs/nixos/tests/hostname.nix b/nixpkgs/nixos/tests/hostname.nix
index 1de8f19267af..6122e2ffeb83 100644
--- a/nixpkgs/nixos/tests/hostname.nix
+++ b/nixpkgs/nixos/tests/hostname.nix
@@ -1,6 +1,6 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../.. { inherit system config; }
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
@@ -14,55 +14,55 @@ let
         let res = builtins.tryEval str;
         in if (res.success && res.value != null) then res.value else "null";
     in
-      makeTest {
-        name = "hostname-${fqdn}";
-        meta = with pkgs.lib.maintainers; {
-          maintainers = [ primeos blitz ];
-        };
+    makeTest {
+      name = "hostname-${fqdn}";
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ primeos blitz ];
+      };
 
-        nodes.machine = { lib, ... }: {
-          networking.hostName = hostName;
-          networking.domain = domain;
+      nodes.machine = { lib, ... }: {
+        networking.hostName = hostName;
+        networking.domain = domain;
 
-          environment.systemPackages = with pkgs; [
-            inetutils
-          ];
-        };
+        environment.systemPackages = with pkgs; [
+          inetutils
+        ];
+      };
 
-        testScript = { nodes, ... }: ''
-          start_all()
+      testScript = { nodes, ... }: ''
+        start_all()
 
-          machine = ${hostName}
+        machine = ${hostName}
 
-          machine.wait_for_unit("network-online.target")
+        machine.wait_for_unit("network-online.target")
 
-          # Test if NixOS computes the correct FQDN (either a FQDN or an error/null):
-          assert "${getStr nodes.machine.config.networking.fqdn}" == "${getStr fqdnOrNull}"
+        # Test if NixOS computes the correct FQDN (either a FQDN or an error/null):
+        assert "${getStr nodes.machine.networking.fqdn}" == "${getStr fqdnOrNull}"
 
-          # The FQDN, domain name, and hostname detection should work as expected:
-          assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
-          assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
-          assert (
-              "${hostName}"
-              == machine.succeed(
-                  'hostnamectl status | grep "Static hostname" | cut -d: -f2'
-              ).strip()
-          )
+        # The FQDN, domain name, and hostname detection should work as expected:
+        assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
+        assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
+        assert (
+            "${hostName}"
+            == machine.succeed(
+                'hostnamectl status | grep "Static hostname" | cut -d: -f2'
+            ).strip()
+        )
 
-          # 127.0.0.1 and ::1 should resolve back to "localhost":
-          assert (
-              "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
-          )
-          assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
+        # 127.0.0.1 and ::1 should resolve back to "localhost":
+        assert (
+            "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
+        )
+        assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
 
-          # 127.0.0.2 should resolve back to the FQDN and hostname:
-          fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
-          assert (
-              fqdn_and_host_name
-              == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
-          )
-        '';
-      };
+        # 127.0.0.2 should resolve back to the FQDN and hostname:
+        fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
+        assert (
+            fqdn_and_host_name
+            == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
+        )
+      '';
+    };
 
 in
 {
diff --git a/nixpkgs/nixos/tests/hydra/common.nix b/nixpkgs/nixos/tests/hydra/common.nix
index fdf2b2c6f6dc..2bce03418e1f 100644
--- a/nixpkgs/nixos/tests/hydra/common.nix
+++ b/nixpkgs/nixos/tests/hydra/common.nix
@@ -16,7 +16,7 @@
     createTrivialProject = pkgs.stdenv.mkDerivation {
       name = "create-trivial-project";
       dontUnpack = true;
-      buildInputs = [ pkgs.makeWrapper ];
+      nativeBuildInputs = [ pkgs.makeWrapper ];
       installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
       postFixup = ''
         wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob}
diff --git a/nixpkgs/nixos/tests/iftop.nix b/nixpkgs/nixos/tests/iftop.nix
index 6d0090b39463..933f115a8a5a 100644
--- a/nixpkgs/nixos/tests/iftop.nix
+++ b/nixpkgs/nixos/tests/iftop.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }:
 
-with lib;
-
 {
   name = "iftop";
-  meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
+  meta.maintainers = with lib.maintainers; [ ma27 ];
 
   nodes = {
     withIftop = {
diff --git a/nixpkgs/nixos/tests/ihatemoney/default.nix b/nixpkgs/nixos/tests/ihatemoney/default.nix
deleted file mode 100644
index 894a97d43d35..000000000000
--- a/nixpkgs/nixos/tests/ihatemoney/default.nix
+++ /dev/null
@@ -1,71 +0,0 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../../.. { inherit system config; }
-}:
-
-let
-  inherit (import ../../lib/testing-python.nix { inherit system pkgs; }) makeTest;
-  f = backend: makeTest {
-    name = "ihatemoney-${backend}";
-    nodes.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.postfix.enable = true;
-    };
-    testScript = ''
-      machine.wait_for_open_port(8000)
-      machine.wait_for_unit("uwsgi.service")
-      machine.wait_until_succeeds("curl --fail https://api.exchangerate.host")
-      machine.wait_until_succeeds("curl --fail http://localhost:8000")
-
-      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("___")
-      assert "ihatemoney:ihatemoney" == owner
-
-      with subtest("Restart machine and service"):
-          machine.shutdown()
-          machine.start()
-          machine.wait_for_open_port(8000)
-          machine.wait_for_unit("uwsgi.service")
-
-      with subtest("check that the database is really persistent"):
-          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 --fail http://localhost:8000")
-    '';
-  };
-in {
-  ihatemoney-sqlite = f "sqlite";
-  ihatemoney-postgresql = f "postgresql";
-}
diff --git a/nixpkgs/nixos/tests/ihatemoney/rates.json b/nixpkgs/nixos/tests/ihatemoney/rates.json
deleted file mode 100644
index ebdd2651b040..000000000000
--- a/nixpkgs/nixos/tests/ihatemoney/rates.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "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
deleted file mode 100644
index 10e568b14b14..000000000000
--- a/nixpkgs/nixos/tests/ihatemoney/server.crt
+++ /dev/null
@@ -1,28 +0,0 @@
------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
deleted file mode 100644
index 72a43577d64d..000000000000
--- a/nixpkgs/nixos/tests/ihatemoney/server.key
+++ /dev/null
@@ -1,52 +0,0 @@
------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/image-contents.nix b/nixpkgs/nixos/tests/image-contents.nix
index 90908968a7e2..858f7d8c68f4 100644
--- a/nixpkgs/nixos/tests/image-contents.nix
+++ b/nixpkgs/nixos/tests/image-contents.nix
@@ -27,13 +27,19 @@ let
     inherit pkgs config;
     lib = pkgs.lib;
     format = "qcow2";
-    contents = [{
-      source = pkgs.writeText "testFile" "contents";
-      target = "/testFile";
-      user = "1234";
-      group = "5678";
-      mode = "755";
-    }];
+    contents = [
+      {
+        source = pkgs.writeText "testFile" "contents";
+        target = "/testFile";
+        user = "1234";
+        group = "5678";
+        mode = "755";
+      }
+      {
+        source = ./.;
+        target = "/testDir";
+      }
+    ];
   }) + "/nixos.qcow2";
 
 in makeEc2Test {
@@ -42,10 +48,15 @@ in makeEc2Test {
   userData = null;
   script = ''
     machine.start()
+    # Test that if contents includes a file, it is copied to the target.
     assert "content" in machine.succeed("cat /testFile")
     fileDetails = machine.succeed("ls -l /testFile")
     assert "1234" in fileDetails
     assert "5678" in fileDetails
     assert "rwxr-xr-x" in fileDetails
+
+    # Test that if contents includes a directory, it is copied to the target.
+    dirList = machine.succeed("ls /testDir")
+    assert "image-contents.nix" in dirList
   '';
 }
diff --git a/nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix b/nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix
new file mode 100644
index 000000000000..521456e7e0b2
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix
@@ -0,0 +1,102 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. {inherit system config; }
+, systemdStage1 ? false }:
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+
+  keyfile = pkgs.writeText "luks-keyfile" ''
+    MIGHAoGBAJ4rGTSo/ldyjQypd0kuS7k2OSsmQYzMH6TNj3nQ/vIUjDn7fqa3slt2
+    gV6EK3TmTbGc4tzC1v4SWx2m+2Bjdtn4Fs4wiBwn1lbRdC6i5ZYCqasTWIntWn+6
+    FllUkMD5oqjOR/YcboxG8Z3B5sJuvTP9llsF+gnuveWih9dpbBr7AgEC
+  '';
+
+in {
+  name = "initrd-luks-empty-passphrase";
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      useEFIBoot = true;
+      # This requires to have access
+      # to a host Nix store as
+      # the new root device is /dev/vdb
+      # an empty 512MiB drive, containing no Nix store.
+      mountHostNixStore = true;
+    };
+
+    boot.loader.systemd-boot.enable = true;
+    boot.initrd.systemd = lib.mkIf systemdStage1 {
+      enable = true;
+      emergencyAccess = true;
+    };
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation.boot-luks-wrong-keyfile.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          keyFile = "/etc/cryptroot.key";
+          tryEmptyPassphrase = true;
+          fallbackToPassword = !systemdStage1;
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
+    };
+
+    specialisation.boot-luks-missing-keyfile.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          keyFile = "/etc/cryptroot.key";
+          tryEmptyPassphrase = true;
+          fallbackToPassword = !systemdStage1;
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+    };
+  };
+
+  testScript = ''
+    # Encrypt key with empty key so boot should try keyfile and then fallback to empty passphrase
+
+
+    def grub_select_boot_luks_wrong_key_file():
+        """
+        Selects "boot-luks" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+    def grub_select_boot_luks_missing_key_file():
+        """
+        Selects "boot-luks" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo "" | cryptsetup luksFormat /dev/vdb --batch-mode")
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-wrong-keyfile.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Check if rootfs is on /dev/mapper/cryptroot
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+
+    # Choose boot-luks-missing-keyfile specialisation
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-missing-keyfile.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Check if rootfs is on /dev/mapper/cryptroot
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix b/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix
index bb4c41e6d709..769049905eb8 100644
--- a/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix
+++ b/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix
@@ -1,3 +1,9 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
 import ../make-test-python.nix ({ lib, ...}:
 
 {
@@ -22,11 +28,12 @@ import ../make-test-python.nix ({ lib, ...}:
       minimalboot =
         { ... }:
         {
+          boot.initrd.systemd.enable = systemdStage1;
           boot.initrd.network = {
             enable = true;
             openvpn = {
               enable = true;
-              configuration = "/dev/null";
+              configuration = builtins.toFile "initrd.ovpn" "";
             };
           };
         };
@@ -39,6 +46,17 @@ import ../make-test-python.nix ({ lib, ...}:
           virtualisation.vlans = [ 1 ];
 
           boot.initrd = {
+            systemd.enable = systemdStage1;
+            systemd.extraBin.nc = "${pkgs.busybox}/bin/nc";
+            systemd.services.nc = {
+              requiredBy = ["initrd.target"];
+              after = ["network.target"];
+              serviceConfig = {
+                ExecStart = "/bin/nc -p 1234 -lke /bin/echo TESTVALUE";
+                Type = "oneshot";
+              };
+            };
+
             # This command does not fork to keep the VM in the state where
             # only the initramfs is loaded
             preLVMCommands =
@@ -91,6 +109,7 @@ import ../make-test-python.nix ({ lib, ...}:
             config = ''
               dev tun0
               ifconfig 10.8.0.1 10.8.0.2
+              cipher AES-256-CBC
               ${secretblock}
             '';
           };
diff --git a/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn b/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn
index 5926a48af00f..3ada4130e868 100644
--- a/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn
+++ b/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn
@@ -3,6 +3,7 @@ dev tun
 ifconfig 10.8.0.2 10.8.0.1
 # Only force VLAN 2 through the VPN
 route 192.168.2.0 255.255.255.0 10.8.0.1
+cipher AES-256-CBC
 secret [inline]
 <secret>
 #
@@ -26,4 +27,4 @@ be5a69522a8e60ccb217f8521681b45d
 e7811584363597599cce2040a68ac00e
 f2125540e0f7f4adc37cb3f0d922eeb7
 -----END OpenVPN Static key V1-----
-</secret>
\ No newline at end of file
+</secret>
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/default.nix b/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
index 0ad0563b0ce1..017de6882081 100644
--- a/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
@@ -22,10 +22,6 @@ import ../make-test-python.nix ({ lib, ... }:
             hostKeys = [ ./ssh_host_ed25519_key ];
           };
         };
-        boot.initrd.extraUtilsCommands = ''
-          mkdir -p $out/secrets/etc/ssh
-          cat "${./ssh_host_ed25519_key}" > $out/secrets/etc/ssh/sh_host_ed25519_key
-        '';
         boot.initrd.preLVMCommands = ''
           while true; do
             if [ -f fnord ]; then
diff --git a/nixpkgs/nixos/tests/initrd-secrets-changing.nix b/nixpkgs/nixos/tests/initrd-secrets-changing.nix
new file mode 100644
index 000000000000..d6f9ef9ced83
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-secrets-changing.nix
@@ -0,0 +1,57 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+, testing ? import ../lib/testing-python.nix { inherit system pkgs; }
+}:
+
+let
+  secret1InStore = pkgs.writeText "topsecret" "iamasecret1";
+  secret2InStore = pkgs.writeText "topsecret" "iamasecret2";
+in
+
+testing.makeTest {
+  name = "initrd-secrets-changing";
+
+  nodes.machine = { ... }: {
+    virtualisation.useBootLoader = true;
+
+    boot.loader.grub.device = "/dev/vda";
+
+    boot.initrd.secrets = {
+      "/test" = secret1InStore;
+      "/run/keys/test" = secret1InStore;
+    };
+    boot.initrd.postMountCommands = "cp /test /mnt-root/secret-from-initramfs";
+
+    specialisation.secrets2System.configuration = {
+      boot.initrd.secrets = lib.mkForce {
+        "/test" = secret2InStore;
+        "/run/keys/test" = secret2InStore;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("multi-user.target")
+    print(machine.succeed("cat /run/keys/test"))
+    machine.succeed(
+        "cmp ${secret1InStore} /secret-from-initramfs",
+        "cmp ${secret1InStore} /run/keys/test",
+    )
+    # Select the second boot entry corresponding to the specialisation secrets2System.
+    machine.succeed("grub-reboot 1")
+    machine.shutdown()
+
+    with subtest("Check that the specialisation's secrets are distinct despite identical kernels"):
+        machine.wait_for_unit("multi-user.target")
+        print(machine.succeed("cat /run/keys/test"))
+        machine.succeed(
+            "cmp ${secret2InStore} /secret-from-initramfs",
+            "cmp ${secret2InStore} /run/keys/test",
+        )
+        machine.shutdown()
+  '';
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/default.nix b/nixpkgs/nixos/tests/installed-tests/default.nix
index b81384aa8c0b..78a6325a245e 100644
--- a/nixpkgs/nixos/tests/installed-tests/default.nix
+++ b/nixpkgs/nixos/tests/installed-tests/default.nix
@@ -28,7 +28,7 @@ let
     , withX11 ? false
 
       # Extra flags to pass to gnome-desktop-testing-runner.
-    , testRunnerFlags ? ""
+    , testRunnerFlags ? []
 
       # Extra attributes to pass to makeTest.
       # They will be recursively merged into the attrset created by this function.
@@ -40,7 +40,7 @@ let
           name = tested.name;
 
           meta = {
-            maintainers = tested.meta.maintainers;
+            maintainers = tested.meta.maintainers or [];
           };
 
           nodes.machine = { ... }: {
@@ -67,7 +67,7 @@ let
             '' +
             ''
               machine.succeed(
-                  "gnome-desktop-testing-runner ${testRunnerFlags} -d '${tested.installedTests}/share'"
+                  "gnome-desktop-testing-runner ${escapeShellArgs testRunnerFlags} -d '${tested.installedTests}/share'"
               )
             '';
         }
@@ -98,9 +98,9 @@ in
   gnome-photos = callInstalledTest ./gnome-photos.nix {};
   graphene = callInstalledTest ./graphene.nix {};
   gsconnect = callInstalledTest ./gsconnect.nix {};
+  json-glib = callInstalledTest ./json-glib.nix {};
   ibus = callInstalledTest ./ibus.nix {};
   libgdata = callInstalledTest ./libgdata.nix {};
-  librsvg = callInstalledTest ./librsvg.nix {};
   glib-testing = callInstalledTest ./glib-testing.nix {};
   libjcat = callInstalledTest ./libjcat.nix {};
   libxmlb = callInstalledTest ./libxmlb.nix {};
diff --git a/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix b/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix
index 41f4060fb69e..d5e04fcf975c 100644
--- a/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix
+++ b/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix
@@ -11,5 +11,5 @@ makeInstalledTest {
     virtualisation.diskSize = 2048;
   };
 
-  testRunnerFlags = "--timeout 3600";
+  testRunnerFlags = [ "--timeout" "3600" ];
 }
diff --git a/nixpkgs/nixos/tests/installed-tests/flatpak.nix b/nixpkgs/nixos/tests/installed-tests/flatpak.nix
index c7fe9cf45882..9524d890c402 100644
--- a/nixpkgs/nixos/tests/installed-tests/flatpak.nix
+++ b/nixpkgs/nixos/tests/installed-tests/flatpak.nix
@@ -13,5 +13,5 @@ makeInstalledTest {
     virtualisation.diskSize = 3072;
   };
 
-  testRunnerFlags = "--timeout 3600";
+  testRunnerFlags = [ "--timeout" "3600" ];
 }
diff --git a/nixpkgs/nixos/tests/installed-tests/fwupd.nix b/nixpkgs/nixos/tests/installed-tests/fwupd.nix
index 65614e2689d8..c095a50dc836 100644
--- a/nixpkgs/nixos/tests/installed-tests/fwupd.nix
+++ b/nixpkgs/nixos/tests/installed-tests/fwupd.nix
@@ -5,7 +5,7 @@ makeInstalledTest {
 
   testConfig = {
     services.fwupd.enable = true;
-    services.fwupd.disabledPlugins = lib.mkForce []; # don't disable test plugin
+    services.fwupd.daemonSettings.DisabledPlugins = lib.mkForce [ ]; # don't disable test plugin
     services.fwupd.enableTestRemote = true;
   };
 }
diff --git a/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix b/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix
index 3d0011a427a4..110efdbf710f 100644
--- a/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix
+++ b/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix
@@ -9,5 +9,5 @@ makeInstalledTest {
     virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
   };
 
-  testRunnerFlags = "--timeout 1800";
+  testRunnerFlags = [ "--timeout" "1800" ];
 }
diff --git a/nixpkgs/nixos/tests/installed-tests/ibus.nix b/nixpkgs/nixos/tests/installed-tests/ibus.nix
index a4bc2a7d7de0..028c20c29f2d 100644
--- a/nixpkgs/nixos/tests/installed-tests/ibus.nix
+++ b/nixpkgs/nixos/tests/installed-tests/ibus.nix
@@ -4,6 +4,7 @@ makeInstalledTest {
   tested = pkgs.ibus;
 
   testConfig = {
+    i18n.supportedLocales = [ "all" ];
     i18n.inputMethod.enabled = "ibus";
     systemd.user.services.ibus-daemon = {
       serviceConfig.ExecStart = "${pkgs.ibus}/bin/ibus-daemon --xim --verbose";
diff --git a/nixpkgs/nixos/tests/installed-tests/json-glib.nix b/nixpkgs/nixos/tests/installed-tests/json-glib.nix
new file mode 100644
index 000000000000..3dfd3dd0b098
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/json-glib.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.json-glib;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/librsvg.nix b/nixpkgs/nixos/tests/installed-tests/librsvg.nix
deleted file mode 100644
index 378e7cce3ff4..000000000000
--- a/nixpkgs/nixos/tests/installed-tests/librsvg.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-{ pkgs, makeInstalledTest, ... }:
-
-makeInstalledTest {
-  tested = pkgs.librsvg;
-
-  testConfig = {
-    virtualisation.memorySize = 2047;
-  };
-}
diff --git a/nixpkgs/nixos/tests/installed-tests/pipewire.nix b/nixpkgs/nixos/tests/installed-tests/pipewire.nix
index b04265658fcf..6e69ada8612f 100644
--- a/nixpkgs/nixos/tests/installed-tests/pipewire.nix
+++ b/nixpkgs/nixos/tests/installed-tests/pipewire.nix
@@ -1,15 +1,5 @@
-{ pkgs, lib, makeInstalledTest, ... }:
+{ pkgs, makeInstalledTest, ... }:
 
 makeInstalledTest {
   tested = pkgs.pipewire;
-  testConfig = {
-    hardware.pulseaudio.enable = false;
-    services.pipewire = {
-      enable = true;
-      pulse.enable = true;
-      jack.enable = true;
-      alsa.enable = true;
-      alsa.support32Bit = true;
-    };
-  };
 }
diff --git a/nixpkgs/nixos/tests/installer-systemd-stage-1.nix b/nixpkgs/nixos/tests/installer-systemd-stage-1.nix
index d02387ee80e0..05fb2b2ae89c 100644
--- a/nixpkgs/nixos/tests/installer-systemd-stage-1.nix
+++ b/nixpkgs/nixos/tests/installer-systemd-stage-1.nix
@@ -8,9 +8,10 @@
   # them when fixed.
   inherit (import ./installer.nix { inherit system config pkgs; systemdStage1 = true; })
     # bcache
-    # btrfsSimple
-    # btrfsSubvolDefault
-    # btrfsSubvols
+    btrfsSimple
+    btrfsSubvolDefault
+    btrfsSubvolEscape
+    btrfsSubvols
     # encryptedFSWithKeyfile
     # grub1
     # luksroot
@@ -26,6 +27,7 @@
     simpleUefiGrub
     simpleUefiGrubSpecialisation
     simpleUefiSystemdBoot
+    stratisRoot
     # swraid
     zfsroot
     ;
diff --git a/nixpkgs/nixos/tests/installer.nix b/nixpkgs/nixos/tests/installer.nix
index 8bef4fad3dd2..1ac164f4b816 100644
--- a/nixpkgs/nixos/tests/installer.nix
+++ b/nixpkgs/nixos/tests/installer.nix
@@ -10,7 +10,7 @@ with pkgs.lib;
 let
 
   # The configuration to install.
-  makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier, grubUseEfi
+  makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
                , extraConfig, forceGrubReinstallCount ? 0
                }:
     pkgs.writeText "configuration.nix" ''
@@ -21,17 +21,14 @@ let
             <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
           ];
 
+        documentation.enable = false;
+
         # To ensure that we can rebuild the grub configuration on the nixos-rebuild
         system.extraDependencies = with pkgs; [ stdenvNoCC ];
 
         ${optionalString systemdStage1 "boot.initrd.systemd.enable = true;"}
 
         ${optionalString (bootLoader == "grub") ''
-          boot.loader.grub.version = ${toString grubVersion};
-          ${optionalString (grubVersion == 1) ''
-            boot.loader.grub.splashImage = null;
-          ''}
-
           boot.loader.grub.extraConfig = "serial; terminal_output serial";
           ${if grubUseEfi then ''
             boot.loader.grub.device = "nodev";
@@ -49,6 +46,8 @@ let
           boot.loader.systemd-boot.enable = true;
         ''}
 
+        boot.initrd.secrets."/etc/secret" = ./secret;
+
         users.users.alice = {
           isNormalUser = true;
           home = "/home/alice";
@@ -57,7 +56,7 @@ let
 
         hardware.enableAllFirmware = lib.mkForce false;
 
-        ${replaceChars ["\n"] ["\n  "] extraConfig}
+        ${replaceStrings ["\n"] ["\n  "] extraConfig}
       }
     '';
 
@@ -66,16 +65,16 @@ let
   # disk, and then reboot from the hard disk.  It's parameterized with
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
-  testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
+  testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi
                   , grubIdentifier, preBootCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig
                   }:
-    let iface = if grubVersion == 1 then "ide" else "virtio";
+    let iface = "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.hostPlatform.isx86 then
-      throw "Non-EFI boot methods are only supported on i686 / x86_64"
-    else ''
+    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
+      machine.succeed("true")
+    '' else ''
       def assemble_qemu_flags():
           flags = "-cpu max"
           ${if (system == "x86_64-linux" || system == "i686-linux")
@@ -118,12 +117,13 @@ let
           machine.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
           machine.copy_from_host(
               "${ makeConfig {
-                    inherit bootLoader grubVersion grubDevice grubIdentifier
+                    inherit bootLoader grubDevice grubIdentifier
                             grubUseEfi extraConfig;
                   }
               }",
               "/mnt/etc/nixos/configuration.nix",
           )
+          machine.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
 
       with subtest("Perform the installation"):
           machine.succeed("nixos-install < /dev/null >&2")
@@ -131,9 +131,21 @@ let
       with subtest("Do it again to make sure it's idempotent"):
           machine.succeed("nixos-install < /dev/null >&2")
 
+      with subtest("Check that we can build things in nixos-enter"):
+          machine.succeed(
+              """
+              nixos-enter -- nix-build --option substitute false -E 'derivation {
+                  name = "t";
+                  builder = "/bin/sh";
+                  args = ["-c" "echo nixos-enter build > $out"];
+                  system = builtins.currentSystem;
+                  preferLocalBuild = true;
+              }'
+              """
+          )
+
       with subtest("Shutdown system after installation"):
-          machine.succeed("umount /mnt/boot || true")
-          machine.succeed("umount /mnt")
+          machine.succeed("umount -R /mnt")
           machine.succeed("sync")
           machine.shutdown()
 
@@ -176,7 +188,7 @@ let
           # doesn't know about the host-guest sharing mechanism.
           machine.copy_from_host_via_shell(
               "${ makeConfig {
-                    inherit bootLoader grubVersion grubDevice grubIdentifier
+                    inherit bootLoader grubDevice grubIdentifier
                             grubUseEfi extraConfig;
                     forceGrubReinstallCount = 1;
                   }
@@ -205,7 +217,7 @@ let
       # doesn't know about the host-guest sharing mechanism.
       machine.copy_from_host_via_shell(
           "${ makeConfig {
-                inherit bootLoader grubVersion grubDevice grubIdentifier
+                inherit bootLoader grubDevice grubIdentifier
                 grubUseEfi extraConfig;
                 forceGrubReinstallCount = 2;
               }
@@ -267,7 +279,7 @@ let
     { createPartitions, preBootCommands ? "", postBootCommands ? "", extraConfig ? ""
     , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
-    , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
+    , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
     , enableOCR ? false, meta ? {}
     , testSpecialisationConfig ? false
     }:
@@ -299,10 +311,9 @@ let
           # installer. This ensures the target disk (/dev/vda) is
           # the same during and after installation.
           virtualisation.emptyDiskImages = [ 512 ];
-          virtualisation.bootDevice =
-            if grubVersion == 1 then "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive2" else "/dev/vdb";
-          virtualisation.qemu.diskInterface =
-            if grubVersion == 1 then "scsi" else "virtio";
+          virtualisation.rootDevice = "/dev/vdb";
+          virtualisation.bootLoaderDevice = "/dev/vda";
+          virtualisation.qemu.diskInterface = "virtio";
 
           # We don't want to have any networking in the guest whatsoever.
           # Also, if any vlans are enabled, the guest will reboot
@@ -324,6 +335,10 @@ let
             desktop-file-utils
             docbook5
             docbook_xsl_ns
+            (docbook-xsl-ns.override {
+              withManOptDedupPatch = true;
+            })
+            kbd.dev
             kmod.dev
             libarchive.dev
             libxml2.bin
@@ -333,6 +348,13 @@ let
             perlPackages.ListCompare
             perlPackages.XMLLibXML
             python3Minimal
+            # make-options-doc/default.nix
+            (let
+                self = (pkgs.python3Minimal.override {
+                  inherit self;
+                  includeSiteCustomize = true;
+                });
+              in self.withPackages (p: [ p.mistune ]))
             shared-mime-info
             sudo
             texinfo
@@ -343,8 +365,7 @@ let
             # curl's tarball, we see what it's trying to download
             curl
           ]
-          ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub
-          ++ optionals (bootLoader == "grub" && grubVersion == 2) (let
+          ++ optionals (bootLoader == "grub") (let
             zfsSupport = lib.any (x: x == "zfs")
               (extraInstallerConfig.boot.supportedFilesystems or []);
           in [
@@ -363,7 +384,7 @@ let
 
       testScript = testScriptFun {
         inherit bootLoader createPartitions preBootCommands postBootCommands
-                grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
+                grubDevice grubIdentifier grubUseEfi extraConfig
                 testSpecialisationConfig;
       };
     };
@@ -455,8 +476,12 @@ let
     '';
     testSpecialisationConfig = true;
   };
-
-
+  # disable zfs so we can support latest kernel if needed
+  no-zfs-module = {
+    nixpkgs.overlays = [(final: super: {
+      zfs = super.zfs.overrideAttrs(_: {meta.platforms = [];});}
+    )];
+  };
 in {
 
   # !!! `parted mkpart' seems to silently create overlapping partitions.
@@ -640,6 +665,55 @@ in {
     '';
   };
 
+  # Full disk encryption (root, kernel and initrd encrypted) using GRUB, GPT/UEFI,
+  # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once
+  fullDiskEncryption = makeInstallerTest "fullDiskEncryption" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+          + " mkpart ESP fat32 1M 100MiB"  # /boot/efi
+          + " set 1 boot on"
+          + " mkpart primary ext2 1024MiB -1MiB",  # LUKS
+          "udevadm settle",
+          "modprobe dm_mod dm_crypt",
+          "dd if=/dev/random of=luks.key bs=256 count=1",
+          "echo -n supersecret | cryptsetup luksFormat -q --pbkdf-force-iterations 1000 --type luks1 /dev/vda2 -",
+          "echo -n supersecret | cryptsetup luksAddKey -q --pbkdf-force-iterations 1000 --key-file - /dev/vda2 luks.key",
+          "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda2 crypt",
+          "pvcreate /dev/mapper/crypt",
+          "vgcreate crypt /dev/mapper/crypt",
+          "lvcreate -L 100M -n swap crypt",
+          "lvcreate -l '100%FREE' -n nixos crypt",
+          "mkfs.vfat -n efi /dev/vda1",
+          "mkfs.ext4 -L nixos /dev/crypt/nixos",
+          "mkswap -L swap /dev/crypt/swap",
+          "mount LABEL=nixos /mnt",
+          "mkdir -p /mnt/{etc/nixos,boot/efi}",
+          "mount LABEL=efi /mnt/boot/efi",
+          "swapon -L swap",
+          "mv luks.key /mnt/etc/nixos/"
+      )
+    '';
+    bootLoader = "grub";
+    grubUseEfi = true;
+    extraConfig = ''
+      boot.loader.grub.enableCryptodisk = true;
+      boot.loader.efi.efiSysMountPoint = "/boot/efi";
+
+      boot.initrd.secrets."/luks.key" = ./luks.key;
+      boot.initrd.luks.devices.crypt =
+        { device  = "/dev/vda2";
+          keyFile = "/luks.key";
+        };
+    '';
+    enableOCR = true;
+    preBootCommands = ''
+      machine.start()
+      machine.wait_for_text("Enter passphrase for")
+      machine.send_chars("supersecret\n")
+    '';
+  };
+
   swraid = makeInstallerTest "swraid" {
     createPartitions = ''
       machine.succeed(
@@ -704,6 +778,7 @@ in {
   bcachefsSimple = makeInstallerTest "bcachefs-simple" {
     extraInstallerConfig = {
       boot.supportedFilesystems = [ "bcachefs" ];
+      imports = [ no-zfs-module ];
     };
 
     createPartitions = ''
@@ -727,13 +802,22 @@ in {
   bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
     extraInstallerConfig = {
       boot.supportedFilesystems = [ "bcachefs" ];
+
+      # disable zfs so we can support latest kernel if needed
+      imports = [ no-zfs-module ];
+
       environment.systemPackages = with pkgs; [ keyutils ];
     };
 
-    # We don't want to use the normal way of unlocking bcachefs defined in tasks/filesystems/bcachefs.nix.
-    # So, override initrd.postDeviceCommands completely and simply unlock with the predefined password.
     extraConfig = ''
-      boot.initrd.postDeviceCommands = lib.mkForce "echo password | bcachefs unlock /dev/vda3";
+      boot.kernelParams = lib.mkAfter [ "console=tty0" ];
+    '';
+
+    enableOCR = true;
+    preBootCommands = ''
+      machine.start()
+      machine.wait_for_text("enter passphrase for ")
+      machine.send_chars("password\n")
     '';
 
     createPartitions = ''
@@ -759,6 +843,9 @@ in {
   bcachefsMulti = makeInstallerTest "bcachefs-multi" {
     extraInstallerConfig = {
       boot.supportedFilesystems = [ "bcachefs" ];
+
+      # disable zfs so we can support latest kernel if needed
+      imports = [ no-zfs-module ];
     };
 
     createPartitions = ''
@@ -780,26 +867,6 @@ in {
     '';
   };
 
-  # Test a basic install using GRUB 1.
-  grub1 = makeInstallerTest "grub1" rec {
-    createPartitions = ''
-      machine.succeed(
-          "flock ${grubDevice} parted --script ${grubDevice} -- mklabel msdos"
-          + " mkpart primary linux-swap 1M 1024M"
-          + " mkpart primary ext2 1024M -1s",
-          "udevadm settle",
-          "mkswap ${grubDevice}-part1 -L swap",
-          "swapon -L swap",
-          "mkfs.ext3 -L nixos ${grubDevice}-part2",
-          "mount LABEL=nixos /mnt",
-          "mkdir -p /mnt/tmp",
-      )
-    '';
-    grubVersion = 1;
-    # /dev/sda is not stable, even when the SCSI disk number is.
-    grubDevice = "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive1";
-  };
-
   # Test using labels to identify volumes in grub
   simpleLabels = makeInstallerTest "simpleLabels" {
     createPartitions = ''
@@ -901,4 +968,60 @@ in {
       )
     '';
   };
+
+  # Test to see if we can deal with subvols that need to be escaped in fstab
+  btrfsSubvolEscape = makeInstallerTest "btrfsSubvolEscape" {
+    createPartitions = ''
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.btrfs -L root /dev/vda3",
+          "btrfs device scan",
+          "mount LABEL=root /mnt",
+          "btrfs subvol create '/mnt/nixos in space'",
+          "btrfs subvol create /mnt/boot",
+          "umount /mnt",
+          "mount -o 'defaults,subvol=nixos in space' LABEL=root /mnt",
+          "mkdir /mnt/boot",
+          "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
+      )
+    '';
+  };
+} // optionalAttrs systemdStage1 {
+  stratisRoot = makeInstallerTest "stratisRoot" {
+    createPartitions = ''
+      machine.succeed(
+        "sgdisk --zap-all /dev/vda",
+        "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
+        "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
+        "sgdisk --new=3:0:+5G --typecode=0:8300 /dev/vda", # /
+        "udevadm settle",
+
+        "mkfs.vfat /dev/vda1",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "stratis pool create my-pool /dev/vda3",
+        "stratis filesystem create my-pool nixos",
+        "udevadm settle",
+
+        "mount /dev/stratis/my-pool/nixos /mnt",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot"
+      )
+    '';
+    bootLoader = "systemd-boot";
+    extraInstallerConfig = { modulesPath, ...}: {
+      config = {
+        services.stratis.enable = true;
+        environment.systemPackages = [
+          pkgs.stratis-cli
+          pkgs.thin-provisioning-tools
+          pkgs.lvm2.bin
+          pkgs.stratisd.initrd
+        ];
+      };
+    };
+  };
 }
diff --git a/nixpkgs/nixos/tests/invoiceplane.nix b/nixpkgs/nixos/tests/invoiceplane.nix
index 4e63f8ac21c9..70ed96ee39f3 100644
--- a/nixpkgs/nixos/tests/invoiceplane.nix
+++ b/nixpkgs/nixos/tests/invoiceplane.nix
@@ -13,12 +13,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
       services.invoiceplane.webserver = "caddy";
       services.invoiceplane.sites = {
         "site1.local" = {
-          #database.name = "invoiceplane1";
+          database.name = "invoiceplane1";
           database.createLocally = true;
           enable = true;
         };
         "site2.local" = {
-          #database.name = "invoiceplane2";
+          database.name = "invoiceplane2";
           database.createLocally = true;
           enable = true;
         };
@@ -46,37 +46,37 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
         with subtest("Finish InvoicePlane setup"):
           machine.succeed(
-            f"curl -sSfL --cookie-jar cjar {site_name}/index.php/setup/language"
+            f"curl -sSfL --cookie-jar cjar {site_name}/setup/language"
           )
           csrf_token = machine.succeed(
             "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
           )
           machine.succeed(
-            f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&ip_lang=english&btn_continue=Continue' {site_name}/index.php/setup/language"
+            f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&ip_lang=english&btn_continue=Continue' {site_name}/setup/language"
           )
           csrf_token = machine.succeed(
             "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
           )
           machine.succeed(
-            f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/prerequisites"
+            f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/prerequisites"
           )
           csrf_token = machine.succeed(
             "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
           )
           machine.succeed(
-            f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/configure_database"
+            f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/configure_database"
           )
           csrf_token = machine.succeed(
             "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
           )
           machine.succeed(
-            f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/install_tables"
+            f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/install_tables"
           )
           csrf_token = machine.succeed(
             "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
           )
           machine.succeed(
-            f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/upgrade_tables"
+            f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/upgrade_tables"
           )
   '';
 })
diff --git a/nixpkgs/nixos/tests/ipfs.nix b/nixpkgs/nixos/tests/ipfs.nix
deleted file mode 100644
index 024822745ada..000000000000
--- a/nixpkgs/nixos/tests/ipfs.nix
+++ /dev/null
@@ -1,61 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "ipfs";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ mguentner ];
-  };
-
-  nodes.machine = { ... }: {
-    services.ipfs = {
-      enable = true;
-      # Also will add a unix domain socket socket API address, see module.
-      startWhenNeeded = true;
-      apiAddress = "/ip4/127.0.0.1/tcp/2324";
-      dataDir = "/mnt/ipfs";
-    };
-  };
-
-  nodes.fuse = { ... }: {
-    services.ipfs = {
-      enable = true;
-      apiAddress = "/ip4/127.0.0.1/tcp/2324";
-      autoMount = true;
-    };
-  };
-
-  testScript = ''
-    start_all()
-
-    # IPv4 activation
-
-    machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
-    ipfs_hash = machine.succeed(
-        "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | awk '{ print $2 }'"
-    )
-
-    machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")
-
-    # Unix domain socket activation
-
-    machine.stop_job("ipfs")
-
-    ipfs_hash = machine.succeed(
-        "echo fnord2 | ipfs --api /unix/run/ipfs.sock add | awk '{ print $2 }'"
-    )
-    machine.succeed(
-        f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
-    )
-
-    # Test if setting dataDir works properly with the hardened systemd unit
-    machine.succeed("test -e /mnt/ipfs/config")
-    machine.succeed("test ! -e /var/lib/ipfs/")
-
-    # Test FUSE mountpoint
-    ipfs_hash = fuse.succeed(
-        "echo fnord3 | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter"
-    )
-
-    # The FUSE mount functionality is broken as of v0.13.0.
-    # See https://github.com/ipfs/kubo/issues/9044.
-    # fuse.succeed(f"cat /ipfs/{ipfs_hash.strip()} | grep fnord3")
-  '';
-})
diff --git a/nixpkgs/nixos/tests/isso.nix b/nixpkgs/nixos/tests/isso.nix
index 575e1c52eccf..4ec8b5ec3593 100644
--- a/nixpkgs/nixos/tests/isso.nix
+++ b/nixpkgs/nixos/tests/isso.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "isso";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ asbachb ];
+    maintainers = [ ];
   };
 
   nodes.machine = { config, pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/jackett.nix b/nixpkgs/nixos/tests/jackett.nix
index 0a706c99b999..bc8b724e8b4b 100644
--- a/nixpkgs/nixos/tests/jackett.nix
+++ b/nixpkgs/nixos/tests/jackett.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "jackett";
-  meta.maintainers = with maintainers; [ etu ];
+  meta.maintainers = with lib.maintainers; [ etu ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/jenkins.nix b/nixpkgs/nixos/tests/jenkins.nix
index 3f111426db38..a1ede6dc917b 100644
--- a/nixpkgs/nixos/tests/jenkins.nix
+++ b/nixpkgs/nixos/tests/jenkins.nix
@@ -18,8 +18,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           enable = true;
           jobBuilder = {
             enable = true;
-            accessUser = "admin";
-            accessTokenFile = "/var/lib/jenkins/secrets/initialAdminPassword";
             nixJobs = [
               { job = {
                   name = "job-1";
diff --git a/nixpkgs/nixos/tests/jibri.nix b/nixpkgs/nixos/tests/jibri.nix
index 223120cdb229..45e30af9a9a5 100644
--- a/nixpkgs/nixos/tests/jibri.nix
+++ b/nixpkgs/nixos/tests/jibri.nix
@@ -35,9 +35,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     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(
diff --git a/nixpkgs/nixos/tests/jirafeau.nix b/nixpkgs/nixos/tests/jirafeau.nix
index 0f5af7f718a4..dbfaf515e257 100644
--- a/nixpkgs/nixos/tests/jirafeau.nix
+++ b/nixpkgs/nixos/tests/jirafeau.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "jirafeau";
-  meta.maintainers = with maintainers; [ davidtwco ];
+  meta.maintainers = with lib.maintainers; [ davidtwco ];
 
   nodes.machine = { pkgs, ... }: {
     services.jirafeau = {
diff --git a/nixpkgs/nixos/tests/k3s/default.nix b/nixpkgs/nixos/tests/k3s/default.nix
index 07d93c41c7a6..e168f8233c76 100644
--- a/nixpkgs/nixos/tests/k3s/default.nix
+++ b/nixpkgs/nixos/tests/k3s/default.nix
@@ -1,9 +1,13 @@
 { system ? builtins.currentSystem
 , pkgs ? import ../../.. { inherit system; }
+, lib ? pkgs.lib
 }:
+let
+  allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs;
+in
 {
   # Run a single node k3s cluster and verify a pod can run
-  single-node = import ./single-node.nix { inherit system pkgs; };
+  single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s;
   # Run a multi-node k3s cluster and verify pod networking works across nodes
-  multi-node = import ./multi-node.nix { inherit system pkgs; };
+  multi-node = lib.mapAttrs (_: k3s: import ./multi-node.nix { inherit system pkgs k3s; }) allK3s;
 }
diff --git a/nixpkgs/nixos/tests/k3s/multi-node.nix b/nixpkgs/nixos/tests/k3s/multi-node.nix
index afb8c78f2339..932b4639b39c 100644
--- a/nixpkgs/nixos/tests/k3s/multi-node.nix
+++ b/nixpkgs/nixos/tests/k3s/multi-node.nix
@@ -1,4 +1,4 @@
-import ../make-test-python.nix ({ pkgs, ... }:
+import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
   let
     imageEnv = pkgs.buildEnv {
       name = "k3s-pause-image-env";
@@ -39,7 +39,7 @@ import ../make-test-python.nix ({ pkgs, ... }:
     tokenFile = pkgs.writeText "token" "p@s$w0rd";
   in
   {
-    name = "k3s-multi-node";
+    name = "${k3s.name}-multi-node";
 
     nodes = {
       server = { pkgs, ... }: {
@@ -52,10 +52,19 @@ import ../make-test-python.nix ({ pkgs, ... }:
           inherit tokenFile;
           enable = true;
           role = "server";
-          package = pkgs.k3s;
-          extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local --node-ip 192.168.1.1";
+          package = k3s;
+          clusterInit = true;
+          extraFlags = builtins.toString [
+            "--disable" "coredns"
+            "--disable" "local-storage"
+            "--disable" "metrics-server"
+            "--disable" "servicelb"
+            "--disable" "traefik"
+            "--node-ip" "192.168.1.1"
+            "--pause-image" "test.local/pause:local"
+          ];
         };
-        networking.firewall.allowedTCPPorts = [ 6443 ];
+        networking.firewall.allowedTCPPorts = [ 2379 2380 6443 ];
         networking.firewall.allowedUDPPorts = [ 8472 ];
         networking.firewall.trustedInterfaces = [ "flannel.1" ];
         networking.useDHCP = false;
@@ -65,6 +74,36 @@ import ../make-test-python.nix ({ pkgs, ... }:
         ];
       };
 
+      server2 = { pkgs, ... }: {
+        environment.systemPackages = with pkgs; [ gzip jq ];
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          serverAddr = "https://192.168.1.1:6443";
+          clusterInit = false;
+          extraFlags = builtins.toString [
+            "--disable" "coredns"
+            "--disable" "local-storage"
+            "--disable" "metrics-server"
+            "--disable" "servicelb"
+            "--disable" "traefik"
+            "--node-ip" "192.168.1.3"
+            "--pause-image" "test.local/pause:local"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 2379 2380 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.3";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.3"; prefixLength = 24; }
+        ];
+      };
+
       agent = { pkgs, ... }: {
         virtualisation.memorySize = 1024;
         virtualisation.diskSize = 2048;
@@ -72,8 +111,11 @@ import ../make-test-python.nix ({ pkgs, ... }:
           inherit tokenFile;
           enable = true;
           role = "agent";
-          serverAddr = "https://192.168.1.1:6443";
-          extraFlags = "--pause-image test.local/pause:local --node-ip 192.168.1.2";
+          serverAddr = "https://192.168.1.3:6443";
+          extraFlags = lib.concatStringsSep " " [
+            "--pause-image" "test.local/pause:local"
+            "--node-ip" "192.168.1.2"
+          ];
         };
         networking.firewall.allowedTCPPorts = [ 6443 ];
         networking.firewall.allowedUDPPorts = [ 8472 ];
@@ -91,16 +133,20 @@ import ../make-test-python.nix ({ pkgs, ... }:
     };
 
     testScript = ''
-      start_all()
-      machines = [server, agent]
+      machines = [server, server2, agent]
       for m in machines:
+          m.start()
           m.wait_for_unit("k3s")
 
+      is_aarch64 = "${toString pkgs.stdenv.isAarch64}" == "1"
+
       # wait for the agent to show up
       server.wait_until_succeeds("k3s kubectl get node agent")
 
       for m in machines:
-          m.succeed("k3s check-config")
+          # Fix-Me: Tests fail for 'aarch64-linux' as: "CONFIG_CGROUP_FREEZER: missing (fail)"
+          if not is_aarch64:
+              m.succeed("k3s check-config")
           m.succeed(
               "${pauseImage} | k3s ctr image import -"
           )
diff --git a/nixpkgs/nixos/tests/k3s/single-node.nix b/nixpkgs/nixos/tests/k3s/single-node.nix
index 27e1e455e641..d61595d889e2 100644
--- a/nixpkgs/nixos/tests/k3s/single-node.nix
+++ b/nixpkgs/nixos/tests/k3s/single-node.nix
@@ -1,4 +1,4 @@
-import ../make-test-python.nix ({ pkgs, ... }:
+import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
   let
     imageEnv = pkgs.buildEnv {
       name = "k3s-pause-image-env";
@@ -24,7 +24,7 @@ import ../make-test-python.nix ({ pkgs, ... }:
     '';
   in
   {
-    name = "k3s";
+    name = "${k3s.name}-single-node";
     meta = with pkgs.lib.maintainers; {
       maintainers = [ euank ];
     };
@@ -38,9 +38,16 @@ import ../make-test-python.nix ({ pkgs, ... }:
 
       services.k3s.enable = true;
       services.k3s.role = "server";
-      services.k3s.package = pkgs.k3s;
+      services.k3s.package = k3s;
       # Slightly reduce resource usage
-      services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local";
+      services.k3s.extraFlags = builtins.toString [
+        "--disable" "coredns"
+        "--disable" "local-storage"
+        "--disable" "metrics-server"
+        "--disable" "servicelb"
+        "--disable" "traefik"
+        "--pause-image" "test.local/pause:local"
+      ];
 
       users.users = {
         noprivs = {
@@ -57,7 +64,8 @@ import ../make-test-python.nix ({ pkgs, ... }:
       machine.wait_for_unit("k3s")
       machine.succeed("k3s kubectl cluster-info")
       machine.fail("sudo -u noprivs k3s kubectl cluster-info")
-      machine.succeed("k3s check-config")
+      '' # Fix-Me: Tests fail for 'aarch64-linux' as: "CONFIG_CGROUP_FREEZER: missing (fail)"
+      + lib.optionalString (!pkgs.stdenv.isAarch64) ''machine.succeed("k3s check-config")'' + ''
 
       machine.succeed(
           "${pauseImage} | k3s ctr image import -"
@@ -69,6 +77,9 @@ import ../make-test-python.nix ({ pkgs, ... }:
       machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test")
       machine.succeed("k3s kubectl delete -f ${testPodYaml}")
 
+      # regression test for #176445
+      machine.fail("journalctl -o cat -u k3s.service | grep 'ipset utility not found'")
+
       machine.shutdown()
     '';
   })
diff --git a/nixpkgs/nixos/tests/kafka.nix b/nixpkgs/nixos/tests/kafka.nix
index 5def759ca24d..79af02710c32 100644
--- a/nixpkgs/nixos/tests/kafka.nix
+++ b/nixpkgs/nixos/tests/kafka.nix
@@ -50,7 +50,7 @@ let
 
       kafka.wait_until_succeeds(
           "${kafkaPackage}/bin/kafka-topics.sh --create "
-          + "--zookeeper zookeeper1:2181 --partitions 1 "
+          + "--bootstrap-server localhost:9092 --partitions 1 "
           + "--replication-factor 1 --topic testtopic"
       )
       kafka.succeed(
@@ -58,22 +58,19 @@ let
           + "${kafkaPackage}/bin/kafka-console-producer.sh "
           + "--broker-list localhost:9092 --topic testtopic"
       )
-    '' + (if name == "kafka_0_9" then ''
-      assert "test 1" in kafka.succeed(
-          "${kafkaPackage}/bin/kafka-console-consumer.sh "
-          + "--zookeeper zookeeper1:2181 --topic testtopic "
-          + "--from-beginning --max-messages 1"
-      )
-    '' else ''
       assert "test 1" in kafka.succeed(
           "${kafkaPackage}/bin/kafka-console-consumer.sh "
           + "--bootstrap-server localhost:9092 --topic testtopic "
           + "--from-beginning --max-messages 1"
       )
-    '');
+    '';
   }) { inherit system; });
 
 in with pkgs; {
-  kafka_2_7  = makeKafkaTest "kafka_2_7"  apacheKafka_2_7;
   kafka_2_8  = makeKafkaTest "kafka_2_8"  apacheKafka_2_8;
+  kafka_3_0  = makeKafkaTest "kafka_3_0"  apacheKafka_3_0;
+  kafka_3_1  = makeKafkaTest "kafka_3_1"  apacheKafka_3_1;
+  kafka_3_2  = makeKafkaTest "kafka_3_2"  apacheKafka_3_2;
+  kafka_3_3  = makeKafkaTest "kafka_3_3"  apacheKafka_3_3;
+  kafka  = makeKafkaTest "kafka"  apacheKafka;
 }
diff --git a/nixpkgs/nixos/tests/kanidm.nix b/nixpkgs/nixos/tests/kanidm.nix
index d34f680f5224..7e174590cb5f 100644
--- a/nixpkgs/nixos/tests/kanidm.nix
+++ b/nixpkgs/nixos/tests/kanidm.nix
@@ -13,26 +13,17 @@ import ./make-test-python.nix ({ pkgs, ... }:
         serverSettings = {
           origin = "https://${serverDomain}";
           domain = serverDomain;
-          bindaddress = "[::1]:8443";
+          bindaddress = "[::]:443";
           ldapbindaddress = "[::1]:636";
-        };
-      };
-
-      services.nginx = {
-        enable = true;
-        recommendedProxySettings = true;
-        virtualHosts."${serverDomain}" = {
-          forceSSL = true;
-          sslCertificate = certs."${serverDomain}".cert;
-          sslCertificateKey = certs."${serverDomain}".key;
-          locations."/".proxyPass = "http://[::1]:8443";
+          tls_chain = certs."${serverDomain}".cert;
+          tls_key = certs."${serverDomain}".key;
         };
       };
 
       security.pki.certificateFiles = [ certs.ca.cert ];
 
       networking.hosts."::1" = [ serverDomain ];
-      networking.firewall.allowedTCPPorts = [ 80 443 ];
+      networking.firewall.allowedTCPPorts = [ 443 ];
 
       users.users.kanidm.shell = pkgs.bashInteractive;
 
@@ -44,10 +35,16 @@ import ./make-test-python.nix ({ pkgs, ... }:
         enableClient = true;
         clientSettings = {
           uri = "https://${serverDomain}";
+          verify_ca = true;
+          verify_hostnames = true;
+        };
+        enablePam = true;
+        unixSettings = {
+          pam_allowed_login_groups = [ "shell" ];
         };
       };
 
-      networking.hosts."${nodes.server.config.networking.primaryIPAddress}" = [ serverDomain ];
+      networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ serverDomain ];
 
       security.pki.certificateFiles = [ certs.ca.cert ];
     };
@@ -59,17 +56,34 @@ import ./make-test-python.nix ({ pkgs, ... }:
         # We need access to the config file in the test script.
         filteredConfig = pkgs.lib.converge
           (pkgs.lib.filterAttrsRecursive (_: v: v != null))
-          nodes.server.config.services.kanidm.serverSettings;
+          nodes.server.services.kanidm.serverSettings;
         serverConfigFile = (pkgs.formats.toml { }).generate "server.toml" filteredConfig;
 
       in
       ''
         start_all()
         server.wait_for_unit("kanidm.service")
-        server.wait_until_succeeds("curl -sf https://${serverDomain} | grep Kanidm")
-        server.wait_until_succeeds("ldapsearch -H ldap://[::1]:636 -b '${ldapBaseDN}' -x '(name=test)'")
-        client.wait_until_succeeds("kanidm login -D anonymous && kanidm self whoami | grep anonymous@${serverDomain}")
-        (rv, result) = server.execute("kanidmd recover_account -d quiet -c ${serverConfigFile} -n admin 2>&1 | rg -o '[A-Za-z0-9]{48}'")
-        assert rv == 0
+
+        with subtest("Test HTTP interface"):
+            server.wait_until_succeeds("curl -sf https://${serverDomain} | grep Kanidm")
+
+        with subtest("Test LDAP interface"):
+            server.succeed("ldapsearch -H ldaps://${serverDomain}:636 -b '${ldapBaseDN}' -x '(name=test)'")
+
+        with subtest("Test CLI login"):
+            client.succeed("kanidm login -D anonymous")
+            client.succeed("kanidm self whoami | grep anonymous@${serverDomain}")
+
+        with subtest("Recover idm_admin account"):
+            # Must stop the server for account recovery or else kanidmd fails with
+            # "unable to lock kanidm exclusive lock at /var/lib/kanidm/kanidm.db.klock".
+            server.succeed("systemctl stop kanidm")
+            server.succeed("su - kanidm -c 'kanidmd recover-account -c ${serverConfigFile} idm_admin 2>&1 | rg -o \'[A-Za-z0-9]{48}\' '")
+            server.succeed("systemctl start kanidm")
+
+        with subtest("Test unixd connection"):
+            client.wait_for_unit("kanidm-unixd.service")
+            # TODO: client.wait_for_file("/run/kanidm-unixd/sock")
+            client.wait_until_succeeds("kanidm-unix status | grep working!")
       '';
   })
diff --git a/nixpkgs/nixos/tests/karma.nix b/nixpkgs/nixos/tests/karma.nix
new file mode 100644
index 000000000000..5ac2983b8aa3
--- /dev/null
+++ b/nixpkgs/nixos/tests/karma.nix
@@ -0,0 +1,84 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "karma";
+  nodes = {
+    server = { ... }: {
+      services.prometheus.alertmanager = {
+        enable = true;
+        logLevel = "debug";
+        port = 9093;
+        openFirewall = true;
+        configuration = {
+          global = {
+            resolve_timeout = "1m";
+          };
+          route = {
+            # Root route node
+            receiver = "test";
+            group_by = ["..."];
+            continue = false;
+            group_wait = "1s";
+            group_interval="15s";
+            repeat_interval = "24h";
+          };
+          receivers = [
+            {
+              name = "test";
+              webhook_configs = [
+                {
+                  url = "http://localhost:1234";
+                  send_resolved = true;
+                  max_alerts = 0;
+                }
+              ];
+            }
+          ];
+        };
+      };
+      services.karma = {
+        enable = true;
+        openFirewall = true;
+        settings = {
+          listen = {
+            address = "0.0.0.0";
+            port = 8081;
+          };
+          alertmanager = {
+            servers = [
+              {
+                name = "alertmanager";
+                uri = "https://127.0.0.1:9093";
+              }
+            ];
+          };
+          karma.name = "test-dashboard";
+          log.config = true;
+          log.requests = true;
+          log.timestamp = true;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("Wait for server to come up"):
+
+      server.wait_for_unit("alertmanager.service")
+      server.wait_for_unit("karma.service")
+
+      server.sleep(5) # wait for both services to settle
+
+      server.wait_for_open_port(9093)
+      server.wait_for_open_port(8081)
+
+    with subtest("Test alertmanager readiness"):
+      server.succeed("curl -s http://127.0.0.1:9093/-/ready")
+
+      # Karma only starts serving the dashboard once it has established connectivity to all alertmanagers in its config
+      # Therefore, this will fail if karma isn't able to reach alertmanager
+      server.succeed("curl -s http://127.0.0.1:8081")
+
+    server.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kavita.nix b/nixpkgs/nixos/tests/kavita.nix
new file mode 100644
index 000000000000..f27b3fffbcf6
--- /dev/null
+++ b/nixpkgs/nixos/tests/kavita.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "kavita";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ misterio77 ];
+  };
+
+  nodes = {
+    kavita = { config, pkgs, ... }: {
+      services.kavita = {
+        enable = true;
+        port = 5000;
+        tokenKeyFile = builtins.toFile "kavita.key" "QfpjFvjT83BLtZ74GE3U3Q==";
+      };
+    };
+  };
+
+  testScript = let
+    regUrl = "http://kavita:5000/api/Account/register";
+    payload = builtins.toFile "payload.json" (builtins.toJSON {
+      username = "foo";
+      password = "correcthorsebatterystaple";
+      email = "foo@bar";
+    });
+  in ''
+    kavita.start
+    kavita.wait_for_unit("kavita.service")
+
+    # Check that static assets are working
+    kavita.wait_until_succeeds("curl http://kavita:5000/site.webmanifest | grep Kavita")
+
+    # Check that registration is working
+    kavita.succeed("curl -fX POST ${regUrl} --json @${payload}")
+    # But only for the first one
+    kavita.fail("curl -fX POST ${regUrl} --json @${payload}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kea.nix b/nixpkgs/nixos/tests/kea.nix
index b1d5894cc7cd..b4095893b482 100644
--- a/nixpkgs/nixos/tests/kea.nix
+++ b/nixpkgs/nixos/tests/kea.nix
@@ -1,3 +1,10 @@
+# This test verifies DHCPv4 interaction between a client and a router.
+# For successful DHCP allocations a dynamic update request is sent
+# towards a nameserver to allocate a name in the lan.nixos.test zone.
+# We then verify whether client and router can ping each other, and
+# that the nameserver can resolve the clients fqdn to the correct IP
+# address.
+
 import ./make-test-python.nix ({ pkgs, lib, ...}: {
   meta.maintainers = with lib.maintainers; [ hexa ];
 
@@ -8,17 +15,17 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
       virtualisation.vlans = [ 1 ];
 
       networking = {
-        useNetworkd = true;
         useDHCP = false;
         firewall.allowedUDPPorts = [ 67 ];
       };
 
       systemd.network = {
+        enable = true;
         networks = {
           "01-eth1" = {
             name = "eth1";
             networkConfig = {
-              Address = "10.0.0.1/30";
+              Address = "10.0.0.1/29";
             };
           };
         };
@@ -45,13 +52,115 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
           };
 
           subnet4 = [ {
-            subnet = "10.0.0.0/30";
+            subnet = "10.0.0.0/29";
             pools = [ {
-              pool = "10.0.0.2 - 10.0.0.2";
+              pool = "10.0.0.3 - 10.0.0.3";
             } ];
           } ];
+
+          # Enable communication between dhcp4 and a local dhcp-ddns
+          # instance.
+          # https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp4-srv.html#ddns-for-dhcpv4
+          dhcp-ddns = {
+            enable-updates = true;
+          };
+
+          ddns-send-updates = true;
+          ddns-qualifying-suffix = "lan.nixos.test.";
         };
       };
+
+      services.kea.dhcp-ddns = {
+        enable = true;
+        settings = {
+          forward-ddns = {
+            # Configure updates of a forward zone named `lan.nixos.test`
+            # hosted at the nameserver at 10.0.0.2
+            # https://kea.readthedocs.io/en/kea-2.2.0/arm/ddns.html#adding-forward-dns-servers
+            ddns-domains = [ {
+              name = "lan.nixos.test.";
+              # Use a TSIG key in production!
+              key-name = "";
+              dns-servers = [ {
+                ip-address = "10.0.0.2";
+                port = 53;
+              } ];
+            } ];
+          };
+        };
+      };
+    };
+
+    nameserver = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useDHCP = false;
+        firewall.allowedUDPPorts = [ 53 ];
+      };
+
+      systemd.network = {
+        enable = true;
+        networks = {
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              Address = "10.0.0.2/29";
+            };
+          };
+        };
+      };
+
+      services.resolved.enable = false;
+
+      # Set up an authoritative nameserver, serving the `lan.nixos.test`
+      # zone and configure an ACL that allows dynamic updates from
+      # the router's ip address.
+      # This ACL is likely insufficient for production usage. Please
+      # use TSIG keys.
+      services.knot = let
+        zone = pkgs.writeTextDir "lan.nixos.test.zone" ''
+          @ SOA ns.nixos.test nox.nixos.test 0 86400 7200 3600000 172800
+          @ NS nameserver
+          nameserver A 10.0.0.3
+          router A 10.0.0.1
+        '';
+        zonesDir = pkgs.buildEnv {
+          name = "knot-zones";
+          paths = [ zone ];
+        };
+      in {
+        enable = true;
+        extraArgs = [
+          "-v"
+        ];
+        extraConfig = ''
+          server:
+              listen: 0.0.0.0@53
+
+          log:
+            - target: syslog
+              any: debug
+
+          acl:
+            - id: dhcp_ddns
+              address: 10.0.0.1
+              action: update
+
+          template:
+            - id: default
+              storage: ${zonesDir}
+              zonefile-sync: -1
+              zonefile-load: difference-no-serial
+              journal-content: all
+
+          zone:
+            - domain: lan.nixos.test
+              file: lan.nixos.test.zone
+              acl: [dhcp_ddns]
+        '';
+      };
+
     };
 
     client = { config, pkgs, ... }: {
@@ -70,6 +179,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
     router.wait_for_unit("kea-dhcp4-server.service")
     client.wait_for_unit("systemd-networkd-wait-online.service")
     client.wait_until_succeeds("ping -c 5 10.0.0.1")
-    router.wait_until_succeeds("ping -c 5 10.0.0.2")
+    router.wait_until_succeeds("ping -c 5 10.0.0.3")
+    nameserver.wait_until_succeeds("kdig +short client.lan.nixos.test @10.0.0.2 | grep -q 10.0.0.3")
   '';
 })
diff --git a/nixpkgs/nixos/tests/keepassxc.nix b/nixpkgs/nixos/tests/keepassxc.nix
index 303be1330405..a4f452412cdf 100644
--- a/nixpkgs/nixos/tests/keepassxc.nix
+++ b/nixpkgs/nixos/tests/keepassxc.nix
@@ -4,6 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
   name = "keepassxc";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ turion ];
+    timeout = 1800;
   };
 
   nodes.machine = { ... }:
@@ -17,7 +18,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     services.xserver.enable = true;
 
     # Regression test for https://github.com/NixOS/nixpkgs/issues/163482
-    qt5 = {
+    qt = {
       enable = true;
       platformTheme = "gnome";
       style = "adwaita-dark";
@@ -55,9 +56,12 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.sleep(5)
         # Regression #163482: keepassxc did not crash
         machine.succeed("ps -e | grep keepassxc")
-        machine.wait_for_text("foo.kdbx")
+        machine.wait_for_text("Open database")
         machine.send_key("ret")
-        machine.sleep(1)
+
+        # Wait for the enter password screen to appear.
+        machine.wait_for_text("/home/alice/foo.kdbx")
+
         # Click on "Browse" button to select keyfile
         machine.send_key("tab")
         machine.send_chars("/home/alice/foo.keyfile")
diff --git a/nixpkgs/nixos/tests/kerberos/mit.nix b/nixpkgs/nixos/tests/kerberos/mit.nix
index b475b7e4c92b..7e427ffef0ba 100644
--- a/nixpkgs/nixos/tests/kerberos/mit.nix
+++ b/nixpkgs/nixos/tests/kerberos/mit.nix
@@ -9,7 +9,7 @@ import ../make-test-python.nix ({pkgs, ...}: {
     };
     krb5 = {
       enable = true;
-      kerberos = pkgs.krb5Full;
+      kerberos = pkgs.krb5;
       libdefaults = {
         default_realm = "FOO.BAR";
       };
diff --git a/nixpkgs/nixos/tests/kernel-generic.nix b/nixpkgs/nixos/tests/kernel-generic.nix
index 04d52cf82e42..3e74554de339 100644
--- a/nixpkgs/nixos/tests/kernel-generic.nix
+++ b/nixpkgs/nixos/tests/kernel-generic.nix
@@ -30,7 +30,7 @@ let
       linux_5_4_hardened
       linux_5_10_hardened
       linux_5_15_hardened
-      linux_5_18_hardened
+      linux_6_1_hardened
 
       linux_testing;
   };
diff --git a/nixpkgs/nixos/tests/keycloak.nix b/nixpkgs/nixos/tests/keycloak.nix
index 6ce136330d43..228e57d1cdd6 100644
--- a/nixpkgs/nixos/tests/keycloak.nix
+++ b/nixpkgs/nixos/tests/keycloak.nix
@@ -5,10 +5,13 @@
 let
   certs = import ./common/acme/server/snakeoil-certs.nix;
   frontendUrl = "https://${certs.domain}";
-  initialAdminPassword = "h4IhoJFnt2iQIR9";
 
   keycloakTest = import ./make-test-python.nix (
     { pkgs, databaseType, ... }:
+    let
+      initialAdminPassword = "h4Iho\"JFn't2>iQIR9";
+      adminPasswordFile = pkgs.writeText "admin-password" "${initialAdminPassword}";
+    in
     {
       name = "keycloak";
       meta = with pkgs.lib.maintainers; {
@@ -37,7 +40,7 @@ let
               type = databaseType;
               username = "bogus";
               name = "also bogus";
-              passwordFile = "${pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH"}";
+              passwordFile = "${pkgs.writeText "dbPassword" ''wzf6\"vO"Cb\nP>p#6;c&o?eu=q'THE'''H''''E''}";
             };
             plugins = with config.services.keycloak.package.plugins; [
               keycloak-discord
@@ -111,7 +114,7 @@ let
           keycloak.succeed("""
               curl -sSf -d 'client_id=admin-cli' \
                    -d 'username=admin' \
-                   -d 'password=${initialAdminPassword}' \
+                   -d "password=$(<${adminPasswordFile})" \
                    -d 'grant_type=password' \
                    '${frontendUrl}/realms/master/protocol/openid-connect/token' \
                    | jq -r '"Authorization: bearer " + .access_token' >admin_auth_header
@@ -119,10 +122,10 @@ let
 
           # Register the metrics SPI
           keycloak.succeed(
-              "${pkgs.jre}/bin/keytool -import -alias snakeoil -file ${certs.ca.cert} -storepass aaaaaa -keystore cacert.jks -noprompt",
-              "KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh config credentials --server '${frontendUrl}' --realm master --user admin --password '${initialAdminPassword}'",
-              "KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh update events/config -s 'eventsEnabled=true' -s 'adminEventsEnabled=true' -s 'eventsListeners+=metrics-listener'",
-              "curl -sSf '${frontendUrl}/realms/master/metrics' | grep '^keycloak_admin_event_UPDATE'"
+              """${pkgs.jre}/bin/keytool -import -alias snakeoil -file ${certs.ca.cert} -storepass aaaaaa -keystore cacert.jks -noprompt""",
+              """KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh config credentials --server '${frontendUrl}' --realm master --user admin --password "$(<${adminPasswordFile})" """,
+              """KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh update events/config -s 'eventsEnabled=true' -s 'adminEventsEnabled=true' -s 'eventsListeners+=metrics-listener'""",
+              """curl -sSf '${frontendUrl}/realms/master/metrics' | grep '^keycloak_admin_event_UPDATE'"""
           )
 
           # Publish the realm, including a test OIDC client and user
diff --git a/nixpkgs/nixos/tests/keyd.nix b/nixpkgs/nixos/tests/keyd.nix
new file mode 100644
index 000000000000..d492cc194895
--- /dev/null
+++ b/nixpkgs/nixos/tests/keyd.nix
@@ -0,0 +1,82 @@
+# The test template is taken from the `./keymap.nix`
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  readyFile = "/tmp/readerReady";
+  resultFile = "/tmp/readerResult";
+
+  testReader = pkgs.writeScript "test-input-reader" ''
+    rm -f ${resultFile} ${resultFile}.tmp
+    logger "testReader: START: Waiting for $1 characters, expecting '$2'."
+    touch ${readyFile}
+    read -r -N $1 chars
+    rm -f ${readyFile}
+    if [ "$chars" == "$2" ]; then
+      logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
+    else
+      logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
+    fi
+    # rename after the file is written to prevent a race condition
+    mv  ${resultFile}.tmp ${resultFile}
+  '';
+
+
+  mkKeyboardTest = name: { settings, test }: with pkgs.lib; makeTest {
+    inherit name;
+
+    nodes.machine = {
+      services.keyd = {
+        enable = true;
+        inherit settings;
+      };
+    };
+
+    testScript = ''
+      import shlex
+
+      machine.wait_for_unit("keyd.service")
+
+      def run_test_case(cmd, test_case_name, inputs, expected):
+          with subtest(test_case_name):
+              assert len(inputs) == len(expected)
+              machine.execute("rm -f ${readyFile} ${resultFile}")
+              # set up process that expects all the keys to be entered
+              machine.succeed(
+                  "{} {} {} {} >&2 &".format(
+                      cmd,
+                      "${testReader}",
+                      len(inputs),
+                      shlex.quote("".join(expected)),
+                  )
+              )
+              # wait for reader to be ready
+              machine.wait_for_file("${readyFile}")
+              # send all keys
+              for key in inputs:
+                  machine.send_key(key)
+              # wait for result and check
+              machine.wait_for_file("${resultFile}")
+              machine.succeed("grep -q 'PASS:' ${resultFile}")
+      test = ${builtins.toJSON test}
+      run_test_case("openvt -sw --", "${name}", test["press"], test["expect"])
+    '';
+  };
+
+in
+pkgs.lib.mapAttrs mkKeyboardTest {
+  swap-ab_and_ctrl-as-shift = {
+    test.press = [ "a" "ctrl-b" "c" ];
+    test.expect = [ "b" "A" "c" ];
+
+    settings.main = {
+      "a" = "b";
+      "b" = "a";
+      "control" = "oneshot(shift)";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/keymap.nix b/nixpkgs/nixos/tests/keymap.nix
index 4306a9ae2cf9..0bde21093b0a 100644
--- a/nixpkgs/nixos/tests/keymap.nix
+++ b/nixpkgs/nixos/tests/keymap.nix
@@ -64,7 +64,6 @@ let
 
               # wait for reader to be ready
               machine.wait_for_file("${readyFile}")
-              machine.sleep(1)
 
               # send all keys
               for key in inputs:
@@ -78,9 +77,18 @@ let
       with open("${pkgs.writeText "tests.json" (builtins.toJSON tests)}") as json_file:
           tests = json.load(json_file)
 
+      # These environments used to run in the opposite order, causing the
+      # following error at openvt startup.
+      #
+      # openvt: Couldn't deallocate console 1
+      #
+      # This error did not appear in successful runs.
+      # I don't know the exact cause, but I it seems that openvt and X are
+      # fighting over the virtual terminal. This does not appear to be a problem
+      # when the X test runs first.
       keymap_environments = {
-          "VT Keymap": "openvt -sw --",
           "Xorg Keymap": "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e",
+          "VT Keymap": "openvt -sw --",
       }
 
       machine.wait_for_x()
diff --git a/nixpkgs/nixos/tests/knot.nix b/nixpkgs/nixos/tests/knot.nix
index 203fd03fac26..2ecbf69194bb 100644
--- a/nixpkgs/nixos/tests/knot.nix
+++ b/nixpkgs/nixos/tests/knot.nix
@@ -31,7 +31,7 @@ let
   # DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store!
   tsigFile = pkgs.writeText "tsig.conf" ''
     key:
-      - id: slave_key
+      - id: xfr_key
         algorithm: hmac-sha256
         secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s=
   '';
@@ -43,7 +43,7 @@ in {
 
 
   nodes = {
-    master = { lib, ... }: {
+    primary = { lib, ... }: {
       imports = [ common ];
 
       # trigger sched_setaffinity syscall
@@ -64,22 +64,17 @@ in {
         server:
             listen: 0.0.0.0@53
             listen: ::@53
-
-        acl:
-          - id: slave_acl
-            address: 192.168.0.2
-            key: slave_key
-            action: transfer
+            automatic-acl: true
 
         remote:
-          - id: slave
+          - id: secondary
             address: 192.168.0.2@53
+            key: xfr_key
 
         template:
           - id: default
             storage: ${knotZonesEnv}
-            notify: [slave]
-            acl: [slave_acl]
+            notify: [secondary]
             dnssec-signing: on
             # Input-only zone files
             # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
@@ -105,7 +100,7 @@ in {
       '';
     };
 
-    slave = { lib, ... }: {
+    secondary = { lib, ... }: {
       imports = [ common ];
       networking.interfaces.eth1 = {
         ipv4.addresses = lib.mkForce [
@@ -122,21 +117,16 @@ in {
         server:
             listen: 0.0.0.0@53
             listen: ::@53
-
-        acl:
-          - id: notify_from_master
-            address: 192.168.0.1
-            action: notify
+            automatic-acl: true
 
         remote:
-          - id: master
+          - id: primary
             address: 192.168.0.1@53
-            key: slave_key
+            key: xfr_key
 
         template:
           - id: default
-            master: master
-            acl: [notify_from_master]
+            master: primary
             # zonefileless setup
             # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
             zonefile-sync: -1
@@ -174,19 +164,19 @@ in {
   };
 
   testScript = { nodes, ... }: let
-    master4 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv4.addresses).address;
-    master6 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv6.addresses).address;
+    primary4 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv4.addresses).address;
+    primary6 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv6.addresses).address;
 
-    slave4 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv4.addresses).address;
-    slave6 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv6.addresses).address;
+    secondary4 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv4.addresses).address;
+    secondary6 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv6.addresses).address;
   in ''
     import re
 
     start_all()
 
     client.wait_for_unit("network.target")
-    master.wait_for_unit("knot.service")
-    slave.wait_for_unit("knot.service")
+    primary.wait_for_unit("knot.service")
+    secondary.wait_for_unit("knot.service")
 
 
     def test(host, query_type, query, pattern):
@@ -195,7 +185,7 @@ in {
         assert re.search(pattern, out), f'Did not match "{pattern}"'
 
 
-    for host in ("${master4}", "${master6}", "${slave4}", "${slave6}"):
+    for host in ("${primary4}", "${primary6}", "${secondary4}", "${secondary6}"):
         with subtest(f"Interrogate {host}"):
             test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.")
             test(host, "A", "example.com", r"has no [^ ]+ record")
@@ -211,6 +201,6 @@ 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 '✓'"))
+    primary.log(primary.succeed("systemd-analyze security knot.service | grep -v '✓'"))
   '';
 })
diff --git a/nixpkgs/nixos/tests/komga.nix b/nixpkgs/nixos/tests/komga.nix
index 02db50ef25f7..d48d19bbbdd3 100644
--- a/nixpkgs/nixos/tests/komga.nix
+++ b/nixpkgs/nixos/tests/komga.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "komga";
-  meta.maintainers = with maintainers; [ govanify ];
+  meta.maintainers = with lib.maintainers; [ govanify ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/krb5/example-config.nix b/nixpkgs/nixos/tests/krb5/example-config.nix
index 1125b02f01ca..9a5c3b2af249 100644
--- a/nixpkgs/nixos/tests/krb5/example-config.nix
+++ b/nixpkgs/nixos/tests/krb5/example-config.nix
@@ -11,7 +11,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     { pkgs, ... }: {
       krb5 = {
         enable = true;
-        kerberos = pkgs.krb5Full;
+        kerberos = pkgs.krb5;
         libdefaults = {
           default_realm = "ATHENA.MIT.EDU";
         };
diff --git a/nixpkgs/nixos/tests/kthxbye.nix b/nixpkgs/nixos/tests/kthxbye.nix
new file mode 100644
index 000000000000..5ca0917ec8e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/kthxbye.nix
@@ -0,0 +1,110 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "kthxbye";
+
+  meta = with lib.maintainers; {
+    maintainers = [ nukaduka ];
+  };
+
+  nodes.server = { ... }: {
+    environment.systemPackages = with pkgs; [ prometheus-alertmanager ];
+    services.prometheus = {
+      enable = true;
+
+      globalConfig = {
+        scrape_interval = "5s";
+        scrape_timeout = "5s";
+        evaluation_interval = "5s";
+      };
+
+      scrapeConfigs = [
+        {
+          job_name = "prometheus";
+          scrape_interval = "5s";
+          static_configs = [
+            {
+              targets = [ "localhost:9090" ];
+            }
+          ];
+        }
+      ];
+
+      rules = [
+        ''
+          groups:
+            - name: test
+              rules:
+                - alert: node_up
+                  expr: up != 0
+                  for: 5s
+                  labels:
+                    severity: bottom of the barrel
+                  annotations:
+                    summary: node is fine
+        ''
+      ];
+
+      alertmanagers = [
+        {
+          static_configs = [
+            {
+              targets = [
+                "localhost:9093"
+              ];
+            }
+          ];
+        }
+      ];
+
+      alertmanager = {
+        enable = true;
+        openFirewall = true;
+        configuration.route = {
+          receiver = "test";
+          group_wait = "5s";
+          group_interval = "5s";
+          group_by = [ "..." ];
+        };
+        configuration.receivers = [
+          {
+            name = "test";
+            webhook_configs = [
+              {
+                url = "http://localhost:1234";
+              }
+            ];
+          }
+        ];
+      };
+    };
+
+    services.kthxbye = {
+      enable = true;
+      openFirewall = true;
+      extendIfExpiringIn = "30s";
+      logJSON = true;
+      maxDuration = "15m";
+      interval = "5s";
+    };
+  };
+
+  testScript = ''
+    with subtest("start the server"):
+      start_all()
+      server.wait_for_unit("prometheus.service")
+      server.wait_for_unit("alertmanager.service")
+      server.wait_for_unit("kthxbye.service")
+
+      server.sleep(2) # wait for units to settle
+      server.systemctl("restart kthxbye.service") # make sure kthxbye comes up after alertmanager
+      server.sleep(2)
+
+    with subtest("set up test silence which expires in 20s"):
+      server.succeed('amtool --alertmanager.url "http://localhost:9093" silence add alertname="node_up" -a "nixosTest" -d "20s" -c "ACK! this server is fine!!"')
+
+    with subtest("wait for 21 seconds and check if the silence is still active"):
+      server.sleep(21)
+      server.systemctl("status kthxbye.service")
+      server.succeed("amtool --alertmanager.url 'http://localhost:9093' silence | grep 'ACK'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kubernetes/base.nix b/nixpkgs/nixos/tests/kubernetes/base.nix
index d4410beb937e..ba7b2d9b1d2d 100644
--- a/nixpkgs/nixos/tests/kubernetes/base.nix
+++ b/nixpkgs/nixos/tests/kubernetes/base.nix
@@ -18,7 +18,7 @@ let
         ${master.ip}  api.${domain}
         ${concatMapStringsSep "\n" (machineName: "${machines.${machineName}.ip}  ${machineName}.${domain}") (attrNames machines)}
       '';
-      wrapKubectl = with pkgs; runCommand "wrap-kubectl" { buildInputs = [ makeWrapper ]; } ''
+      wrapKubectl = with pkgs; runCommand "wrap-kubectl" { nativeBuildInputs = [ makeWrapper ]; } ''
         mkdir -p $out/bin
         makeWrapper ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl --set KUBECONFIG "/etc/kubernetes/cluster-admin.kubeconfig"
       '';
@@ -43,7 +43,7 @@ let
                   trustedInterfaces = ["mynet"];
 
                   extraCommands = concatMapStrings  (node: ''
-                    iptables -A INPUT -s ${node.config.networking.primaryIPAddress} -j ACCEPT
+                    iptables -A INPUT -s ${node.networking.primaryIPAddress} -j ACCEPT
                   '') (attrValues nodes);
                 };
               };
diff --git a/nixpkgs/nixos/tests/kubernetes/default.nix b/nixpkgs/nixos/tests/kubernetes/default.nix
index 60ba482758fb..a3de9ed115d4 100644
--- a/nixpkgs/nixos/tests/kubernetes/default.nix
+++ b/nixpkgs/nixos/tests/kubernetes/default.nix
@@ -4,8 +4,6 @@
 let
   dns = import ./dns.nix { inherit system pkgs; };
   rbac = import ./rbac.nix { inherit system pkgs; };
-  # TODO kubernetes.e2e should eventually replace kubernetes.rbac when it works
-  # e2e = import ./e2e.nix { inherit system pkgs; };
 in
 {
   dns-single-node = dns.singlenode.test;
diff --git a/nixpkgs/nixos/tests/kubernetes/dns.nix b/nixpkgs/nixos/tests/kubernetes/dns.nix
index 6299b7ff988a..1b7145eb5d5e 100644
--- a/nixpkgs/nixos/tests/kubernetes/dns.nix
+++ b/nixpkgs/nixos/tests/kubernetes/dns.nix
@@ -69,7 +69,7 @@ let
   extraConfiguration = { config, pkgs, lib, ... }: {
     environment.systemPackages = [ pkgs.bind.host ];
     services.dnsmasq.enable = true;
-    services.dnsmasq.servers = [
+    services.dnsmasq.settings.server = [
       "/cluster.local/${config.services.kubernetes.addons.dns.clusterIp}#53"
     ];
   };
diff --git a/nixpkgs/nixos/tests/kubernetes/e2e.nix b/nixpkgs/nixos/tests/kubernetes/e2e.nix
deleted file mode 100644
index fb29d9cc6953..000000000000
--- a/nixpkgs/nixos/tests/kubernetes/e2e.nix
+++ /dev/null
@@ -1,40 +0,0 @@
-{ system ? builtins.currentSystem, pkgs ? import ../../.. { inherit system; } }:
-with import ./base.nix { inherit system; };
-let
-  domain = "my.zyx";
-  certs = import ./certs.nix { externalDomain = domain; kubelets = ["machine1" "machine2"]; };
-  kubeconfig = pkgs.writeText "kubeconfig.json" (builtins.toJSON {
-    apiVersion = "v1";
-    kind = "Config";
-    clusters = [{
-      name = "local";
-      cluster.certificate-authority = "${certs.master}/ca.pem";
-      cluster.server = "https://api.${domain}";
-    }];
-    users = [{
-      name = "kubelet";
-      user = {
-        client-certificate = "${certs.admin}/admin.pem";
-        client-key = "${certs.admin}/admin-key.pem";
-      };
-    }];
-    contexts = [{
-      context = {
-        cluster = "local";
-        user = "kubelet";
-      };
-      current-context = "kubelet-context";
-    }];
-  });
-
-  base = {
-    name = "e2e";
-    inherit domain certs;
-    test = ''
-      $machine1->succeed("e2e.test -kubeconfig ${kubeconfig} -provider local -ginkgo.focus '\\[Conformance\\]' -ginkgo.skip '\\[Flaky\\]|\\[Serial\\]'");
-    '';
-  };
-in {
-  singlenode = mkKubernetesSingleNodeTest base;
-  multinode = mkKubernetesMultiNodeTest base;
-}
diff --git a/nixpkgs/nixos/tests/kubo.nix b/nixpkgs/nixos/tests/kubo.nix
new file mode 100644
index 000000000000..496f409a40a9
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubo.nix
@@ -0,0 +1,85 @@
+{ lib, ...} : {
+  name = "kubo";
+  meta = with lib.maintainers; {
+    maintainers = [ mguentner Luflosi ];
+  };
+
+  nodes.machine = { config, ... }: {
+    services.kubo = {
+      enable = true;
+      # Also will add a unix domain socket socket API address, see module.
+      startWhenNeeded = true;
+      settings.Addresses.API = "/ip4/127.0.0.1/tcp/2324";
+      dataDir = "/mnt/ipfs";
+    };
+    users.users.alice = {
+      isNormalUser = true;
+      extraGroups = [ config.services.kubo.group ];
+    };
+  };
+
+  nodes.fuse = { config, ... }: {
+    services.kubo = {
+      enable = true;
+      autoMount = true;
+    };
+    users.users.alice = {
+      isNormalUser = true;
+      extraGroups = [ config.services.kubo.group ];
+    };
+    users.users.bob = {
+      isNormalUser = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("Automatic socket activation"):
+        ipfs_hash = machine.succeed(
+            "echo fnord0 | su alice -l -c 'ipfs add --quieter'"
+        )
+        machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord0")
+
+    machine.stop_job("ipfs")
+
+    with subtest("IPv4 socket activation"):
+        machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
+        ipfs_hash = machine.succeed(
+            "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter"
+        )
+        machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")
+
+    machine.stop_job("ipfs")
+
+    with subtest("Unix domain socket activation"):
+        ipfs_hash = machine.succeed(
+            "echo fnord2 | ipfs --api /unix/run/ipfs.sock add --quieter"
+        )
+        machine.succeed(
+            f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
+        )
+
+    with subtest("Setting dataDir works properly with the hardened systemd unit"):
+        machine.succeed("test -e /mnt/ipfs/config")
+        machine.succeed("test ! -e /var/lib/ipfs/")
+
+    with subtest("FUSE mountpoint"):
+        fuse.fail("echo a | su bob -l -c 'ipfs add --quieter'")
+        # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0.
+        # See https://github.com/ipfs/kubo/issues/9044.
+        # Workaround: using CID Version 1 avoids that.
+        ipfs_hash = fuse.succeed(
+            "echo fnord3 | su alice -l -c 'ipfs add --quieter --cid-version=1'"
+        ).strip()
+
+        fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
+
+    with subtest("Unmounting of /ipns and /ipfs"):
+        # Force Kubo to crash and wait for it to restart
+        fuse.systemctl("kill --signal=SIGKILL ipfs.service")
+        fuse.wait_for_unit("ipfs.service", timeout = 30)
+
+        fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ladybird.nix b/nixpkgs/nixos/tests/ladybird.nix
new file mode 100644
index 000000000000..4e9ab9a36d13
--- /dev/null
+++ b/nixpkgs/nixos/tests/ladybird.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ladybird";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [
+      pkgs.ladybird
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.succeed("echo '<!DOCTYPE html><html><body><h1>Hello world</h1></body></html>' > page.html")
+      machine.execute("ladybird file://$(pwd)/page.html >&2 &")
+      machine.wait_for_window("Ladybird")
+      machine.sleep(5)
+      machine.wait_for_text("Hello world")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/languagetool.nix b/nixpkgs/nixos/tests/languagetool.nix
new file mode 100644
index 000000000000..e4ab2a47064e
--- /dev/null
+++ b/nixpkgs/nixos/tests/languagetool.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let port = 8082;
+in {
+  name = "languagetool";
+  meta = with lib.maintainers; { maintainers = [ fbeffa ]; };
+
+  nodes.machine = { ... }:
+    {
+      services.languagetool.enable = true;
+      services.languagetool.port = port;
+    };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("languagetool.service")
+    machine.wait_for_open_port(${toString port})
+    machine.wait_until_succeeds('curl -d "language=en-US" -d "text=a simple test" http://localhost:${toString port}/v2/check')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/legit.nix b/nixpkgs/nixos/tests/legit.nix
new file mode 100644
index 000000000000..3eb3f5035699
--- /dev/null
+++ b/nixpkgs/nixos/tests/legit.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+  port = 5000;
+  scanPath = "/var/lib/legit";
+in
+{
+  name = "legit-web";
+  meta.maintainers = [ lib.maintainers.ratsclub ];
+
+  nodes = {
+    server = { config, pkgs }: {
+      services.legit = {
+        enable = true;
+        settings = {
+          server.port = 5000;
+          repo = { inherit scanPath; };
+        };
+      };
+
+      environment.systemPackages = [ pkgs.git ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      strPort = builtins.toString port;
+    in
+    ''
+      start_all()
+
+      server.wait_for_unit("network.target")
+      server.wait_for_unit("legit.service")
+
+      server.wait_until_succeeds(
+          "curl -f http://localhost:${strPort}"
+      )
+
+      server.succeed("${pkgs.writeShellScript "setup-legit-test-repo" ''
+        set -e
+        git init --bare -b master ${scanPath}/some-repo
+        git init -b master reference
+        cd reference
+        git remote add origin ${scanPath}/some-repo
+        date > date.txt
+        git add date.txt
+        git -c user.name=test -c user.email=test@localhost commit -m 'add date'
+        git push -u origin master
+      ''}")
+
+      server.wait_until_succeeds(
+          "curl -f http://localhost:${strPort}/some-repo"
+      )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/lemmy.nix b/nixpkgs/nixos/tests/lemmy.nix
new file mode 100644
index 000000000000..fb64daa80e64
--- /dev/null
+++ b/nixpkgs/nixos/tests/lemmy.nix
@@ -0,0 +1,89 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  uiPort = 1234;
+  backendPort = 5678;
+  lemmyNodeName = "server";
+in
+{
+  name = "lemmy";
+  meta = with lib.maintainers; { maintainers = [ mightyiam ]; };
+
+  nodes = {
+    client = { };
+
+    "${lemmyNodeName}" = {
+      services.lemmy = {
+        enable = true;
+        ui.port = uiPort;
+        database.createLocally = true;
+        settings = {
+          hostname = "http://${lemmyNodeName}";
+          port = backendPort;
+          # Without setup, the /feeds/* and /nodeinfo/* API endpoints won't return 200
+          setup = {
+            admin_username = "mightyiam";
+            admin_password = "ThisIsWhatIUseEverywhereTryIt";
+            site_name = "Lemmy FTW";
+            admin_email = "mightyiam@example.com";
+          };
+        };
+        caddy.enable = true;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      # pict-rs seems to need more than 1025114112 bytes
+      virtualisation.memorySize = 2000;
+    };
+  };
+
+  testScript = ''
+    server = ${lemmyNodeName}
+
+    with subtest("the backend starts and responds"):
+        server.wait_for_unit("lemmy.service")
+        server.wait_for_open_port(${toString backendPort})
+        server.succeed("curl --fail localhost:${toString backendPort}/api/v3/site")
+
+    with subtest("the UI starts and responds"):
+        server.wait_for_unit("lemmy-ui.service")
+        server.wait_for_open_port(${toString uiPort})
+        server.succeed("curl --fail localhost:${toString uiPort}")
+
+    with subtest("Lemmy-UI responds through the caddy reverse proxy"):
+        server.wait_for_unit("network-online.target")
+        server.wait_for_unit("caddy.service")
+        server.wait_for_open_port(80)
+        body = server.execute("curl --fail --location ${lemmyNodeName}")[1]
+        assert "Lemmy" in body, f"String Lemmy not found in response for ${lemmyNodeName}: \n{body}"
+
+    with subtest("the server is exposed externally"):
+        client.wait_for_unit("network-online.target")
+        client.succeed("curl -v --fail ${lemmyNodeName}")
+
+    with subtest("caddy correctly routes backend requests"):
+        # Make sure we are not hitting frontend
+        server.execute("systemctl stop lemmy-ui.service")
+
+        def assert_http_code(url, expected_http_code, extra_curl_args=""):
+            _, http_code = server.execute(f'curl --silent -o /dev/null {extra_curl_args} --fail --write-out "%{{http_code}}" {url}')
+            assert http_code == str(expected_http_code), f"expected http code {expected_http_code}, got {http_code}"
+
+        # Caddy responds with HTTP code 502 if it cannot handle the requested path
+        assert_http_code("${lemmyNodeName}/obviously-wrong-path/", 502)
+
+        assert_http_code("${lemmyNodeName}/static/js/client.js", 200)
+        assert_http_code("${lemmyNodeName}/api/v3/site", 200)
+
+        # A 404 confirms that the request goes to the backend
+        # No path can return 200 until after we upload an image to pict-rs
+        assert_http_code("${lemmyNodeName}/pictrs/", 404)
+
+        assert_http_code("${lemmyNodeName}/feeds/all.xml", 200)
+        assert_http_code("${lemmyNodeName}/nodeinfo/2.0.json", 200)
+
+        assert_http_code("${lemmyNodeName}/some-other-made-up-path/", 404, "-X POST")
+        assert_http_code("${lemmyNodeName}/some-other-path", 404, "-H 'Accept: application/activity+json'")
+        assert_http_code("${lemmyNodeName}/some-other-path", 404, "-H 'Accept: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/libreddit.nix b/nixpkgs/nixos/tests/libreddit.nix
index 82a44cb4e9cb..ecf347b9e12e 100644
--- a/nixpkgs/nixos/tests/libreddit.nix
+++ b/nixpkgs/nixos/tests/libreddit.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "libreddit";
-  meta.maintainers = with maintainers; [ fab ];
+  meta.maintainers = with lib.maintainers; [ fab ];
 
   nodes.machine = {
     services.libreddit.enable = true;
diff --git a/nixpkgs/nixos/tests/libreswan.nix b/nixpkgs/nixos/tests/libreswan.nix
index ff3d2344a679..aadba941fab1 100644
--- a/nixpkgs/nixos/tests/libreswan.nix
+++ b/nixpkgs/nixos/tests/libreswan.nix
@@ -107,6 +107,8 @@ in
 
       with subtest("Network is up"):
           alice.wait_until_succeeds("ping -c1 bob")
+          alice.succeed("systemctl restart ipsec")
+          bob.succeed("systemctl restart ipsec")
 
       with subtest("Eve can eavesdrop cleartext traffic"):
           eavesdrop()
diff --git a/nixpkgs/nixos/tests/libvirtd.nix b/nixpkgs/nixos/tests/libvirtd.nix
new file mode 100644
index 000000000000..b6e604075997
--- /dev/null
+++ b/nixpkgs/nixos/tests/libvirtd.nix
@@ -0,0 +1,61 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "libvirtd";
+  meta.maintainers = with pkgs.lib.maintainers; [ fpletz ];
+
+  nodes = {
+    virthost =
+      { pkgs, ... }:
+      {
+        virtualisation = {
+          cores = 2;
+          memorySize = 2048;
+
+          libvirtd.enable = true;
+        };
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "deadbeef"; # needed for zfs
+        networking.nameservers = [ "192.168.122.1" ];
+        security.polkit.enable = true;
+        environment.systemPackages = with pkgs; [ virt-manager ];
+      };
+  };
+
+  testScript = let
+    nixosInstallISO = (import ../release.nix {}).iso_minimal.${pkgs.stdenv.hostPlatform.system};
+    virshShutdownCmd = if pkgs.stdenv.isx86_64 then "shutdown" else "destroy";
+  in ''
+    start_all()
+
+    virthost.wait_for_unit("multi-user.target")
+
+    with subtest("enable default network"):
+      virthost.succeed("virsh net-start default")
+      virthost.succeed("virsh net-autostart default")
+      virthost.succeed("virsh net-info default")
+
+    with subtest("check if partition disk pools works with parted"):
+      virthost.succeed("fallocate -l100m /tmp/foo; losetup /dev/loop0 /tmp/foo; echo 'label: dos' | sfdisk /dev/loop0")
+      virthost.succeed("virsh pool-create-as foo disk --source-dev /dev/loop0 --target /dev")
+      virthost.succeed("virsh vol-create-as foo loop0p1 25MB")
+      virthost.succeed("virsh vol-create-as foo loop0p2 50MB")
+
+    with subtest("check if virsh zfs pools work"):
+      virthost.succeed("fallocate -l100m /tmp/zfs; losetup /dev/loop1 /tmp/zfs;")
+      virthost.succeed("zpool create zfs_loop /dev/loop1")
+      virthost.succeed("virsh pool-define-as --name zfs_storagepool --source-name zfs_loop --type zfs")
+      virthost.succeed("virsh pool-start zfs_storagepool")
+      virthost.succeed("virsh vol-create-as zfs_storagepool disk1 25MB")
+
+    with subtest("check if nixos install iso boots, network and autostart works"):
+      virthost.succeed(
+        "virt-install -n nixos --osinfo nixos-unstable --memory 1024 --graphics none --disk `find ${nixosInstallISO}/iso -type f | head -n1`,readonly=on --import --noautoconsole --autostart"
+      )
+      virthost.succeed("virsh domstate nixos | grep running")
+      virthost.wait_until_succeeds("ping -c 1 nixos")
+      virthost.succeed("virsh ${virshShutdownCmd} nixos")
+      virthost.wait_until_succeeds("virsh domstate nixos | grep 'shut off'")
+      virthost.shutdown()
+      virthost.wait_for_unit("multi-user.target")
+      virthost.wait_until_succeeds("ping -c 1 nixos")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lidarr.nix b/nixpkgs/nixos/tests/lidarr.nix
index 7fbaea62f80e..8230dda53736 100644
--- a/nixpkgs/nixos/tests/lidarr.nix
+++ b/nixpkgs/nixos/tests/lidarr.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "lidarr";
-  meta.maintainers = with maintainers; [ etu ];
+  meta.maintainers = with lib.maintainers; [ etu ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/listmonk.nix b/nixpkgs/nixos/tests/listmonk.nix
new file mode 100644
index 000000000000..91003653c09e
--- /dev/null
+++ b/nixpkgs/nixos/tests/listmonk.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "listmonk";
+  meta.maintainers = with lib.maintainers; [ raitobezarius ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.mailhog.enable = true;
+    services.listmonk = {
+      enable = true;
+      settings = {
+        admin_username = "listmonk";
+        admin_password = "hunter2";
+      };
+      database = {
+        createLocally = true;
+        # https://github.com/knadh/listmonk/blob/174a48f252a146d7e69dab42724e3329dbe25ebe/internal/messenger/email/email.go#L18-L27
+        settings.smtp = [ {
+          enabled = true;
+          host = "localhost";
+          port = 1025;
+          tls_type = "none";
+        }];
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    start_all()
+
+    basic_auth = "listmonk:hunter2"
+    def generate_listmonk_request(type, url, data=None):
+       if data is None: data = {}
+       json_data = json.dumps(data)
+       return f'curl -u "{basic_auth}" -X {type} "http://localhost:9000/api/{url}" -H "Content-Type: application/json; charset=utf-8" --data-raw \'{json_data}\'''
+
+    machine.wait_for_unit("mailhog.service")
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_unit("listmonk.service")
+    machine.wait_for_open_port(1025)
+    machine.wait_for_open_port(8025)
+    machine.wait_for_open_port(9000)
+    machine.succeed("[[ -f /var/lib/listmonk/.db_settings_initialized ]]")
+
+    # Test transactional endpoint
+    # subscriber_id=1 is guaranteed to exist at install-time
+    # template_id=2 is guaranteed to exist at install-time and is a transactional template (1 is a campaign template).
+    machine.succeed(
+      generate_listmonk_request('POST', 'tx', data={'subscriber_id': 1, 'template_id': 2})
+    )
+    assert 'Welcome John Doe' in machine.succeed(
+        "curl --fail http://localhost:8025/api/v2/messages"
+    )
+
+    # Test campaign endpoint
+    # Based on https://github.com/knadh/listmonk/blob/174a48f252a146d7e69dab42724e3329dbe25ebe/cmd/campaigns.go#L549 as docs do not exist.
+    campaign_data = json.loads(machine.succeed(
+      generate_listmonk_request('POST', 'campaigns/1/test', data={'template_id': 1, 'subscribers': ['john@example.com'], 'name': 'Test', 'subject': 'NixOS is great', 'lists': [1], 'messenger': 'email'})
+    ))
+
+    assert campaign_data['data']  # This is a boolean asserting if the test was successful or not: https://github.com/knadh/listmonk/blob/174a48f252a146d7e69dab42724e3329dbe25ebe/cmd/campaigns.go#L626
+
+    messages = json.loads(machine.succeed(
+        "curl --fail http://localhost:8025/api/v2/messages"
+    ))
+
+    assert messages['total'] == 2
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lldap.nix b/nixpkgs/nixos/tests/lldap.nix
new file mode 100644
index 000000000000..d6c3a865aa04
--- /dev/null
+++ b/nixpkgs/nixos/tests/lldap.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "lldap";
+
+  nodes.machine = { pkgs, ... }: {
+    services.lldap = {
+      enable = true;
+      settings = {
+        verbose = true;
+        ldap_base_dn = "dc=example,dc=com";
+      };
+    };
+    environment.systemPackages = [ pkgs.openldap ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("lldap.service")
+    machine.wait_for_open_port(3890)
+    machine.wait_for_open_port(17170)
+
+    machine.succeed("curl --location --fail http://localhost:17170/")
+
+    print(
+      machine.succeed('ldapsearch -H ldap://localhost:3890 -D uid=admin,ou=people,dc=example,dc=com -b "ou=people,dc=example,dc=com" -w password')
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/login.nix b/nixpkgs/nixos/tests/login.nix
index 2cff38d20059..67f5764a0a16 100644
--- a/nixpkgs/nixos/tests/login.nix
+++ b/nixpkgs/nixos/tests/login.nix
@@ -13,6 +13,8 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
     };
 
   testScript = ''
+      machine.start(allow_reboot = True)
+
       machine.wait_for_unit("multi-user.target")
       machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
       machine.screenshot("postboot")
@@ -53,7 +55,14 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
           machine.screenshot("getty")
 
       with subtest("Check whether ctrl-alt-delete works"):
-          machine.send_key("ctrl-alt-delete")
-          machine.wait_for_shutdown()
+          boot_id1 = machine.succeed("cat /proc/sys/kernel/random/boot_id").strip()
+          assert boot_id1 != ""
+
+          machine.reboot()
+
+          boot_id2 = machine.succeed("cat /proc/sys/kernel/random/boot_id").strip()
+          assert boot_id2 != ""
+
+          assert boot_id1 != boot_id2
   '';
 })
diff --git a/nixpkgs/nixos/tests/logrotate.nix b/nixpkgs/nixos/tests/logrotate.nix
index b0685f3af9ff..bcbe89c259ae 100644
--- a/nixpkgs/nixos/tests/logrotate.nix
+++ b/nixpkgs/nixos/tests/logrotate.nix
@@ -38,7 +38,7 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
           priority = 2000;
           shred = true;
         };
-        # using mail somewhere should add --mail to logrotate invokation
+        # using mail somewhere should add --mail to logrotate invocation
         sendmail = {
           mail = "user@domain.tld";
         };
@@ -64,29 +64,6 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
           notifempty = true;
         };
       };
-      # extraConfig compatibility - should be added to top level, early.
-      services.logrotate.extraConfig = ''
-        nomail
-      '';
-      # paths compatibility
-      services.logrotate.paths = {
-        compat_path = {
-          path = "compat_test_path";
-        };
-        # user/group should be grouped as 'su user group'
-        compat_user = {
-          user = config.users.users.root.name;
-          group = "root";
-        };
-        # extraConfig in path should be added to block
-        compat_extraConfig = {
-          extraConfig = "dateext";
-        };
-        # keep -> rotate
-        compat_keep = {
-          keep = 1;
-        };
-      };
     };
   };
 
@@ -127,12 +104,6 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
               "sed -ne '/\"postrotate\" {/,/}/p' /tmp/logrotate.conf | grep endscript",
               "grep '\"file1\"\n\"file2\" {' /tmp/logrotate.conf",
               "sed -ne '/\"import\" {/,/}/p' /tmp/logrotate.conf | grep noolddir",
-              "sed -ne '1,/^\"/p' /tmp/logrotate.conf | grep nomail",
-              "grep '\"compat_test_path\" {' /tmp/logrotate.conf",
-              "sed -ne '/\"compat_user\" {/,/}/p' /tmp/logrotate.conf | grep 'su root root'",
-              "sed -ne '/\"compat_extraConfig\" {/,/}/p' /tmp/logrotate.conf | grep dateext",
-              "[[ $(sed -ne '/\"compat_keep\" {/,/}/p' /tmp/logrotate.conf | grep -w rotate) = \"  rotate 1\" ]]",
-              "! sed -ne '/\"compat_keep\" {/,/}/p' /tmp/logrotate.conf | grep -w keep",
           )
           # also check configFile option
           failingMachine.succeed(
diff --git a/nixpkgs/nixos/tests/lorri/default.nix b/nixpkgs/nixos/tests/lorri/default.nix
index 209b87f9f26a..a4bdc92490ce 100644
--- a/nixpkgs/nixos/tests/lorri/default.nix
+++ b/nixpkgs/nixos/tests/lorri/default.nix
@@ -1,4 +1,6 @@
 import ../make-test-python.nix {
+  name = "lorri";
+
   nodes.machine = { pkgs, ... }: {
     imports = [ ../../modules/profiles/minimal.nix ];
     environment.systemPackages = [ pkgs.lorri ];
diff --git a/nixpkgs/nixos/tests/luks.nix b/nixpkgs/nixos/tests/luks.nix
new file mode 100644
index 000000000000..d5ac550a3c57
--- /dev/null
+++ b/nixpkgs/nixos/tests/luks.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "luks";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      useEFIBoot = true;
+      # To boot off the encrypted disk, we need to have a init script which comes from the Nix store
+      mountHostNixStore = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.kernelParams = lib.mkOverride 5 [ "console=tty1" ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation = rec {
+      boot-luks.configuration = {
+        boot.initrd.luks.devices = lib.mkVMOverride {
+          # We have two disks and only type one password - key reuse is in place
+          cryptroot.device = "/dev/vdb";
+          cryptroot2.device = "/dev/vdc";
+        };
+        virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      };
+      boot-luks-custom-keymap.configuration = lib.mkMerge [
+        boot-luks.configuration
+        {
+          console.keyMap = "neo";
+        }
+      ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Passphrase for")
+    machine.send_chars("supersecret\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+
+    # Boot from the encrypted disk with custom keymap
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-custom-keymap.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Passphrase for")
+    machine.send_chars("havfkhfrkfl\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lvm2/default.nix b/nixpkgs/nixos/tests/lvm2/default.nix
index 1eb9f572a4d6..e0358ec2806f 100644
--- a/nixpkgs/nixos/tests/lvm2/default.nix
+++ b/nixpkgs/nixos/tests/lvm2/default.nix
@@ -2,7 +2,7 @@
 , config ? { }
 , pkgs ? import ../../.. { inherit system config; }
 , lib ? pkgs.lib
-, kernelVersionsToTest ? [ "4.19" "5.4" "5.10" "5.15" "latest" ]
+, kernelVersionsToTest ? [ "4.19" "5.4" "5.10" "5.15" "6.1" "latest" ]
 }:
 
 # For quickly running a test, the nixosTests.lvm2.lvm-thinpool-linux-latest attribute is recommended
diff --git a/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix b/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix
index 617ba77b1796..b581f2b23507 100644
--- a/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix
+++ b/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix
@@ -1,18 +1,18 @@
 { kernelPackages ? null, flavour }: let
   preparationCode = {
     raid = ''
-      machine.succeed("vgcreate test_vg /dev/vdc /dev/vdd")
+      machine.succeed("vgcreate test_vg /dev/vdb /dev/vdc")
       machine.succeed("lvcreate -L 512M --type raid0 test_vg -n test_lv")
     '';
 
     thinpool = ''
-      machine.succeed("vgcreate test_vg /dev/vdc")
+      machine.succeed("vgcreate test_vg /dev/vdb")
       machine.succeed("lvcreate -L 512M -T test_vg/test_thin_pool")
       machine.succeed("lvcreate -n test_lv -V 16G --thinpool test_thin_pool test_vg")
     '';
 
     vdo = ''
-      machine.succeed("vgcreate test_vg /dev/vdc")
+      machine.succeed("vgcreate test_vg /dev/vdb")
       machine.succeed("lvcreate --type vdo -n test_lv -L 6G -V 12G test_vg/vdo_pool_lv")
     '';
   }.${flavour};
@@ -65,6 +65,8 @@ in import ../make-test-python.nix ({ pkgs, ... }: {
       emptyDiskImages = [ 8192 8192 ];
       useBootLoader = true;
       useEFIBoot = true;
+      # To boot off the LVM disk, we need to have a init script which comes from the Nix store.
+      mountHostNixStore = true;
     };
     boot.loader.systemd-boot.enable = true;
     boot.loader.efi.canTouchEfiVariables = true;
@@ -79,7 +81,7 @@ in import ../make-test-python.nix ({ pkgs, ... }: {
       kernelPackages = lib.mkIf (kernelPackages != null) kernelPackages;
     };
 
-    specialisation.boot-lvm.configuration.virtualisation.bootDevice = "/dev/test_vg/test_lv";
+    specialisation.boot-lvm.configuration.virtualisation.rootDevice = "/dev/test_vg/test_lv";
   };
 
   testScript = ''
diff --git a/nixpkgs/nixos/tests/lvm2/thinpool.nix b/nixpkgs/nixos/tests/lvm2/thinpool.nix
index 82c6460a890a..14781a8a6045 100644
--- a/nixpkgs/nixos/tests/lvm2/thinpool.nix
+++ b/nixpkgs/nixos/tests/lvm2/thinpool.nix
@@ -1,5 +1,5 @@
 { kernelPackages ? null }:
-import ../make-test-python.nix ({ pkgs, ... }: {
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
   name = "lvm2-thinpool";
   meta.maintainers = with pkgs.lib.maintainers; [ ajs124 ];
 
@@ -17,11 +17,13 @@ import ../make-test-python.nix ({ pkgs, ... }: {
     boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
   };
 
-  testScript = ''
+  testScript = let
+    mkXfsFlags = lib.optionalString (lib.versionOlder kernelPackages.kernel.version "5.10") "-m bigtime=0 -m inobtcount=0";
+  in ''
     machine.succeed("vgcreate test_vg /dev/vdb")
     machine.succeed("lvcreate -L 512M -T test_vg/test_thin_pool")
     machine.succeed("lvcreate -n test_lv -V 16G --thinpool test_thin_pool test_vg")
-    machine.succeed("mkfs.xfs /dev/test_vg/test_lv")
+    machine.succeed("mkfs.xfs ${mkXfsFlags} /dev/test_vg/test_lv")
     machine.succeed("mkdir /mnt; mount /dev/test_vg/test_lv /mnt")
     assert "/dev/mapper/test_vg-test_lv" == machine.succeed("findmnt -no SOURCE /mnt").strip()
     machine.succeed("dd if=/dev/zero of=/mnt/empty.file bs=1M count=1024")
diff --git a/nixpkgs/nixos/tests/lxd-image-server.nix b/nixpkgs/nixos/tests/lxd-image-server.nix
index 072f4570c2c9..e5a292b61bd9 100644
--- a/nixpkgs/nixos/tests/lxd-image-server.nix
+++ b/nixpkgs/nixos/tests/lxd-image-server.nix
@@ -8,8 +8,8 @@ let
     };
   };
 
-  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.system};
-  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.system};
+  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.stdenv.hostPlatform.system};
 
 in {
   name = "lxd-image-server";
diff --git a/nixpkgs/nixos/tests/lxd.nix b/nixpkgs/nixos/tests/lxd.nix
index 15d16564d641..2c2c19e0eecf 100644
--- a/nixpkgs/nixos/tests/lxd.nix
+++ b/nixpkgs/nixos/tests/lxd.nix
@@ -11,8 +11,8 @@ let
     };
   };
 
-  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.system};
-  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.system};
+  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.stdenv.hostPlatform.system};
 
 in {
   name = "lxd";
@@ -23,7 +23,7 @@ in {
 
   nodes.machine = { lib, ... }: {
     virtualisation = {
-      diskSize = 2048;
+      diskSize = 4096;
 
       # Since we're testing `limits.cpu`, we've gotta have a known number of
       # cores to lean on
diff --git a/nixpkgs/nixos/tests/maddy/default.nix b/nixpkgs/nixos/tests/maddy/default.nix
new file mode 100644
index 000000000000..043906863e64
--- /dev/null
+++ b/nixpkgs/nixos/tests/maddy/default.nix
@@ -0,0 +1,6 @@
+{ handleTest }:
+
+{
+  unencrypted = handleTest ./unencrypted.nix { };
+  tls = handleTest ./tls.nix { };
+}
diff --git a/nixpkgs/nixos/tests/maddy/tls.nix b/nixpkgs/nixos/tests/maddy/tls.nix
new file mode 100644
index 000000000000..44da4cf2a3cf
--- /dev/null
+++ b/nixpkgs/nixos/tests/maddy/tls.nix
@@ -0,0 +1,94 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+let
+  certs = import ../common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in {
+  name = "maddy-tls";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    server = { options, ... }: {
+      services.maddy = {
+        enable = true;
+        hostname = domain;
+        primaryDomain = domain;
+        openFirewall = true;
+        ensureAccounts = [ "postmaster@${domain}" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "postmaster@${domain}".passwordFile = "${pkgs.writeText "postmaster" "test"}";
+        };
+        tls = {
+          loader = "file";
+          certificates = [{
+            certPath = "${certs.${domain}.cert}";
+            keyPath = "${certs.${domain}.key}";
+          }];
+        };
+        # Enable TLS listeners. Configuring this via the module is not yet
+        # implemented.
+        config = builtins.replaceStrings [
+          "imap tcp://0.0.0.0:143"
+          "submission tcp://0.0.0.0:587"
+        ] [
+          "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
+          "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
+        ] options.services.maddy.config.default;
+      };
+      # Not covered by openFirewall yet
+      networking.firewall.allowedTCPPorts = [ 993 465 ];
+    };
+
+    client = { nodes, ... }: {
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        ${nodes.server.networking.primaryIPAddress} ${domain}
+     '';
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          import ssl
+          from email.mime.text import MIMEText
+
+          context = ssl.create_default_context()
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "postmaster@${domain}"
+          msg['To'] = "postmaster@${domain}"
+          with smtplib.SMTP_SSL(host='${domain}', port=465, context=context) as smtp:
+              smtp.login('postmaster@${domain}', 'test')
+              smtp.sendmail(
+                'postmaster@${domain}', 'postmaster@${domain}', msg.as_string()
+              )
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('postmaster@${domain}', '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(993)
+    server.wait_for_open_port(587)
+    server.wait_for_open_port(465)
+    client.succeed("send-testmail")
+    client.succeed("test-imap")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/maddy.nix b/nixpkgs/nixos/tests/maddy/unencrypted.nix
index b9d0416482da..2420d461e4e7 100644
--- a/nixpkgs/nixos/tests/maddy.nix
+++ b/nixpkgs/nixos/tests/maddy/unencrypted.nix
@@ -1,5 +1,5 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "maddy";
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "maddy-unencrypted";
   meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
 
   nodes = {
@@ -9,6 +9,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         hostname = "server";
         primaryDomain = "server";
         openFirewall = true;
+        ensureAccounts = [ "postmaster@server" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "postmaster@server".passwordFile = "${pkgs.writeText "postmaster" "test"}";
+        };
       };
     };
 
@@ -48,10 +54,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     server.wait_for_unit("maddy.service")
     server.wait_for_open_port(143)
     server.wait_for_open_port(587)
-
-    server.succeed("maddyctl creds create --password test postmaster@server")
-    server.succeed("maddyctl imap-acct create postmaster@server")
-
     client.succeed("send-testmail")
     client.succeed("test-imap")
   '';
diff --git a/nixpkgs/nixos/tests/mailman.nix b/nixpkgs/nixos/tests/mailman.nix
new file mode 100644
index 000000000000..2806e9166d9a
--- /dev/null
+++ b/nixpkgs/nixos/tests/mailman.nix
@@ -0,0 +1,67 @@
+import ./make-test-python.nix {
+  name = "mailman";
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ mailutils ];
+
+    services.mailman.enable = true;
+    services.mailman.serve.enable = true;
+    services.mailman.siteOwner = "postmaster@example.com";
+    services.mailman.webHosts = [ "example.com" ];
+
+    services.postfix.enable = true;
+    services.postfix.destination = [ "example.com" "example.net" ];
+    services.postfix.relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ];
+    services.postfix.config.local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" "proxy:unix:passwd.byname" ];
+    services.postfix.config.transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+
+    users.users.user = { isNormalUser = true; };
+
+    virtualisation.memorySize = 2048;
+
+    specialisation.restApiPassFileSystem.configuration = {
+      services.mailman.restApiPassFile = "/var/lib/mailman/pass";
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    restApiPassFileSystem = "${nodes.machine.system.build.toplevel}/specialisation/restApiPassFileSystem";
+  in ''
+    def check_mail(_) -> bool:
+        status, _ = machine.execute("grep -q hello /var/spool/mail/user/new/*")
+        return status == 0
+
+    def try_api(_) -> bool:
+        status, _ = machine.execute("curl -s http://localhost:8001/")
+        return status == 0
+
+    def wait_for_api():
+        with machine.nested("waiting for Mailman REST API to be available"):
+            retry(try_api)
+
+    machine.wait_for_unit("mailman.service")
+    wait_for_api()
+
+    with subtest("subscription and delivery"):
+        creds = machine.succeed("su -s /bin/sh -c 'mailman info' mailman | grep '^REST credentials: ' | sed 's/^REST credentials: //'").strip()
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d mail_host=example.com http://localhost:8001/3.1/domains")
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d fqdn_listname=list@example.com http://localhost:8001/3.1/lists")
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d list_id=list.example.com -d subscriber=root@example.com -d pre_confirmed=True -d pre_verified=True -d send_welcome_message=False http://localhost:8001/3.1/members")
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d list_id=list.example.com -d subscriber=user@example.net -d pre_confirmed=True -d pre_verified=True -d send_welcome_message=False http://localhost:8001/3.1/members")
+        machine.succeed("mail -a 'From: root@example.com' -s hello list@example.com < /dev/null")
+        with machine.nested("waiting for mail from list"):
+            retry(check_mail)
+
+    with subtest("Postorius"):
+        machine.succeed("curl --fail-with-body -sILS http://localhost/")
+
+    with subtest("restApiPassFile"):
+        machine.succeed("echo secretpassword > /var/lib/mailman/pass")
+        machine.succeed("${restApiPassFileSystem}/bin/switch-to-configuration test >&2")
+        machine.succeed("grep secretpassword /etc/mailman.cfg")
+        machine.succeed("su -s /bin/sh -c 'mailman info' mailman | grep secretpassword")
+        wait_for_api()
+        machine.succeed("curl --fail-with-body -sLSu restadmin:secretpassword http://localhost:8001/3.1/domains")
+        machine.succeed("curl --fail-with-body -sILS http://localhost/")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/make-test-python.nix b/nixpkgs/nixos/tests/make-test-python.nix
index 7a96f538d8d7..28569f1d2955 100644
--- a/nixpkgs/nixos/tests/make-test-python.nix
+++ b/nixpkgs/nixos/tests/make-test-python.nix
@@ -1,6 +1,6 @@
 f: {
   system ? builtins.currentSystem,
-  pkgs ? import ../.. { inherit system; },
+  pkgs ? import ../.. { inherit system; config = {}; overlays = []; },
   ...
 } @ args:
 
diff --git a/nixpkgs/nixos/tests/mate.nix b/nixpkgs/nixos/tests/mate.nix
new file mode 100644
index 000000000000..78ba59c5fc20
--- /dev/null
+++ b/nixpkgs/nixos/tests/mate.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "mate";
+
+  meta = {
+    maintainers = lib.teams.mate.members;
+  };
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.mate.enable = true;
+
+    # Silence log spam due to no sound drivers loaded:
+    # ALSA lib confmisc.c:855:(parse_card) cannot find card '0'
+    hardware.pulseaudio.enable = true;
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+    ''
+      with subtest("Wait for login"):
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if MATE session components actually start"):
+          machine.wait_until_succeeds("pgrep marco")
+          machine.wait_for_window("marco")
+          machine.wait_until_succeeds("pgrep mate-panel")
+          machine.wait_for_window("Top Panel")
+          machine.wait_for_window("Bottom Panel")
+          machine.wait_until_succeeds("pgrep caja")
+          machine.wait_for_window("Caja")
+
+      with subtest("Open MATE terminal"):
+          machine.succeed("su - ${user.name} -c 'DISPLAY=:0.0 mate-terminal >&2 &'")
+          machine.wait_for_window("Terminal")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/matomo.nix b/nixpkgs/nixos/tests/matomo.nix
index 526a24fc4db7..0e09ad295f95 100644
--- a/nixpkgs/nixos/tests/matomo.nix
+++ b/nixpkgs/nixos/tests/matomo.nix
@@ -7,6 +7,8 @@ with pkgs.lib;
 let
   matomoTest = package:
   makeTest {
+    name = "matomo";
+
     nodes.machine = { config, pkgs, ... }: {
       services.matomo = {
         package = package;
diff --git a/nixpkgs/nixos/tests/matrix/conduit.nix b/nixpkgs/nixos/tests/matrix/conduit.nix
index 780837f962fa..2b81c23598eb 100644
--- a/nixpkgs/nixos/tests/matrix/conduit.nix
+++ b/nixpkgs/nixos/tests/matrix/conduit.nix
@@ -3,6 +3,8 @@ import ../make-test-python.nix ({ pkgs, ... }:
     name = "conduit";
   in
   {
+    name = "matrix-conduit";
+
     nodes = {
       conduit = args: {
         services.matrix-conduit = {
diff --git a/nixpkgs/nixos/tests/matrix/mjolnir.nix b/nixpkgs/nixos/tests/matrix/mjolnir.nix
index cb843e2e9e3e..8a888b17a3d7 100644
--- a/nixpkgs/nixos/tests/matrix/mjolnir.nix
+++ b/nixpkgs/nixos/tests/matrix/mjolnir.nix
@@ -98,6 +98,8 @@ import ../make-test-python.nix (
             enable = true;
             username = "mjolnir";
             passwordFile = pkgs.writeText "password.txt" "mjolnir-password";
+            # otherwise mjolnir tries to connect to ::1, which is not listened by pantalaimon
+            options.listenAddress = "127.0.0.1";
           };
           managementRoom = "#moderators:homeserver";
         };
@@ -106,7 +108,10 @@ import ../make-test-python.nix (
       client = { pkgs, ... }: {
         environment.systemPackages = [
           (pkgs.writers.writePython3Bin "create_management_room_and_invite_mjolnir"
-            { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
+            { libraries = with pkgs.python3Packages; [
+                matrix-nio
+              ] ++ matrix-nio.optional-dependencies.e2e;
+            } ''
             import asyncio
 
             from nio import (
diff --git a/nixpkgs/nixos/tests/matrix/synapse.nix b/nixpkgs/nixos/tests/matrix/synapse.nix
index 756a8d5de49a..698d67c793e3 100644
--- a/nixpkgs/nixos/tests/matrix/synapse.nix
+++ b/nixpkgs/nixos/tests/matrix/synapse.nix
@@ -209,7 +209,7 @@ in {
         "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
     )
     serverpostgres.require_unit_state("postgresql.service")
-    serverpostgres.succeed("register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} ")
+    serverpostgres.succeed("register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} https://localhost:8448/")
     serverpostgres.succeed("obtain-token-and-register-email")
     serversqlite.wait_for_unit("matrix-synapse.service")
     serversqlite.wait_until_succeeds(
diff --git a/nixpkgs/nixos/tests/mattermost.nix b/nixpkgs/nixos/tests/mattermost.nix
index 49b418d9fff7..e11201f05357 100644
--- a/nixpkgs/nixos/tests/mattermost.nix
+++ b/nixpkgs/nixos/tests/mattermost.nix
@@ -50,6 +50,13 @@ in
       mutableConfig = false;
       extraConfig.SupportSettings.HelpLink = "https://search.nixos.org";
     };
+    environmentFile = makeMattermost {
+      mutableConfig = false;
+      extraConfig.SupportSettings.AboutLink = "https://example.org";
+      environmentFile = pkgs.writeText "mattermost-env" ''
+        MM_SUPPORTSETTINGS_ABOUTLINK=https://nixos.org
+      '';
+    };
   };
 
   testScript = let
@@ -69,6 +76,7 @@ in
       rm -f $mattermostConfig
       echo "$newConfig" > "$mattermostConfig"
     '';
+
   in
   ''
     start_all()
@@ -120,5 +128,13 @@ in
 
     # Our edits should be ignored on restart
     immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+
+
+    ## Environment File node tests ##
+    environmentFile.wait_for_unit("mattermost.service")
+    environmentFile.wait_for_open_port(8065)
+
+    # Settings in the environment file should override settings set otherwise
+    environmentFile.succeed("${expectConfig ''.AboutLink == "https://nixos.org"''}")
   '';
 })
diff --git a/nixpkgs/nixos/tests/mediatomb.nix b/nixpkgs/nixos/tests/mediatomb.nix
index b7a126a01ad5..9c84aa3e92a5 100644
--- a/nixpkgs/nixos/tests/mediatomb.nix
+++ b/nixpkgs/nixos/tests/mediatomb.nix
@@ -1,81 +1,44 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-{
+import ./make-test-python.nix {
   name = "mediatomb";
 
   nodes = {
-    serverGerbera =
-      { ... }:
-      let port = 49152;
-      in {
-        imports = [ ../modules/profiles/minimal.nix ];
-        services.mediatomb = {
-          enable = true;
-          serverName = "Gerbera";
-          package = pkgs.gerbera;
-          interface = "eth1";  # accessible from test
-          openFirewall = true;
-          mediaDirectories = [
-            { path = "/var/lib/gerbera/pictures"; recursive = false; hidden-files = false; }
-            { path = "/var/lib/gerbera/audio"; recursive = true; hidden-files = false; }
-          ];
-        };
-      };
-
-    serverMediatomb =
-      { ... }:
-      let port = 49151;
-      in {
-        imports = [ ../modules/profiles/minimal.nix ];
-        services.mediatomb = {
-          enable = true;
-          serverName = "Mediatomb";
-          package = pkgs.mediatomb;
-          interface = "eth1";
-          inherit port;
-          mediaDirectories = [
-            { path = "/var/lib/mediatomb/pictures"; recursive = false; hidden-files = false; }
-            { path = "/var/lib/mediatomb/audio"; recursive = true; hidden-files = false; }
-          ];
-        };
-        networking.firewall.interfaces.eth1 = {
-          allowedUDPPorts = [ 1900 port ];
-          allowedTCPPorts = [ port ];
-        };
+    server = {
+      services.mediatomb = {
+        enable = true;
+        serverName = "Gerbera";
+        interface = "eth1";
+        openFirewall = true;
+        mediaDirectories = [
+          {
+            path = "/var/lib/gerbera/pictures";
+            recursive = false;
+            hidden-files = false;
+          }
+          {
+            path = "/var/lib/gerbera/audio";
+            recursive = true;
+            hidden-files = false;
+          }
+        ];
       };
+      systemd.tmpfiles.rules = [
+        "d /var/lib/gerbera/pictures 0770 mediatomb mediatomb"
+        "d /var/lib/gerbera/audio 0770 mediatomb mediatomb"
+      ];
+    };
 
-      client = { ... }: { };
+    client = {};
   };
 
-  testScript =
-  ''
+  testScript = ''
     start_all()
 
-    port = 49151
-    serverMediatomb.succeed("mkdir -p /var/lib/mediatomb/{pictures,audio}")
-    serverMediatomb.succeed("chown -R mediatomb:mediatomb /var/lib/mediatomb")
-    serverMediatomb.wait_for_unit("mediatomb")
-    serverMediatomb.wait_for_open_port(port)
-    serverMediatomb.succeed(f"curl --fail http://serverMediatomb:{port}/")
-    page = client.succeed(f"curl --fail http://serverMediatomb:{port}/")
-    assert "MediaTomb" in page and "Gerbera" not in page
-    serverMediatomb.shutdown()
+    server.wait_for_unit("mediatomb")
+    server.wait_until_succeeds("nc -z 192.168.1.2 49152")
+    server.succeed("curl -v --fail http://server:49152/")
 
-    port = 49152
-    serverGerbera.succeed("mkdir -p /var/lib/mediatomb/{pictures,audio}")
-    serverGerbera.succeed("chown -R mediatomb:mediatomb /var/lib/mediatomb")
-    # service running gerbera fails the first time claiming something is already bound
-    # gerbera[715]: 2020-07-18 23:52:14   info: Please check if another instance of Gerbera or
-    # gerbera[715]: 2020-07-18 23:52:14   info: another application is running on port TCP 49152 or UDP 1900.
-    # I did not find anything so here I work around this
-    serverGerbera.succeed("sleep 2")
-    serverGerbera.wait_until_succeeds("systemctl restart mediatomb")
-    serverGerbera.wait_for_unit("mediatomb")
-    serverGerbera.succeed(f"curl --fail http://serverGerbera:{port}/")
-    page = client.succeed(f"curl --fail http://serverGerbera:{port}/")
+    client.wait_for_unit("multi-user.target")
+    page = client.succeed("curl -v --fail http://server:49152/")
     assert "Gerbera" in page and "MediaTomb" not in page
-
-    serverGerbera.shutdown()
-    client.shutdown()
   '';
-})
+}
diff --git a/nixpkgs/nixos/tests/mediawiki.nix b/nixpkgs/nixos/tests/mediawiki.nix
index 7f31d6aadfa2..52122755ad94 100644
--- a/nixpkgs/nixos/tests/mediawiki.nix
+++ b/nixpkgs/nixos/tests/mediawiki.nix
@@ -1,28 +1,77 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
-  name = "mediawiki";
-  meta.maintainers = [ lib.maintainers.aanderse ];
-
-  nodes.machine =
-    { ... }:
-    { services.mediawiki.enable = true;
-      services.mediawiki.virtualHost.hostName = "localhost";
-      services.mediawiki.virtualHost.adminAddr = "root@example.com";
-      services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
-      services.mediawiki.extensions = {
-        Matomo = pkgs.fetchzip {
-          url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
-          sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b";
-        };
-        ParserFunctions = null;
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+}:
+
+let
+  shared = {
+    services.mediawiki.enable = true;
+    services.mediawiki.httpd.virtualHost.hostName = "localhost";
+    services.mediawiki.httpd.virtualHost.adminAddr = "root@example.com";
+    services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
+    services.mediawiki.extensions = {
+      Matomo = pkgs.fetchzip {
+        url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
+        sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b";
       };
+      ParserFunctions = null;
+    };
+  };
+
+  testLib = import ../lib/testing-python.nix {
+    inherit system pkgs;
+    extraConfigurations = [ shared ];
+  };
+in
+{
+  mysql = testLib.makeTest {
+    name = "mediawiki-mysql";
+    nodes.machine = {
+      services.mediawiki.database.type = "mysql";
+    };
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+
+      page = machine.succeed("curl -fL http://localhost/")
+      assert "MediaWiki has been installed" in page
+    '';
+  };
+
+  postgresql = testLib.makeTest {
+    name = "mediawiki-postgres";
+    nodes.machine = {
+      services.mediawiki.database.type = "postgres";
     };
+    testScript = ''
+      start_all()
 
-  testScript = ''
-    start_all()
+      machine.wait_for_unit("phpfpm-mediawiki.service")
 
-    machine.wait_for_unit("phpfpm-mediawiki.service")
+      page = machine.succeed("curl -fL http://localhost/")
+      assert "MediaWiki has been installed" in page
+    '';
+  };
 
-    page = machine.succeed("curl -fL http://localhost/")
-    assert "MediaWiki has been installed" in page
-  '';
-})
+  nohttpd = testLib.makeTest {
+    name = "mediawiki-nohttpd";
+    nodes.machine = {
+      services.mediawiki.webserver = "none";
+    };
+    testScript = { nodes, ... }: ''
+      start_all()
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+      env = (
+        "SCRIPT_NAME=/index.php",
+        "SCRIPT_FILENAME=${nodes.machine.services.mediawiki.finalPackage}/share/mediawiki/index.php",
+        "REMOTE_ADDR=127.0.0.1",
+        'QUERY_STRING=title=Main_Page',
+        "REQUEST_METHOD=GET",
+      );
+      page = machine.succeed(f"{' '.join(env)} ${pkgs.fcgi}/bin/cgi-fcgi -bind -connect ${nodes.machine.services.phpfpm.pools.mediawiki.socket}")
+      assert "MediaWiki has been installed" in page, f"no 'MediaWiki has been installed' in:\n{page}"
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/meilisearch.nix b/nixpkgs/nixos/tests/meilisearch.nix
index 6d7514a1cc0a..c31dcb0559db 100644
--- a/nixpkgs/nixos/tests/meilisearch.nix
+++ b/nixpkgs/nixos/tests/meilisearch.nix
@@ -35,20 +35,20 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
       with subtest("create index"):
           machine.succeed(
-              "curl -XPOST --header 'Content-Type: application/json' ${apiUrl}/indexes --data @${indexJSON}"
+              "curl -X POST -H 'Content-Type: application/json' ${apiUrl}/indexes --data @${indexJSON}"
           )
           indexes = json.loads(machine.succeed("curl ${apiUrl}/indexes"))
-          assert len(indexes) == 1, "index wasn't created"
+          assert indexes["total"] == 1, "index wasn't created"
 
       with subtest("add documents"):
           response = json.loads(
               machine.succeed(
-                  "curl -XPOST --header 'Content-Type: application/json' ${apiUrl}/indexes/${uid}/documents --data @${moviesJSON}"
+                  "curl -X POST -H 'Content-Type: application/json' ${apiUrl}/indexes/${uid}/documents --data-binary @${moviesJSON}"
               )
           )
-          update_id = response["updateId"]
+          task_uid = response["taskUid"]
           machine.wait_until_succeeds(
-              f"curl ${apiUrl}/indexes/${uid}/updates/{update_id} | jq -e '.status == \"processed\"'"
+              f"curl ${apiUrl}/tasks/{task_uid} | jq -e '.status == \"succeeded\"'"
           )
 
       with subtest("search"):
diff --git a/nixpkgs/nixos/tests/merecat.nix b/nixpkgs/nixos/tests/merecat.nix
new file mode 100644
index 000000000000..9d8f66165ee9
--- /dev/null
+++ b/nixpkgs/nixos/tests/merecat.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "merecat";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.merecat = {
+      enable = true;
+      settings = {
+        hostname = "localhost";
+        virtual-host = true;
+        directory = toString (pkgs.runCommand "merecat-webdir" {} ''
+          mkdir -p $out/foo.localhost $out/bar.localhost
+          echo '<h1>Hello foo</h1>' > $out/foo.localhost/index.html
+          echo '<h1>Hello bar</h1>' > $out/bar.localhost/index.html
+        '');
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("merecat")
+    machine.wait_for_open_port(80)
+    machine.succeed("curl --fail foo.localhost | grep 'Hello foo'")
+    machine.succeed("curl --fail bar.localhost | grep 'Hello bar'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mindustry.nix b/nixpkgs/nixos/tests/mindustry.nix
new file mode 100644
index 000000000000..b3f5423c601b
--- /dev/null
+++ b/nixpkgs/nixos/tests/mindustry.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "mindustry";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.mindustry ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("mindustry >&2 &")
+      machine.wait_for_window("Mindustry")
+      # Loading can take a while. Avoid wasting cycles on OCR during that time
+      machine.sleep(60)
+      machine.wait_for_text(r"(Play|Database|Editor|Mods|Settings|Quit)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/minecraft-server.nix b/nixpkgs/nixos/tests/minecraft-server.nix
index a51b36ff5308..6e733bb96c1c 100644
--- a/nixpkgs/nixos/tests/minecraft-server.nix
+++ b/nixpkgs/nixos/tests/minecraft-server.nix
@@ -18,6 +18,8 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
       serverProperties = {
         enable-rcon = true;
         level-seed = seed;
+        level-type = "flat";
+        generate-structures = false;
         online-mode = false;
         "rcon.password" = rcon-pass;
         "rcon.port" = rcon-port;
diff --git a/nixpkgs/nixos/tests/minidlna.nix b/nixpkgs/nixos/tests/minidlna.nix
index 76039b0bb42c..32721819634e 100644
--- a/nixpkgs/nixos/tests/minidlna.nix
+++ b/nixpkgs/nixos/tests/minidlna.nix
@@ -6,25 +6,24 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       { ... }:
       {
         imports = [ ../modules/profiles/minimal.nix ];
-        networking.firewall.allowedTCPPorts = [ 8200 ];
-        services.minidlna = {
-          enable = true;
-          loglevel = "error";
-          mediaDirs = [
-           "PV,/tmp/stuff"
+        services.minidlna.enable = true;
+        services.minidlna.openFirewall = true;
+        services.minidlna.settings = {
+          log_level = "error";
+          media_dir = [
+            "PV,/tmp/stuff"
+          ];
+          friendly_name = "rpi3";
+          root_container = "B";
+          notify_interval = 60;
+          album_art_names = [
+            "Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg"
+            "AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg"
+            "Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg"
           ];
-          friendlyName = "rpi3";
-          rootContainer = "B";
-          extraConfig =
-          ''
-            album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg
-            album_art_names=AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg
-            album_art_names=Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg
-            notify_interval=60
-          '';
         };
       };
-      client = { ... }: { };
+    client = { ... }: { };
   };
 
   testScript =
diff --git a/nixpkgs/nixos/tests/miniflux.nix b/nixpkgs/nixos/tests/miniflux.nix
index d905aea048a3..be3e7abb6abd 100644
--- a/nixpkgs/nixos/tests/miniflux.nix
+++ b/nixpkgs/nixos/tests/miniflux.nix
@@ -17,10 +17,9 @@ let
           '';
 
 in
-with lib;
 {
   name = "miniflux";
-  meta.maintainers = with pkgs.lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   nodes = {
     default =
diff --git a/nixpkgs/nixos/tests/minio.nix b/nixpkgs/nixos/tests/minio.nix
index ad51f738d490..ece4864f771c 100644
--- a/nixpkgs/nixos/tests/minio.nix
+++ b/nixpkgs/nixos/tests/minio.nix
@@ -1,5 +1,5 @@
-import ./make-test-python.nix ({ pkgs, ...} :
-let
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
     accessKey = "BKIKJAA5BMMU2RHO6IBB";
     secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
     minioPythonScript = pkgs.writeScript "minio-test.py" ''
@@ -18,41 +18,55 @@ let
       sio.seek(0)
       minioClient.put_object('test-bucket', 'test.txt', sio, sio_len, content_type='text/plain')
     '';
-in {
-  name = "minio";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ bachp ];
-  };
+    rootCredentialsFile = "/etc/nixos/minio-root-credentials";
+    credsPartial = pkgs.writeText "minio-credentials-partial" ''
+      MINIO_ROOT_USER=${accessKey}
+    '';
+    credsFull = pkgs.writeText "minio-credentials-full" ''
+      MINIO_ROOT_USER=${accessKey}
+      MINIO_ROOT_PASSWORD=${secretKey}
+    '';
+  in
+  {
+    name = "minio";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bachp ];
+    };
 
-  nodes = {
-    machine = { pkgs, ... }: {
-      services.minio = {
-        enable = true;
-        rootCredentialsFile = pkgs.writeText "minio-credentials" ''
-          MINIO_ROOT_USER=${accessKey}
-          MINIO_ROOT_PASSWORD=${secretKey}
-        '';
-      };
-      environment.systemPackages = [ pkgs.minio-client ];
+    nodes = {
+      machine = { pkgs, ... }: {
+        services.minio = {
+          enable = true;
+          inherit rootCredentialsFile;
+        };
+        environment.systemPackages = [ pkgs.minio-client ];
 
-      # Minio requires at least 1GiB of free disk space to run.
-      virtualisation.diskSize = 4 * 1024;
+        # Minio requires at least 1GiB of free disk space to run.
+        virtualisation.diskSize = 4 * 1024;
+      };
     };
-  };
 
-  testScript = ''
-    start_all()
-    machine.wait_for_unit("minio.service")
-    machine.wait_for_open_port(9000)
+    testScript = ''
+      import time
+
+      start_all()
+      # simulate manually editing root credentials file
+      machine.wait_for_unit("multi-user.target")
+      machine.copy_from_host("${credsPartial}", "${rootCredentialsFile}")
+      time.sleep(3)
+      machine.copy_from_host("${credsFull}", "${rootCredentialsFile}")
 
-    # Create a test bucket on the server
-    machine.succeed(
-        "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
-    )
-    machine.succeed("mc mb minio/test-bucket")
-    machine.succeed("${minioPythonScript}")
-    assert "test-bucket" in machine.succeed("mc ls minio")
-    assert "Test from Python" in machine.succeed("mc cat minio/test-bucket/test.txt")
-    machine.shutdown()
-  '';
-})
+      machine.wait_for_unit("minio.service")
+      machine.wait_for_open_port(9000)
+
+      # Create a test bucket on the server
+      machine.succeed(
+          "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
+      )
+      machine.succeed("mc mb minio/test-bucket")
+      machine.succeed("${minioPythonScript}")
+      assert "test-bucket" in machine.succeed("mc ls minio")
+      assert "Test from Python" in machine.succeed("mc cat minio/test-bucket/test.txt")
+      machine.shutdown()
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/miriway.nix b/nixpkgs/nixos/tests/miriway.nix
new file mode 100644
index 000000000000..9000e9278197
--- /dev/null
+++ b/nixpkgs/nixos/tests/miriway.nix
@@ -0,0 +1,125 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "miriway";
+
+  meta = {
+    maintainers = with lib.maintainers; [ OPNA2608 ];
+  };
+
+  nodes.machine = { config, ... }: {
+    imports = [
+      ./common/auto.nix
+      ./common/user-account.nix
+    ];
+
+    # Seems to very rarely get interrupted by oom-killer
+    virtualisation.memorySize = 2047;
+
+    test-support.displayManager.auto = {
+      enable = true;
+      user = "alice";
+    };
+
+    services.xserver = {
+      enable = true;
+      displayManager.defaultSession = lib.mkForce "miriway";
+    };
+
+    programs.miriway = {
+      enable = true;
+      config = ''
+        add-wayland-extensions=all
+        enable-x11=
+
+        ctrl-alt=t:foot --maximized
+        ctrl-alt=a:env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY=invalid alacritty --option window.startup_mode=maximized
+
+        shell-component=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+
+        shell-component=foot --maximized
+      '';
+    };
+
+    environment = {
+      shellAliases = {
+        test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
+        test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
+      };
+
+      systemPackages = with pkgs; [
+        mesa-demos
+        wayland-utils
+        foot
+        alacritty
+      ];
+
+      # To help with OCR
+      etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
+        main = {
+          font = "inconsolata:size=16";
+        };
+        colors = rec {
+          foreground = "000000";
+          background = "ffffff";
+          regular2 = foreground;
+        };
+      };
+      etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
+        font = rec {
+          normal.family = "Inconsolata";
+          bold.family = normal.family;
+          italic.family = normal.family;
+          bold_italic.family = normal.family;
+          size = 16;
+        };
+        colors = rec {
+          primary = {
+            foreground = "0x000000";
+            background = "0xffffff";
+          };
+          normal = {
+            green = primary.foreground;
+          };
+        };
+      };
+    };
+
+    fonts.fonts = [ pkgs.inconsolata ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    # Wait for Miriway to complete startup
+    machine.wait_for_file("/run/user/1000/wayland-0")
+    machine.succeed("pgrep miriway-shell")
+    machine.screenshot("miriway_launched")
+
+    # Test Wayland
+    # We let Miriway start the first terminal, as we might get stuck if it's not ready to process the first keybind
+    # machine.send_key("ctrl-alt-t")
+    machine.wait_for_text("alice@machine")
+    machine.send_chars("test-wayland\n")
+    machine.wait_for_file("/tmp/test-wayland-exit-ok")
+    machine.copy_from_vm("/tmp/test-wayland.out")
+    machine.screenshot("foot_wayland_info")
+    # Only succeeds when a mouse is moved inside an interactive session?
+    # machine.send_chars("exit\n")
+    # machine.wait_until_fails("pgrep foot")
+    machine.succeed("pkill foot")
+
+    # Test XWayland
+    machine.send_key("ctrl-alt-a")
+    machine.wait_for_text("alice@machine")
+    machine.send_chars("test-x11\n")
+    machine.wait_for_file("/tmp/test-x11-exit-ok")
+    machine.copy_from_vm("/tmp/test-x11.out")
+    machine.screenshot("alacritty_glinfo")
+    # Only succeeds when a mouse is moved inside an interactive session?
+    # machine.send_chars("exit\n")
+    # machine.wait_until_fails("pgrep alacritty")
+    machine.succeed("pkill alacritty")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/misc.nix b/nixpkgs/nixos/tests/misc.nix
index 0d5f0fe2f044..442b45948c60 100644
--- a/nixpkgs/nixos/tests/misc.nix
+++ b/nixpkgs/nixos/tests/misc.nix
@@ -1,20 +1,17 @@
 # Miscellaneous small tests that don't warrant their own VM run.
 
-import ./make-test-python.nix ({ pkgs, ...} : let
+import ./make-test-python.nix ({ lib, pkgs, ...} : let
   foo = pkgs.writeText "foo" "Hello World";
 in {
   name = "misc";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ eelco ];
-  };
+  meta.maintainers = with lib.maintainers; [ eelco ];
 
   nodes.machine =
     { lib, ... }:
-    with lib;
-    { swapDevices = mkOverride 0
+    { swapDevices = lib.mkOverride 0
         [ { device = "/root/swapfile"; size = 128; } ];
-      environment.variables.EDITOR = mkOverride 0 "emacs";
-      documentation.nixos.enable = mkOverride 0 true;
+      environment.variables.EDITOR = lib.mkOverride 0 "emacs";
+      documentation.nixos.enable = lib.mkOverride 0 true;
       systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
       virtualisation.fileSystems = { "/tmp2" =
         { fsType = "tmpfs";
@@ -32,7 +29,7 @@ in {
           options = [ "bind" "rw" "noauto" ];
         };
       };
-      systemd.automounts = singleton
+      systemd.automounts = lib.singleton
         { wantedBy = [ "multi-user.target" ];
           where = "/tmp2";
         };
diff --git a/nixpkgs/nixos/tests/mongodb.nix b/nixpkgs/nixos/tests/mongodb.nix
index edd074f5163c..75b0c4c2ab2b 100644
--- a/nixpkgs/nixos/tests/mongodb.nix
+++ b/nixpkgs/nixos/tests/mongodb.nix
@@ -33,10 +33,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
     nodes = {
       node = {...}: {
         environment.systemPackages = with pkgs; [
-          mongodb-3_4
-          mongodb-3_6
-          mongodb-4_0
-          mongodb-4_2
           mongodb-4_4
           mongodb-5_0
         ];
@@ -46,10 +42,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
     testScript = ''
       node.start()
     ''
-      + runMongoDBTest pkgs.mongodb-3_4
-      + runMongoDBTest pkgs.mongodb-3_6
-      + runMongoDBTest pkgs.mongodb-4_0
-      + runMongoDBTest pkgs.mongodb-4_2
       + runMongoDBTest pkgs.mongodb-4_4
       + runMongoDBTest pkgs.mongodb-5_0
       + ''
diff --git a/nixpkgs/nixos/tests/moodle.nix b/nixpkgs/nixos/tests/moodle.nix
index 4570e8963882..8fd011e0cb21 100644
--- a/nixpkgs/nixos/tests/moodle.nix
+++ b/nixpkgs/nixos/tests/moodle.nix
@@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
   testScript = ''
     start_all()
-    machine.wait_for_unit("phpfpm-moodle.service")
+    machine.wait_for_unit("phpfpm-moodle.service", timeout=1800)
     machine.wait_until_succeeds("curl http://localhost/ | grep 'You are not logged in'")
   '';
 })
diff --git a/nixpkgs/nixos/tests/mosquitto.nix b/nixpkgs/nixos/tests/mosquitto.nix
index d516d3373d9f..8eca4f259225 100644
--- a/nixpkgs/nixos/tests/mosquitto.nix
+++ b/nixpkgs/nixos/tests/mosquitto.nix
@@ -4,7 +4,7 @@ let
   port = 1888;
   tlsPort = 1889;
   anonPort = 1890;
-  bindTestPort = 1891;
+  bindTestPort = 18910;
   password = "VERY_secret";
   hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
   topic = "test/foo";
@@ -66,6 +66,7 @@ in {
   in {
     server = { pkgs, ... }: {
       networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ];
+      networking.useNetworkd = true;
       services.mosquitto = {
         enable = true;
         settings = {
@@ -165,6 +166,10 @@ in {
         for t in threads: t.start()
         for t in threads: t.join()
 
+    def wait_uuid(uuid):
+        server.wait_for_console_text(uuid)
+        return None
+
 
     start_all()
     server.wait_for_unit("mosquitto.service")
@@ -203,14 +208,14 @@ in {
         parallel(
             lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")),
             lambda: [
-                server.wait_for_console_text("3688cdd7-aa07-42a4-be22-cb9352917e40"),
+                wait_uuid("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"),
+                wait_uuid("24ff16a2-ae33-4a51-9098-1b417153c712"),
                 client2.succeed(publish("-m test", "reader"))
             ])
 
@@ -229,7 +234,7 @@ in {
             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"),
+                wait_uuid("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 a4803f3cb5b5..32a81cbe2495 100644
--- a/nixpkgs/nixos/tests/mpv.nix
+++ b/nixpkgs/nixos/tests/mpv.nix
@@ -1,13 +1,11 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 let
   port = toString 4321;
 in
 {
   name = "mpv";
-  meta.maintainers = with maintainers; [ zopieux ];
+  meta.maintainers = with lib.maintainers; [ zopieux ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/multipass.nix b/nixpkgs/nixos/tests/multipass.nix
new file mode 100644
index 000000000000..0980e9195f5a
--- /dev/null
+++ b/nixpkgs/nixos/tests/multipass.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  multipass-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    };
+  };
+
+in
+{
+  name = "multipass";
+
+  meta.maintainers = [ lib.maintainers.jnsgruk ];
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      cores = 1;
+      memorySize = 1024;
+      diskSize = 4096;
+
+      multipass.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("multipass.service")
+    machine.wait_for_file("/var/lib/multipass/data/multipassd/network/multipass_subnet")
+
+    # Wait for Multipass to settle
+    machine.sleep(1)
+
+    machine.succeed("multipass list")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/musescore.nix b/nixpkgs/nixos/tests/musescore.nix
index 18de0a550239..6aeb0558a49d 100644
--- a/nixpkgs/nixos/tests/musescore.nix
+++ b/nixpkgs/nixos/tests/musescore.nix
@@ -2,13 +2,12 @@ import ./make-test-python.nix ({ pkgs, ...} :
 
 let
   # Make sure we don't have to go through the startup tutorial
-  customMuseScoreConfig = pkgs.writeText "MuseScore3.ini" ''
+  customMuseScoreConfig = pkgs.writeText "MuseScore4.ini" ''
     [application]
-    startup\firstStart=false
+    hasCompletedFirstLaunchSetup=true
 
-    [ui]
-    application\startup\showTours=false
-    application\startup\showStartCenter=false
+    [project]
+    preferredScoreCreationMode=1
     '';
 in
 {
@@ -40,26 +39,43 @@ in
     # Inject custom settings
     machine.succeed("mkdir -p /root/.config/MuseScore/")
     machine.succeed(
-        "cp ${customMuseScoreConfig} /root/.config/MuseScore/MuseScore3.ini"
+        "cp ${customMuseScoreConfig} /root/.config/MuseScore/MuseScore4.ini"
     )
 
     # Start MuseScore window
     machine.execute("DISPLAY=:0.0 mscore >&2 &")
 
     # Wait until MuseScore has launched
-    machine.wait_for_window("MuseScore")
+    machine.wait_for_window("MuseScore 4")
 
     # Wait until the window has completely initialised
-    machine.wait_for_text("MuseScore")
+    machine.wait_for_text("MuseScore 4")
+
+    machine.screenshot("MuseScore0")
+
+    # Create a new score
+    machine.send_key("ctrl-n")
+
+    # Wait until the creation wizard appears
+    machine.wait_for_window("New score")
+
+    machine.screenshot("MuseScore1")
+
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.send_key("right")
+    machine.send_key("right")
+    machine.send_key("ret")
+
+    machine.sleep(1)
 
-    # Start entering notes
-    machine.send_key("n")
     # Type the beginning of https://de.wikipedia.org/wiki/Alle_meine_Entchen
     machine.send_chars("cdef6gg5aaaa7g")
-    # Make sure the VM catches up with all the keys
     machine.sleep(1)
 
-    machine.screenshot("MuseScore0")
+    machine.screenshot("MuseScore2")
 
     # Go to the export dialogue and create a PDF
     machine.send_key("alt-f")
@@ -67,20 +83,24 @@ in
     machine.send_key("e")
 
     # Wait until the export dialogue appears.
-    machine.wait_for_window("Export")
-    machine.screenshot("MuseScore1")
+    machine.wait_for_text("Export")
+
+    machine.screenshot("MuseScore3")
+
+    machine.send_key("shift-tab")
+    machine.sleep(1)
     machine.send_key("ret")
     machine.sleep(1)
     machine.send_key("ret")
 
-    machine.screenshot("MuseScore2")
+    machine.screenshot("MuseScore4")
 
     # Wait until PDF is exported
-    machine.wait_for_file("/root/Documents/MuseScore3/Scores/Untitled.pdf")
+    machine.wait_for_file('"/root/Documents/MuseScore4/Scores/Untitled score.pdf"')
 
     # Check that it contains the title of the score
-    machine.succeed("pdfgrep Title /root/Documents/MuseScore3/Scores/Untitled.pdf")
+    machine.succeed('pdfgrep "Untitled score" "/root/Documents/MuseScore4/Scores/Untitled score.pdf"')
 
-    machine.screenshot("MuseScore3")
+    machine.screenshot("MuseScore5")
   '';
 })
diff --git a/nixpkgs/nixos/tests/mysql/common.nix b/nixpkgs/nixos/tests/mysql/common.nix
index 040d360b6d99..7fdf0f33d3f3 100644
--- a/nixpkgs/nixos/tests/mysql/common.nix
+++ b/nixpkgs/nixos/tests/mysql/common.nix
@@ -1,10 +1,7 @@
 { lib, pkgs }: {
-  mariadbPackages = lib.filterAttrs (n: _: lib.hasPrefix "mariadb" n) (pkgs.callPackage ../../../pkgs/servers/sql/mariadb {
-    inherit (pkgs.darwin) cctools;
-    inherit (pkgs.darwin.apple_sdk.frameworks) CoreServices;
-  });
+  mariadbPackages = lib.filterAttrs (n: _: lib.hasPrefix "mariadb" n) (import ../../../pkgs/servers/sql/mariadb pkgs);
   mysqlPackages = {
-    inherit (pkgs) mysql57 mysql80;
+    inherit (pkgs) mysql80;
   };
   mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}";
 }
diff --git a/nixpkgs/nixos/tests/mysql/mysql-replication.nix b/nixpkgs/nixos/tests/mysql/mysql-replication.nix
index f6014019bd53..8f1695eb97e2 100644
--- a/nixpkgs/nixos/tests/mysql/mysql-replication.nix
+++ b/nixpkgs/nixos/tests/mysql/mysql-replication.nix
@@ -42,7 +42,7 @@ let
           enable = true;
           replication.role = "slave";
           replication.serverId = 2;
-          replication.masterHost = nodes.primary.config.networking.hostName;
+          replication.masterHost = nodes.primary.networking.hostName;
           replication.masterUser = replicateUser;
           replication.masterPassword = replicatePassword;
         };
@@ -54,7 +54,7 @@ let
           enable = true;
           replication.role = "slave";
           replication.serverId = 3;
-          replication.masterHost = nodes.primary.config.networking.hostName;
+          replication.masterHost = nodes.primary.networking.hostName;
           replication.masterUser = replicateUser;
           replication.masterPassword = replicatePassword;
         };
diff --git a/nixpkgs/nixos/tests/mysql/mysql.nix b/nixpkgs/nixos/tests/mysql/mysql.nix
index 197e6da80e24..6ddc49f86f7c 100644
--- a/nixpkgs/nixos/tests/mysql/mysql.nix
+++ b/nixpkgs/nixos/tests/mysql/mysql.nix
@@ -15,7 +15,7 @@ let
     name ? mkTestName package,
     useSocketAuth ? true,
     hasMroonga ? true,
-    hasRocksDB ? true
+    hasRocksDB ? pkgs.stdenv.hostPlatform.is64bit
   }: makeTest {
     inherit name;
     meta = with lib.maintainers; {
diff --git a/nixpkgs/nixos/tests/n8n.nix b/nixpkgs/nixos/tests/n8n.nix
index c1753a418f67..771296bf37a5 100644
--- a/nixpkgs/nixos/tests/n8n.nix
+++ b/nixpkgs/nixos/tests/n8n.nix
@@ -1,13 +1,10 @@
 import ./make-test-python.nix ({ lib, ... }:
-
-with lib;
-
 let
   port = 5678;
 in
 {
   name = "n8n";
-  meta.maintainers = with maintainers; [ freezeboy k900 ];
+  meta.maintainers = with lib.maintainers; [ freezeboy k900 ];
 
   nodes.machine =
     { pkgs, ... }:
@@ -19,7 +16,7 @@ in
 
   testScript = ''
     machine.wait_for_unit("n8n.service")
-    machine.wait_for_open_port(${toString port})
-    machine.succeed("curl --fail http://localhost:${toString port}/")
+    machine.wait_for_console_text("Editor is now accessible via")
+    machine.succeed("curl --fail -vvv http://localhost:${toString port}/")
   '';
 })
diff --git a/nixpkgs/nixos/tests/nar-serve.nix b/nixpkgs/nixos/tests/nar-serve.nix
index bb95ccb36911..f6197567b822 100644
--- a/nixpkgs/nixos/tests/nar-serve.nix
+++ b/nixpkgs/nixos/tests/nar-serve.nix
@@ -27,6 +27,8 @@ import ./make-test-python.nix (
         };
       };
     testScript = ''
+      import os
+
       start_all()
 
       # Create a fake cache with Nginx service the static files
diff --git a/nixpkgs/nixos/tests/nat.nix b/nixpkgs/nixos/tests/nat.nix
index 545eb46f2bf5..0b617cea7774 100644
--- a/nixpkgs/nixos/tests/nat.nix
+++ b/nixpkgs/nixos/tests/nat.nix
@@ -3,26 +3,24 @@
 # client on the inside network, a server on the outside network, and a
 # router connected to both that performs Network Address Translation
 # for the client.
-import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, ... }:
+import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ... }:
   let
-    unit = if withFirewall then "firewall" else "nat";
+    unit = if nftables then "nftables" else (if withFirewall then "firewall" else "nat");
 
     routerBase =
       lib.mkMerge [
         { virtualisation.vlans = [ 2 1 ];
           networking.firewall.enable = withFirewall;
+          networking.firewall.filterForward = nftables;
+          networking.nftables.enable = nftables;
           networking.nat.internalIPs = [ "192.168.1.0/24" ];
           networking.nat.externalInterface = "eth1";
         }
-        (lib.optionalAttrs withConntrackHelpers {
-          networking.firewall.connectionTrackingModules = [ "ftp" ];
-          networking.firewall.autoLoadConntrackHelpers = true;
-        })
       ];
   in
   {
-    name = "nat" + (if withFirewall then "WithFirewall" else "Standalone")
-                 + (lib.optionalString withConntrackHelpers "withConntrackHelpers");
+    name = "nat" + (lib.optionalString nftables "Nftables")
+                 + (if withFirewall then "WithFirewall" else "Standalone");
     meta = with pkgs.lib.maintainers; {
       maintainers = [ eelco rob ];
     };
@@ -34,11 +32,8 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ?
             { virtualisation.vlans = [ 1 ];
               networking.defaultGateway =
                 (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+              networking.nftables.enable = nftables;
             }
-            (lib.optionalAttrs withConntrackHelpers {
-              networking.firewall.connectionTrackingModules = [ "ftp" ];
-              networking.firewall.autoLoadConntrackHelpers = true;
-            })
           ];
 
         router =
@@ -91,7 +86,7 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ?
         client.succeed("curl -v ftp://server/foo.txt >&2")
 
         # Test whether active FTP works.
-        client.${if withConntrackHelpers then "succeed" else "fail"}("curl -v -P - ftp://server/foo.txt >&2")
+        client.fail("curl -v -P - ftp://server/foo.txt >&2")
 
         # Test ICMP.
         client.succeed("ping -c 1 router >&2")
@@ -111,7 +106,7 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ?
         # FIXME: this should not be necessary, but nat.service is not started because
         #        network.target is not triggered
         #        (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359)
-        ${lib.optionalString (!withFirewall) ''
+        ${lib.optionalString (!withFirewall && !nftables) ''
           router.succeed("systemctl start nat.service")
         ''}
         client.succeed("curl --fail http://server/ >&2")
diff --git a/nixpkgs/nixos/tests/nebula.nix b/nixpkgs/nixos/tests/nebula.nix
index 372cfebdf801..89b91d89fcb3 100644
--- a/nixpkgs/nixos/tests/nebula.nix
+++ b/nixpkgs/nixos/tests/nebula.nix
@@ -10,6 +10,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let
       environment.systemPackages = [ pkgs.nebula ];
       users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
       services.openssh.enable = true;
+      networking.interfaces.eth1.useDHCP = false;
 
       services.nebula.networks.smoke = {
         # Note that these paths won't exist when the machine is first booted.
@@ -30,13 +31,14 @@ in
 
     lighthouse = { ... } @ args:
       makeNebulaNode args "lighthouse" {
-        networking.interfaces.eth1.ipv4.addresses = [{
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
           address = "192.168.1.1";
           prefixLength = 24;
         }];
 
         services.nebula.networks.smoke = {
           isLighthouse = true;
+          isRelay = true;
           firewall = {
             outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
             inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
@@ -44,9 +46,9 @@ in
         };
       };
 
-    node2 = { ... } @ args:
-      makeNebulaNode args "node2" {
-        networking.interfaces.eth1.ipv4.addresses = [{
+    allowAny = { ... } @ args:
+      makeNebulaNode args "allowAny" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
           address = "192.168.1.2";
           prefixLength = 24;
         }];
@@ -55,6 +57,7 @@ in
           staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
           isLighthouse = false;
           lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
           firewall = {
             outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
             inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
@@ -62,9 +65,9 @@ in
         };
       };
 
-    node3 = { ... } @ args:
-      makeNebulaNode args "node3" {
-        networking.interfaces.eth1.ipv4.addresses = [{
+    allowFromLighthouse = { ... } @ args:
+      makeNebulaNode args "allowFromLighthouse" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
           address = "192.168.1.3";
           prefixLength = 24;
         }];
@@ -73,6 +76,7 @@ in
           staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
           isLighthouse = false;
           lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
           firewall = {
             outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
             inbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ];
@@ -80,9 +84,9 @@ in
         };
       };
 
-    node4 = { ... } @ args:
-      makeNebulaNode args "node4" {
-        networking.interfaces.eth1.ipv4.addresses = [{
+    allowToLighthouse = { ... } @ args:
+      makeNebulaNode args "allowToLighthouse" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
           address = "192.168.1.4";
           prefixLength = 24;
         }];
@@ -92,6 +96,7 @@ in
           staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
           isLighthouse = false;
           lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
           firewall = {
             outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ];
             inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
@@ -99,9 +104,9 @@ in
         };
       };
 
-    node5 = { ... } @ args:
-      makeNebulaNode args "node5" {
-        networking.interfaces.eth1.ipv4.addresses = [{
+    disabled = { ... } @ args:
+      makeNebulaNode args "disabled" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
           address = "192.168.1.5";
           prefixLength = 24;
         }];
@@ -111,6 +116,7 @@ in
           staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
           isLighthouse = false;
           lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
           firewall = {
             outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ];
             inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
@@ -123,12 +129,14 @@ in
   testScript = let
 
     setUpPrivateKey = name: ''
-    ${name}.succeed(
-        "mkdir -p /root/.ssh",
-        "chown 700 /root/.ssh",
-        "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil",
-        "chown 600 /root/.ssh/id_snakeoil",
-    )
+      ${name}.start()
+      ${name}.succeed(
+          "mkdir -p /root/.ssh",
+          "chown 700 /root/.ssh",
+          "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil",
+          "chown 600 /root/.ssh/id_snakeoil",
+          "mkdir -p /root"
+      )
     '';
 
     # From what I can tell, StrictHostKeyChecking=no is necessary for ssh to work between machines.
@@ -146,26 +154,48 @@ in
       ${name}.succeed(
           "mkdir -p /etc/nebula",
           "nebula-cert keygen -out-key /etc/nebula/${name}.key -out-pub /etc/nebula/${name}.pub",
-          "scp ${sshOpts} /etc/nebula/${name}.pub 192.168.1.1:/tmp/${name}.pub",
+          "scp ${sshOpts} /etc/nebula/${name}.pub root@192.168.1.1:/root/${name}.pub",
       )
       lighthouse.succeed(
-          'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /tmp/${name}.pub -out-crt /tmp/${name}.crt',
+          'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /root/${name}.pub -out-crt /root/${name}.crt'
       )
       ${name}.succeed(
-          "scp ${sshOpts} 192.168.1.1:/tmp/${name}.crt /etc/nebula/${name}.crt",
-          "scp ${sshOpts} 192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt",
+          "scp ${sshOpts} root@192.168.1.1:/root/${name}.crt /etc/nebula/${name}.crt",
+          "scp ${sshOpts} root@192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt",
+          '(id nebula-smoke >/dev/null && chown -R nebula-smoke:nebula-smoke /etc/nebula) || true'
       )
     '';
 
-  in ''
-    start_all()
+    getPublicIp = node: ''
+      ${node}.succeed("ip --brief addr show eth1 | awk '{print $3}' | tail -n1 | cut -d/ -f1").strip()
+    '';
 
+    # Never do this for anything security critical! (Thankfully it's just a test.)
+    # Restart Nebula right after the mutual block and/or restore so the state is fresh.
+    blockTrafficBetween = nodeA: nodeB: ''
+      node_a = ${getPublicIp nodeA}
+      node_b = ${getPublicIp nodeB}
+      ${nodeA}.succeed("iptables -I INPUT -s " + node_b + " -j DROP")
+      ${nodeB}.succeed("iptables -I INPUT -s " + node_a + " -j DROP")
+      ${nodeA}.systemctl("restart nebula@smoke.service")
+      ${nodeB}.systemctl("restart nebula@smoke.service")
+    '';
+    allowTrafficBetween = nodeA: nodeB: ''
+      node_a = ${getPublicIp nodeA}
+      node_b = ${getPublicIp nodeB}
+      ${nodeA}.succeed("iptables -D INPUT -s " + node_b + " -j DROP")
+      ${nodeB}.succeed("iptables -D INPUT -s " + node_a + " -j DROP")
+      ${nodeA}.systemctl("restart nebula@smoke.service")
+      ${nodeB}.systemctl("restart nebula@smoke.service")
+    '';
+  in ''
     # Create the certificate and sign the lighthouse's keys.
     ${setUpPrivateKey "lighthouse"}
     lighthouse.succeed(
         "mkdir -p /etc/nebula",
         'nebula-cert ca -name "Smoke Test" -out-crt /etc/nebula/ca.crt -out-key /etc/nebula/ca.key',
         'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "lighthouse" -groups "lighthouse" -ip "10.0.100.1/24" -out-crt /etc/nebula/lighthouse.crt -out-key /etc/nebula/lighthouse.key',
+        'chown -R nebula-smoke:nebula-smoke /etc/nebula'
     )
 
     # Reboot the lighthouse and verify that the nebula service comes up on boot.
@@ -175,49 +205,104 @@ in
     lighthouse.wait_for_unit("nebula@smoke.service")
     lighthouse.succeed("ping -c5 10.0.100.1")
 
-    # Create keys for node2's nebula service and test that it comes up.
-    ${setUpPrivateKey "node2"}
-    ${signKeysFor "node2" "10.0.100.2/24"}
-    ${restartAndCheckNebula "node2" "10.0.100.2"}
+    # Create keys for allowAny's nebula service and test that it comes up.
+    ${setUpPrivateKey "allowAny"}
+    ${signKeysFor "allowAny" "10.0.100.2/24"}
+    ${restartAndCheckNebula "allowAny" "10.0.100.2"}
 
-    # Create keys for node3's nebula service and test that it comes up.
-    ${setUpPrivateKey "node3"}
-    ${signKeysFor "node3" "10.0.100.3/24"}
-    ${restartAndCheckNebula "node3" "10.0.100.3"}
+    # Create keys for allowFromLighthouse's nebula service and test that it comes up.
+    ${setUpPrivateKey "allowFromLighthouse"}
+    ${signKeysFor "allowFromLighthouse" "10.0.100.3/24"}
+    ${restartAndCheckNebula "allowFromLighthouse" "10.0.100.3"}
 
-    # Create keys for node4's nebula service and test that it comes up.
-    ${setUpPrivateKey "node4"}
-    ${signKeysFor "node4" "10.0.100.4/24"}
-    ${restartAndCheckNebula "node4" "10.0.100.4"}
+    # Create keys for allowToLighthouse's nebula service and test that it comes up.
+    ${setUpPrivateKey "allowToLighthouse"}
+    ${signKeysFor "allowToLighthouse" "10.0.100.4/24"}
+    ${restartAndCheckNebula "allowToLighthouse" "10.0.100.4"}
 
-    # Create keys for node4's nebula service and test that it does not come up.
-    ${setUpPrivateKey "node5"}
-    ${signKeysFor "node5" "10.0.100.5/24"}
-    node5.fail("systemctl status nebula@smoke.service")
-    node5.fail("ping -c5 10.0.100.5")
+    # Create keys for disabled's nebula service and test that it does not come up.
+    ${setUpPrivateKey "disabled"}
+    ${signKeysFor "disabled" "10.0.100.5/24"}
+    disabled.fail("systemctl status nebula@smoke.service")
+    disabled.fail("ping -c5 10.0.100.5")
 
-    # The lighthouse can ping node2 and node3 but not node5
+    # The lighthouse can ping allowAny and allowFromLighthouse but not disabled
     lighthouse.succeed("ping -c3 10.0.100.2")
     lighthouse.succeed("ping -c3 10.0.100.3")
     lighthouse.fail("ping -c3 10.0.100.5")
 
-    # node2 can ping the lighthouse, but not node3 because of its inbound firewall
-    node2.succeed("ping -c3 10.0.100.1")
-    node2.fail("ping -c3 10.0.100.3")
-
-    # node3 can ping the lighthouse and node2
-    node3.succeed("ping -c3 10.0.100.1")
-    node3.succeed("ping -c3 10.0.100.2")
-
-    # node4 can ping the lighthouse but not node2 or node3
-    node4.succeed("ping -c3 10.0.100.1")
-    node4.fail("ping -c3 10.0.100.2")
-    node4.fail("ping -c3 10.0.100.3")
-
-    # node2 can ping node3 now that node3 pinged it first
-    node2.succeed("ping -c3 10.0.100.3")
-    # node4 can ping node2 if node2 pings it first
-    node2.succeed("ping -c3 10.0.100.4")
-    node4.succeed("ping -c3 10.0.100.2")
+    # allowAny can ping the lighthouse, but not allowFromLighthouse because of its inbound firewall
+    allowAny.succeed("ping -c3 10.0.100.1")
+    allowAny.fail("ping -c3 10.0.100.3")
+
+    # allowFromLighthouse can ping the lighthouse and allowAny
+    allowFromLighthouse.succeed("ping -c3 10.0.100.1")
+    allowFromLighthouse.succeed("ping -c3 10.0.100.2")
+
+    # block allowFromLighthouse <-> allowAny, and allowFromLighthouse -> allowAny should still work.
+    ${blockTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+    ${allowTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+
+    # allowToLighthouse can ping the lighthouse but not allowAny or allowFromLighthouse
+    allowToLighthouse.succeed("ping -c3 10.0.100.1")
+    allowToLighthouse.fail("ping -c3 10.0.100.2")
+    allowToLighthouse.fail("ping -c3 10.0.100.3")
+
+    # allowAny can ping allowFromLighthouse now that allowFromLighthouse pinged it first
+    allowAny.succeed("ping -c3 10.0.100.3")
+
+    # block allowAny <-> allowFromLighthouse, and allowAny -> allowFromLighthouse should still work.
+    ${blockTrafficBetween "allowAny" "allowFromLighthouse"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+    allowAny.succeed("ping -c10 10.0.100.3")
+    ${allowTrafficBetween "allowAny" "allowFromLighthouse"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+    allowAny.succeed("ping -c10 10.0.100.3")
+
+    # allowToLighthouse can ping allowAny if allowAny pings it first
+    allowAny.succeed("ping -c3 10.0.100.4")
+    allowToLighthouse.succeed("ping -c3 10.0.100.2")
+
+    # block allowToLighthouse <-> allowAny, and allowAny <-> allowToLighthouse should still work.
+    ${blockTrafficBetween "allowAny" "allowToLighthouse"}
+    allowAny.succeed("ping -c10 10.0.100.4")
+    allowToLighthouse.succeed("ping -c10 10.0.100.2")
+    ${allowTrafficBetween "allowAny" "allowToLighthouse"}
+    allowAny.succeed("ping -c10 10.0.100.4")
+    allowToLighthouse.succeed("ping -c10 10.0.100.2")
+
+    # block lighthouse <-> allowFromLighthouse and allowAny <-> allowFromLighthouse; allowFromLighthouse won't get to allowAny
+    ${blockTrafficBetween "allowFromLighthouse" "lighthouse"}
+    ${blockTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.fail("ping -c3 10.0.100.2")
+    ${allowTrafficBetween "allowFromLighthouse" "lighthouse"}
+    ${allowTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.succeed("ping -c3 10.0.100.2")
+
+    # block lighthouse <-> allowAny, allowAny <-> allowFromLighthouse, and allowAny <-> allowToLighthouse; it won't get to allowFromLighthouse or allowToLighthouse
+    ${blockTrafficBetween "allowAny" "lighthouse"}
+    ${blockTrafficBetween "allowAny" "allowFromLighthouse"}
+    ${blockTrafficBetween "allowAny" "allowToLighthouse"}
+    allowFromLighthouse.fail("ping -c3 10.0.100.2")
+    allowAny.fail("ping -c3 10.0.100.3")
+    allowAny.fail("ping -c3 10.0.100.4")
+    ${allowTrafficBetween "allowAny" "lighthouse"}
+    ${allowTrafficBetween "allowAny" "allowFromLighthouse"}
+    ${allowTrafficBetween "allowAny" "allowToLighthouse"}
+    allowFromLighthouse.succeed("ping -c3 10.0.100.2")
+    allowAny.succeed("ping -c3 10.0.100.3")
+    allowAny.succeed("ping -c3 10.0.100.4")
+
+    # block lighthouse <-> allowToLighthouse and allowToLighthouse <-> allowAny; it won't get to allowAny
+    ${blockTrafficBetween "allowToLighthouse" "lighthouse"}
+    ${blockTrafficBetween "allowToLighthouse" "allowAny"}
+    allowAny.fail("ping -c3 10.0.100.4")
+    allowToLighthouse.fail("ping -c3 10.0.100.2")
+    ${allowTrafficBetween "allowToLighthouse" "lighthouse"}
+    ${allowTrafficBetween "allowToLighthouse" "allowAny"}
+    allowAny.succeed("ping -c3 10.0.100.4")
+    allowToLighthouse.succeed("ping -c3 10.0.100.2")
   '';
 })
diff --git a/nixpkgs/nixos/tests/netbird.nix b/nixpkgs/nixos/tests/netbird.nix
new file mode 100644
index 000000000000..ef793cfe9881
--- /dev/null
+++ b/nixpkgs/nixos/tests/netbird.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "netbird";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ misuzu ];
+  };
+
+  nodes = {
+    node = { ... }: {
+      services.netbird.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+    node.wait_for_unit("netbird.service")
+    node.wait_for_file("/var/run/netbird/sock")
+    node.succeed("netbird status | grep -q 'Daemon status: NeedsLogin'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/netdata.nix b/nixpkgs/nixos/tests/netdata.nix
index 0f26630da9d4..aea67c29d0d4 100644
--- a/nixpkgs/nixos/tests/netdata.nix
+++ b/nixpkgs/nixos/tests/netdata.nix
@@ -3,7 +3,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "netdata";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ cransom ];
+    maintainers = [ cransom raitobezarius ];
   };
 
   nodes = {
diff --git a/nixpkgs/nixos/tests/networking-proxy.nix b/nixpkgs/nixos/tests/networking-proxy.nix
index fcb2558cf3b0..330bac2588a5 100644
--- a/nixpkgs/nixos/tests/networking-proxy.nix
+++ b/nixpkgs/nixos/tests/networking-proxy.nix
@@ -37,7 +37,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
       default-config //
       {
         networking.proxy = {
-          # useless because overriden by the next options
+          # useless because overridden by the next options
           default = "http://user:pass@host:port";
           # advanced proxy setup
           httpProxy = "123-http://user:pass@http-host:port";
diff --git a/nixpkgs/nixos/tests/networking.nix b/nixpkgs/nixos/tests/networking.nix
index 71b82b871270..05fed0f4b473 100644
--- a/nixpkgs/nixos/tests/networking.nix
+++ b/nixpkgs/nixos/tests/networking.nix
@@ -93,18 +93,19 @@ let
       name = "Static";
       nodes.router = router;
       nodes.client = { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 2 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
           defaultGateway = "192.168.1.1";
           defaultGateway6 = "fd00:1234:5678:1::1";
-          interfaces.eth1.ipv4.addresses = mkOverride 0 [
+          interfaces.enp1s0.ipv4.addresses = [
             { address = "192.168.1.2"; prefixLength = 24; }
             { address = "192.168.1.3"; prefixLength = 32; }
             { address = "192.168.1.10"; prefixLength = 32; }
           ];
-          interfaces.eth2.ipv4.addresses = mkOverride 0 [
+          interfaces.enp2s0.ipv4.addresses = [
             { address = "192.168.2.2"; prefixLength = 24; }
           ];
         };
@@ -170,12 +171,12 @@ let
         # Disable test driver default config
         networking.interfaces = lib.mkForce {};
         networking.useNetworkd = networkd;
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
       };
       testScript = ''
         start_all()
         client.wait_for_unit("multi-user.target")
-        client.wait_until_succeeds("ip addr show dev eth1 | grep '192.168.1'")
+        client.wait_until_succeeds("ip addr show dev enp1s0 | grep '192.168.1'")
         client.shell_interact()
         client.succeed("ping -c 1 192.168.1.1")
         router.succeed("ping -c 1 192.168.1.1")
@@ -187,20 +188,13 @@ let
       name = "SimpleDHCP";
       nodes.router = router;
       nodes.client = { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 2 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1 = {
-            ipv4.addresses = mkOverride 0 [ ];
-            ipv6.addresses = mkOverride 0 [ ];
-            useDHCP = true;
-          };
-          interfaces.eth2 = {
-            ipv4.addresses = mkOverride 0 [ ];
-            ipv6.addresses = mkOverride 0 [ ];
-            useDHCP = true;
-          };
+          interfaces.enp1s0.useDHCP = true;
+          interfaces.enp2s0.useDHCP = true;
         };
       };
       testScript = { ... }:
@@ -211,10 +205,10 @@ let
           router.wait_for_unit("network-online.target")
 
           with subtest("Wait until we have an ip address on each interface"):
-              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
-              client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
-              client.wait_until_succeeds("ip addr show dev eth2 | grep -q '192.168.2'")
-              client.wait_until_succeeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'")
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
+              client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
+              client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
 
           with subtest("Test vlan 1"):
               client.wait_until_succeeds("ping -c 1 192.168.1.1")
@@ -243,16 +237,15 @@ let
       name = "OneInterfaceDHCP";
       nodes.router = router;
       nodes.client = { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 2 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1 = {
-            ipv4.addresses = mkOverride 0 [ ];
+          interfaces.enp1s0 = {
             mtu = 1343;
             useDHCP = true;
           };
-          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
         };
       };
       testScript = { ... }:
@@ -264,10 +257,10 @@ let
               router.wait_for_unit("network.target")
 
           with subtest("Wait until we have an ip address on each interface"):
-              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
 
           with subtest("ensure MTU is set"):
-              assert "mtu 1343" in client.succeed("ip link show dev eth1")
+              assert "mtu 1343" in client.succeed("ip link show dev enp1s0")
 
           with subtest("Test vlan 1"):
               client.wait_until_succeeds("ping -c 1 192.168.1.1")
@@ -286,16 +279,15 @@ let
     };
     bond = let
       node = address: { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 2 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
           bonds.bond0 = {
-            interfaces = [ "eth1" "eth2" ];
+            interfaces = [ "enp1s0" "enp2s0" ];
             driverOptions.mode = "802.3ad";
           };
-          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
-          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
           interfaces.bond0.ipv4.addresses = mkOverride 0
             [ { inherit address; prefixLength = 30; } ];
         };
@@ -326,12 +318,11 @@ let
     };
     bridge = let
       node = { address, vlan }: { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ vlan ];
+        virtualisation.interfaces.enp1s0.vlan = vlan;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1.ipv4.addresses = mkOverride 0
-            [ { inherit address; prefixLength = 24; } ];
+          interfaces.enp1s0.ipv4.addresses = [ { inherit address; prefixLength = 24; } ];
         };
       };
     in {
@@ -339,11 +330,12 @@ let
       nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
       nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
       nodes.router = { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 2 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          bridges.bridge.interfaces = [ "eth1" "eth2" ];
+          bridges.bridge.interfaces = [ "enp1s0" "enp2s0" ];
           interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
           interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
           interfaces.bridge.ipv4.addresses = mkOverride 0
@@ -377,7 +369,7 @@ let
       nodes.router = router;
       nodes.client = { pkgs, ... }: with pkgs.lib; {
         environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
@@ -385,14 +377,9 @@ let
           # reverse path filtering rules for the macvlan interface seem
           # to be incorrect, causing the test to fail. Disable temporarily.
           firewall.checkReversePath = false;
-          macvlans.macvlan.interface = "eth1";
-          interfaces.eth1 = {
-            ipv4.addresses = mkOverride 0 [ ];
-            useDHCP = true;
-          };
-          interfaces.macvlan = {
-            useDHCP = true;
-          };
+          macvlans.macvlan.interface = "enp1s0";
+          interfaces.enp1s0.useDHCP = true;
+          interfaces.macvlan.useDHCP = true;
         };
       };
       testScript = { ... }:
@@ -404,7 +391,7 @@ let
               router.wait_for_unit("network.target")
 
           with subtest("Wait until we have an ip address on each interface"):
-              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
               client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
 
           with subtest("Print lots of diagnostic information"):
@@ -431,23 +418,22 @@ let
     fou = {
       name = "foo-over-udp";
       nodes.machine = { ... }: {
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1.ipv4.addresses = mkOverride 0
-            [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          interfaces.enp1s0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
           fooOverUDP = {
             fou1 = { port = 9001; };
             fou2 = { port = 9002; protocol = 41; };
             fou3 = mkIf (!networkd)
               { port = 9003; local.address = "192.168.1.1"; };
             fou4 = mkIf (!networkd)
-              { port = 9004; local = { address = "192.168.1.1"; dev = "eth1"; }; };
+              { port = 9004; local = { address = "192.168.1.1"; dev = "enp1s0"; }; };
           };
         };
         systemd.services = {
-          fou3-fou-encap.after = optional (!networkd) "network-addresses-eth1.service";
+          fou3-fou-encap.after = optional (!networkd) "network-addresses-enp1s0.service";
         };
       };
       testScript = { ... }:
@@ -470,22 +456,22 @@ let
               "gue": None,
               "family": "inet",
               "local": "192.168.1.1",
-              "dev": "eth1",
+              "dev": "enp1s0",
           } in fous, "fou4 exists"
         '';
     };
     sit = let
       node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
           sits.sit = {
             inherit remote;
             local = address4;
-            dev = "eth1";
+            dev = "enp1s0";
           };
-          interfaces.eth1.ipv4.addresses = mkOverride 0
+          interfaces.enp1s0.ipv4.addresses = mkOverride 0
             [ { address = address4; prefixLength = 24; } ];
           interfaces.sit.ipv6.addresses = mkOverride 0
             [ { address = address6; prefixLength = 64; } ];
@@ -685,10 +671,10 @@ let
     vlan-ping = let
         baseIP = number: "10.10.10.${number}";
         vlanIP = number: "10.1.1.${number}";
-        baseInterface = "eth1";
+        baseInterface = "enp1s0";
         vlanInterface = "vlan42";
         node = number: {pkgs, ... }: with pkgs.lib; {
-          virtualisation.vlans = [ 1 ];
+          virtualisation.interfaces.enp1s0.vlan = 1;
           networking = {
             #useNetworkd = networkd;
             useDHCP = false;
@@ -785,12 +771,12 @@ let
     privacy = {
       name = "Privacy";
       nodes.router = { ... }: {
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
         boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1.ipv6.addresses = singleton {
+          interfaces.enp1s0.ipv6.addresses = singleton {
             address = "fd00:1234:5678:1::1";
             prefixLength = 64;
           };
@@ -798,7 +784,7 @@ let
         services.radvd = {
           enable = true;
           config = ''
-            interface eth1 {
+            interface enp1s0 {
               AdvSendAdvert on;
               AdvManagedFlag on;
               AdvOtherConfigFlag on;
@@ -812,11 +798,11 @@ let
         };
       };
       nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1 = {
+          interfaces.enp1s0 = {
             tempAddress = "default";
             ipv4.addresses = mkOverride 0 [ ];
             ipv6.addresses = mkOverride 0 [ ];
@@ -825,11 +811,11 @@ let
         };
       };
       nodes.client = { pkgs, ... }: with pkgs.lib; {
-        virtualisation.vlans = [ 1 ];
+        virtualisation.interfaces.enp1s0.vlan = 1;
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          interfaces.eth1 = {
+          interfaces.enp1s0 = {
             tempAddress = "enabled";
             ipv4.addresses = mkOverride 0 [ ];
             ipv6.addresses = mkOverride 0 [ ];
@@ -847,9 +833,9 @@ let
 
           with subtest("Wait until we have an ip address"):
               client_with_privacy.wait_until_succeeds(
-                  "ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"
+                  "ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'"
               )
-              client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
 
           with subtest("Test vlan 1"):
               client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
@@ -947,7 +933,7 @@ let
             ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
       '';
     };
-    rename = {
+    rename = if networkd then {
       name = "RenameInterface";
       nodes.machine = { pkgs, ... }: {
         virtualisation.vlans = [ 1 ];
@@ -955,21 +941,19 @@ let
           useNetworkd = networkd;
           useDHCP = false;
         };
-      } //
-      (if networkd
-       then { systemd.network.links."10-custom_name" = {
-                matchConfig.MACAddress = "52:54:00:12:01:01";
-                linkConfig.Name = "custom_name";
-              };
-            }
-       else { boot.initrd.services.udev.rules = ''
-               SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="52:54:00:12:01:01", KERNEL=="eth*", NAME="custom_name"
-              '';
-            });
+        systemd.network.links."10-custom_name" = {
+          matchConfig.MACAddress = "52:54:00:12:01:01";
+          linkConfig.Name = "custom_name";
+        };
+      };
       testScript = ''
         machine.succeed("udevadm settle")
         print(machine.succeed("ip link show dev custom_name"))
       '';
+    } else {
+      name = "RenameInterface";
+      nodes = { };
+      testScript = "";
     };
     # even with disabled networkd, systemd.network.links should work
     # (as it's handled by udev, not networkd)
@@ -1014,6 +998,46 @@ let
         machine.fail("ip address show wlan0 | grep -q ${testMac}")
       '';
     };
+    naughtyInterfaceNames = let
+      ifnames = [
+        # flags of ip-address
+        "home" "temporary" "optimistic"
+        "bridge_slave" "flush"
+        # flags of ip-route
+        "up" "type" "nomaster" "address"
+        # other
+        "very_loong_name" "lowerUpper" "-"
+      ];
+    in {
+      name = "naughtyInterfaceNames";
+      nodes.machine = { pkgs, ... }: {
+        networking.useNetworkd = networkd;
+        networking.bridges = listToAttrs
+          (flip map ifnames
+             (name: { inherit name; value.interfaces = []; }));
+      };
+      testScript = ''
+        machine.start()
+        machine.wait_for_unit("network.target")
+        for ifname in ${builtins.toJSON ifnames}:
+            machine.wait_until_succeeds(f"ip link show dev '{ifname}' | grep -q '{ifname}'")
+      '';
+    };
+    caseSensitiveRenaming = {
+      name = "CaseSensitiveRenaming";
+      nodes.machine = { pkgs, ... }: {
+        virtualisation.interfaces.enCustom.vlan = 11;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+        };
+      };
+      testScript = ''
+        machine.succeed("udevadm settle")
+        print(machine.succeed("ip link show dev enCustom"))
+        machine.wait_until_succeeds("ip link show dev enCustom | grep -q '52:54:00:12:0b:01")
+      '';
+    };
   };
 
 in mapAttrs (const (attrs: makeTest (attrs // {
diff --git a/nixpkgs/nixos/tests/nextcloud/basic.nix b/nixpkgs/nixos/tests/nextcloud/basic.nix
index eb37470a4c7b..e17f701c54b7 100644
--- a/nixpkgs/nixos/tests/nextcloud/basic.nix
+++ b/nixpkgs/nixos/tests/nextcloud/basic.nix
@@ -37,10 +37,13 @@ in {
         "d /var/lib/nextcloud-data 0750 nextcloud nginx - -"
       ];
 
+      system.stateVersion = "22.11"; # stateVersion >=21.11 to make sure that we use OpenSSL3
+
       services.nextcloud = {
         enable = true;
         datadir = "/var/lib/nextcloud-data";
         hostName = "nextcloud";
+        database.createLocally = true;
         config = {
           # Don't inherit adminuser since "root" is supposed to be the default
           adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
@@ -99,6 +102,10 @@ in {
     # This is just to ensure the nextcloud-occ program is working
     nextcloud.succeed("nextcloud-occ status")
     nextcloud.succeed("curl -sSf http://nextcloud/login")
+    # Ensure that no OpenSSL 1.1 is used.
+    nextcloud.succeed(
+        "${nodes.nextcloud.services.phpfpm.pools.nextcloud.phpPackage}/bin/php -i | grep 'OpenSSL Library Version' | awk -F'=>' '{ print $2 }' | awk '{ print $2 }' | grep -v 1.1"
+    )
     nextcloud.succeed(
         "${withRcloneEnv} ${copySharedFile}"
     )
@@ -108,5 +115,6 @@ in {
         "${withRcloneEnv} ${diffSharedFile}"
     )
     assert "hi" in client.succeed("cat /mnt/dav/test-shared-file")
+    nextcloud.succeed("grep -vE '^HBEGIN:oc_encryption_module' /var/lib/nextcloud-data/data/root/files/test-shared-file")
   '';
 })) args
diff --git a/nixpkgs/nixos/tests/nextcloud/default.nix b/nixpkgs/nixos/tests/nextcloud/default.nix
index 9e378fe6a52d..78fe026b4a84 100644
--- a/nixpkgs/nixos/tests/nextcloud/default.nix
+++ b/nixpkgs/nixos/tests/nextcloud/default.nix
@@ -8,6 +8,10 @@ with pkgs.lib;
 foldl
   (matrix: ver: matrix // {
     "basic${toString ver}" = import ./basic.nix { inherit system pkgs; nextcloudVersion = ver; };
+    "openssl-sse${toString ver}" = import ./openssl-sse.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
     "with-postgresql-and-redis${toString ver}" = import ./with-postgresql-and-redis.nix {
       inherit system pkgs;
       nextcloudVersion = ver;
@@ -22,4 +26,4 @@ foldl
     };
   })
 { }
-  [ 23 24 ]
+  [ 25 26 ]
diff --git a/nixpkgs/nixos/tests/nextcloud/openssl-sse.nix b/nixpkgs/nixos/tests/nextcloud/openssl-sse.nix
new file mode 100644
index 000000000000..659a4311cddd
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/openssl-sse.nix
@@ -0,0 +1,109 @@
+args@{ pkgs, nextcloudVersion ? 25, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
+  adminuser = "root";
+  adminpass = "notproduction";
+  nextcloudBase = {
+    networking.firewall.allowedTCPPorts = [ 80 ];
+    system.stateVersion = "22.05"; # stateVersions <22.11 use openssl 1.1 by default
+    services.nextcloud = {
+      enable = true;
+      config.adminpassFile = "${pkgs.writeText "adminpass" adminpass}";
+      database.createLocally = true;
+      package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
+    };
+  };
+in {
+  name = "nextcloud-openssl";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+  nodes.nextcloudwithopenssl1 = {
+    imports = [ nextcloudBase ];
+    services.nextcloud.hostName = "nextcloudwithopenssl1";
+  };
+  nodes.nextcloudwithopenssl3 = {
+    imports = [ nextcloudBase ];
+    services.nextcloud = {
+      hostName = "nextcloudwithopenssl3";
+      enableBrokenCiphersForSSE = false;
+    };
+  };
+  testScript = { nodes, ... }: let
+    withRcloneEnv = host: pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://${host}/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    withRcloneEnv1 = withRcloneEnv "nextcloudwithopenssl1";
+    withRcloneEnv3 = withRcloneEnv "nextcloudwithopenssl3";
+    copySharedFile1 = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${withRcloneEnv1} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+    copySharedFile3 = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'bye' | ${withRcloneEnv3} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file2
+    '';
+    openssl1-node = nodes.nextcloudwithopenssl1.config.system.build.toplevel;
+    openssl3-node = nodes.nextcloudwithopenssl3.config.system.build.toplevel;
+  in ''
+    nextcloudwithopenssl1.start()
+    nextcloudwithopenssl1.wait_for_unit("multi-user.target")
+    nextcloudwithopenssl1.succeed("nextcloud-occ status")
+    nextcloudwithopenssl1.succeed("curl -sSf http://nextcloudwithopenssl1/login")
+    nextcloud_version = ${toString nextcloudVersion}
+
+    with subtest("With OpenSSL 1 SSE can be enabled and used"):
+        nextcloudwithopenssl1.succeed("nextcloud-occ app:enable encryption")
+        nextcloudwithopenssl1.succeed("nextcloud-occ encryption:enable")
+
+    with subtest("Upload file and ensure it's encrypted"):
+        nextcloudwithopenssl1.succeed("${copySharedFile1}")
+        nextcloudwithopenssl1.succeed("grep -E '^HBEGIN:oc_encryption_module' /var/lib/nextcloud/data/root/files/test-shared-file")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
+
+    with subtest("Switch to OpenSSL 3"):
+        nextcloudwithopenssl1.succeed("${openssl3-node}/bin/switch-to-configuration test")
+        nextcloudwithopenssl1.wait_for_open_port(80)
+        nextcloudwithopenssl1.succeed("nextcloud-occ status")
+
+    with subtest("Existing encrypted files cannot be read, but new files can be added"):
+        # This will succeed starting NC26 because of their custom implementation of openssl_seal
+        read_existing_file_test = nextcloudwithopenssl1.fail if nextcloud_version < 26 else nextcloudwithopenssl1.succeed
+        read_existing_file_test("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file >&2")
+        nextcloudwithopenssl1.succeed("nextcloud-occ encryption:disable")
+        nextcloudwithopenssl1.succeed("${copySharedFile3}")
+        nextcloudwithopenssl1.succeed("grep bye /var/lib/nextcloud/data/root/files/test-shared-file2")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
+
+    with subtest("Switch back to OpenSSL 1.1 and ensure that encrypted files are readable again"):
+        nextcloudwithopenssl1.succeed("${openssl1-node}/bin/switch-to-configuration test")
+        nextcloudwithopenssl1.wait_for_open_port(80)
+        nextcloudwithopenssl1.succeed("nextcloud-occ status")
+        nextcloudwithopenssl1.succeed("nextcloud-occ encryption:enable")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
+        nextcloudwithopenssl1.succeed("grep -E '^HBEGIN:oc_encryption_module' /var/lib/nextcloud/data/root/files/test-shared-file")
+        nextcloudwithopenssl1.succeed("grep bye /var/lib/nextcloud/data/root/files/test-shared-file2")
+
+    with subtest("Ensure that everything can be decrypted"):
+        nextcloudwithopenssl1.succeed("echo y | nextcloud-occ encryption:decrypt-all >&2")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
+        nextcloudwithopenssl1.succeed("grep -vE '^HBEGIN:oc_encryption_module' /var/lib/nextcloud/data/root/files/test-shared-file")
+
+    with subtest("Switch to OpenSSL 3 ensure that all files are usable now"):
+        nextcloudwithopenssl1.succeed("${openssl3-node}/bin/switch-to-configuration test")
+        nextcloudwithopenssl1.wait_for_open_port(80)
+        nextcloudwithopenssl1.succeed("nextcloud-occ status")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
+        nextcloudwithopenssl1.succeed("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
+
+    nextcloudwithopenssl1.shutdown()
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
index fda05bacb4fe..ce0019e9da4a 100644
--- a/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
+++ b/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
@@ -1,6 +1,11 @@
 import ../make-test-python.nix ({ pkgs, ...}: let
-  adminpass = "hunter2";
-  adminuser = "custom-admin-username";
+  username = "custom_admin_username";
+  # This will be used both for redis and postgresql
+  pass = "hunter2";
+  # Don't do this at home, use a file outside of the nix store instead
+  passFile = toString (pkgs.writeText "pass-file" ''
+    ${pass}
+  '');
 in {
   name = "nextcloud-with-declarative-redis";
   meta = with pkgs.lib.maintainers; {
@@ -22,15 +27,15 @@ in {
           redis = true;
           memcached = false;
         };
+        # This test also validates that we can use an "external" database
+        database.createLocally = false;
         config = {
           dbtype = "pgsql";
           dbname = "nextcloud";
-          dbuser = "nextcloud";
-          dbhost = "/run/postgresql";
-          inherit adminuser;
-          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
-            ${adminpass}
-          '');
+          dbuser = username;
+          dbpassFile = passFile;
+          adminuser = username;
+          adminpassFile = passFile;
         };
         secretFile = "/etc/nextcloud-secrets.json";
 
@@ -47,26 +52,25 @@ in {
         };
       };
 
-      services.redis = {
-        enable = true;
-      };
+      services.redis.servers."nextcloud".enable = true;
+      services.redis.servers."nextcloud".port = 6379;
 
       systemd.services.nextcloud-setup= {
         requires = ["postgresql.service"];
-        after = [
-          "postgresql.service"
-        ];
+        after = [ "postgresql.service" ];
       };
 
       services.postgresql = {
         enable = true;
-        ensureDatabases = [ "nextcloud" ];
-        ensureUsers = [
-          { name = "nextcloud";
-            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
-          }
-        ];
       };
+      systemd.services.postgresql.postStart = pkgs.lib.mkAfter ''
+        password=$(cat ${passFile})
+        ${config.services.postgresql.package}/bin/psql <<EOF
+          CREATE ROLE ${username} WITH LOGIN PASSWORD '$password' CREATEDB;
+          CREATE DATABASE nextcloud;
+          GRANT ALL PRIVILEGES ON DATABASE nextcloud TO ${username};
+        EOF
+      '';
 
       # This file is meant to contain secret options which should
       # not go into the nix store. Here it is just used to set the
@@ -87,8 +91,8 @@ in {
       export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
       export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
       export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
-      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
-      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${username}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${pass})"
       "''${@}"
     '';
     copySharedFile = pkgs.writeScript "copy-shared-file" ''
diff --git a/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
index 63e0e2c59639..e57aabfaf86b 100644
--- a/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
+++ b/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -29,21 +29,11 @@ in {
         database.createLocally = true;
         config = {
           dbtype = "mysql";
-          dbname = "nextcloud";
-          dbuser = "nextcloud";
-          dbhost = "127.0.0.1";
-          dbport = 3306;
-          dbpassFile = "${pkgs.writeText "dbpass" "hunter2" }";
           # Don't inherit adminuser since "root" is supposed to be the default
           adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
         };
       };
 
-      systemd.services.nextcloud-setup= {
-        requires = ["mysql.service"];
-        after = ["mysql.service"];
-      };
-
       services.memcached.enable = true;
     };
   };
diff --git a/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 36a69fda505b..1cbb13104287 100644
--- a/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -13,7 +13,7 @@ in {
     # The only thing the client needs to do is download a file.
     client = { ... }: {};
 
-    nextcloud = { config, pkgs, ... }: {
+    nextcloud = { config, pkgs, lib, ... }: {
       networking.firewall.allowedTCPPorts = [ 80 ];
 
       services.nextcloud = {
@@ -25,38 +25,27 @@ in {
           redis = true;
           memcached = false;
         };
+        database.createLocally = true;
         config = {
           dbtype = "pgsql";
-          dbname = "nextcloud";
-          dbuser = "nextcloud";
-          dbhost = "/run/postgresql";
           inherit adminuser;
           adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
             ${adminpass}
           '');
+          trustedProxies = [ "::1" ];
+        };
+        notify_push = {
+          enable = true;
+          logLevel = "debug";
+        };
+        extraAppsEnable = true;
+        extraApps = {
+          inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push;
         };
       };
 
-      services.redis = {
-        enable = true;
-      };
-
-      systemd.services.nextcloud-setup= {
-        requires = ["postgresql.service"];
-        after = [
-          "postgresql.service"
-        ];
-      };
-
-      services.postgresql = {
-        enable = true;
-        ensureDatabases = [ "nextcloud" ];
-        ensureUsers = [
-          { name = "nextcloud";
-            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
-          }
-        ];
-      };
+      services.redis.servers."nextcloud".enable = true;
+      services.redis.servers."nextcloud".port = 6379;
     };
   };
 
@@ -95,8 +84,10 @@ in {
         "${withRcloneEnv} ${copySharedFile}"
     )
     client.wait_for_unit("multi-user.target")
+    client.execute("${pkgs.nextcloud-notify_push.passthru.test_client}/bin/test_client http://nextcloud ${adminuser} ${adminpass} >&2 &")
     client.succeed(
         "${withRcloneEnv} ${diffSharedFile}"
     )
+    nextcloud.wait_until_succeeds("journalctl -u nextcloud-notify_push | grep -q \"Sending ping to ${adminuser}\"")
   '';
 })) args
diff --git a/nixpkgs/nixos/tests/nfs/kerberos.nix b/nixpkgs/nixos/tests/nfs/kerberos.nix
index 5684131f671b..a7d08bc628c6 100644
--- a/nixpkgs/nixos/tests/nfs/kerberos.nix
+++ b/nixpkgs/nixos/tests/nfs/kerberos.nix
@@ -1,7 +1,5 @@
 import ../make-test-python.nix ({ pkgs, lib, ... }:
 
-with lib;
-
 let
   krb5 =
     { enable = true;
diff --git a/nixpkgs/nixos/tests/nfs/simple.nix b/nixpkgs/nixos/tests/nfs/simple.nix
index 1e319a8eec81..026da9563bc0 100644
--- a/nixpkgs/nixos/tests/nfs/simple.nix
+++ b/nixpkgs/nixos/tests/nfs/simple.nix
@@ -89,6 +89,7 @@ in
           t1 = time.monotonic()
           client1.shutdown()
           duration = time.monotonic() - t1
-          assert duration < 30, f"shutdown took too long ({duration} seconds)"
+          # FIXME: regressed in kernel 6.1.28, temporarily disabled while investigating
+          # assert duration < 30, f"shutdown took too long ({duration} seconds)"
     '';
 })
diff --git a/nixpkgs/nixos/tests/nginx-globalredirect.nix b/nixpkgs/nixos/tests/nginx-globalredirect.nix
new file mode 100644
index 000000000000..5f5f4f344d82
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-globalredirect.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-globalredirect";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {
+          globalRedirect = "other.example.com";
+          # Add an exception
+          locations."/noredirect".return = "200 'foo'";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    webserver.succeed("curl --fail -si http://localhost/alf | grep '^Location:.*/alf'")
+    webserver.fail("curl --fail -si http://localhost/noredirect | grep '^Location:'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-http3.nix b/nixpkgs/nixos/tests/nginx-http3.nix
index 319f6aac184a..fc9f31037f98 100644
--- a/nixpkgs/nixos/tests/nginx-http3.nix
+++ b/nixpkgs/nixos/tests/nginx-http3.nix
@@ -36,8 +36,10 @@ in
           sslCertificateKey = ./common/acme/server/acme.test.key.pem;
           http2 = true;
           http3 = true;
+          http3_hq = false;
+          quic = true;
           reuseport = true;
-          root = lib.mkForce (pkgs.runCommandLocal "testdir2" {} ''
+          root = lib.mkForce (pkgs.runCommandLocal "testdir" {} ''
             mkdir "$out"
             cat > "$out/index.html" <<EOF
             <html><body>Hello World!</body></html>
@@ -74,17 +76,19 @@ in
     server.wait_for_open_port(443)
 
     # Check http connections
-    client.succeed("curl --verbose --http3 https://acme.test | grep 'Hello World!'")
+    client.succeed("curl --verbose --http3-only https://acme.test | grep 'Hello World!'")
 
     # Check downloadings
-    client.succeed("curl --verbose --http3 https://acme.test/example.txt --output /tmp/example.txt")
+    client.succeed("curl --verbose --http3-only https://acme.test/example.txt --output /tmp/example.txt")
     client.succeed("cat /tmp/example.txt | grep 'Check http3 protocol.'")
 
     # Check header reading
-    client.succeed("curl --verbose --http3 --head https://acme.test | grep 'content-type'")
+    client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'content-type'")
+    client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'HTTP/3 200'")
+    client.succeed("curl --verbose --http3-only --head https://acme.test/error | grep 'HTTP/3 404'")
 
     # Check change User-Agent
-    client.succeed("curl --verbose --http3 --user-agent 'Curl test 3.0' https://acme.test")
+    client.succeed("curl --verbose --http3-only --user-agent 'Curl test 3.0' https://acme.test")
     server.succeed("cat /var/log/nginx/access.log | grep 'Curl test 3.0'")
 
     server.shutdown()
diff --git a/nixpkgs/nixos/tests/nginx-modsecurity.nix b/nixpkgs/nixos/tests/nginx-modsecurity.nix
index 5ceee3787297..3c41da3e8d9b 100644
--- a/nixpkgs/nixos/tests/nginx-modsecurity.nix
+++ b/nixpkgs/nixos/tests/nginx-modsecurity.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   nodes.machine = { config, lib, pkgs, ... }: {
     services.nginx = {
       enable = true;
-      additionalModules = [ pkgs.nginxModules.modsecurity-nginx ];
+      additionalModules = [ pkgs.nginxModules.modsecurity ];
       virtualHosts.localhost =
         let modsecurity_conf = pkgs.writeText "modsecurity.conf" ''
           SecRuleEngine On
diff --git a/nixpkgs/nixos/tests/nginx-njs.nix b/nixpkgs/nixos/tests/nginx-njs.nix
new file mode 100644
index 000000000000..72be16384f1b
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-njs.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "nginx-njs";
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    services.nginx = {
+      enable = true;
+      additionalModules = [ pkgs.nginxModules.njs ];
+      commonHttpConfig = ''
+        js_import http from ${builtins.toFile "http.js" ''
+          function hello(r) {
+              r.return(200, "Hello world!");
+          }
+          export default {hello};
+        ''};
+      '';
+      virtualHosts."localhost".locations."/".extraConfig = ''
+        js_content http.hello;
+      '';
+    };
+  };
+  testScript = ''
+    machine.wait_for_unit("nginx")
+
+    response = machine.wait_until_succeeds("curl -fvvv -s http://127.0.0.1/")
+    assert "Hello world!" == response, f"Expected 'Hello world!', got '{response}'"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem
new file mode 100644
index 000000000000..e5cea72610b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLjCCAhagAwIBAgIIP2+4GFxOYMgwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMB4XDTIzMDEzMDAzNDExOFoXDTQzMDEz
+MDAzNDExOFowFTETMBEGA1UEAwwKKi50ZXN0Lm5peDCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMarJSCzelnzTMT5GMoIKA/MXBNk5j277uI2Gq2MCky/
+DlBpx+tjSsKsz6QLBduKMF8OH5AgjrVAKQAtsVPDseY0Qcyx/5dgJjkdO4on+DFb
+V0SJ3ZhYPKACrqQ1SaoG+Xup37puw7sVR13J7oNvP6fAYRcjYqCiFC7VMjJNG4dR
+251jvWWidSc7v5CYw2AxrngtBgHeQuyG9QCJ1DRH8h6ioV7IeonwReN7noYtTWh8
+NDjGnw9HH2nYMcL91E+DWCxWVmbC9/orvYOT7u0Orho0t1w9BB0/zzcdojwQpMCv
+HahEmFQmdGbWTuI4caBeaDBJVsSwKlTcxLSS4MAZ0c8CAwEAAaN3MHUwDgYDVR0P
+AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB
+Af8EAjAAMB8GA1UdIwQYMBaAFGyXySYI3gL88d7GHnGMU6wpiBf2MBUGA1UdEQQO
+MAyCCioudGVzdC5uaXgwDQYJKoZIhvcNAQELBQADggEBAJ/DpwiLVBgWyozsn++f
+kR4m0dUjnuCgpHo2EMoMZh+9og+OC0vq6WITXHaJytB3aBMxFOUTim3vwxPyWPXX
+/vy+q6jJ6QMLx1J3VIWZdmXsT+qLGbVzL/4gNoaRsLPGO06p3yVjhas+OBFx1Fee
+6kTHb82S/dzBojOJLRRo18CU9yw0FUXOPqN7HF7k2y+Twe6+iwCuCKGSFcvmRjxe
+bWy11C921bTienW0Rmq6ppFWDaUNYP8kKpMN2ViAvc0tyF6wwk5lyOiqCR+pQHJR
+H/J4qSeKDchYLKECuzd6SySz8FW/xPKogQ28zba+DBD86hpqiEJOBzxbrcN3cjUn
+7N4=
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem
new file mode 100644
index 000000000000..ed2b17af0bf6
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAxqslILN6WfNMxPkYyggoD8xcE2TmPbvu4jYarYwKTL8OUGnH
+62NKwqzPpAsF24owXw4fkCCOtUApAC2xU8Ox5jRBzLH/l2AmOR07iif4MVtXRInd
+mFg8oAKupDVJqgb5e6nfum7DuxVHXcnug28/p8BhFyNioKIULtUyMk0bh1HbnWO9
+ZaJ1Jzu/kJjDYDGueC0GAd5C7Ib1AInUNEfyHqKhXsh6ifBF43uehi1NaHw0OMaf
+D0cfadgxwv3UT4NYLFZWZsL3+iu9g5Pu7Q6uGjS3XD0EHT/PNx2iPBCkwK8dqESY
+VCZ0ZtZO4jhxoF5oMElWxLAqVNzEtJLgwBnRzwIDAQABAoIBAFuNGOH184cqKJGI
+3RSVJ6kIGtJRKA0A4vfZyPd61nBBhx4lcRyXOCd4LYPCFKP0DZBwWLk5V6pM89gC
+NnqMbxnPsRbcXBVtGJAvWXW0L5rHJfMOuVBwMRfnxIUljVnONv/264PlcUtwZd/h
+o4lsJeBvNg7MnrG5nyVp1+T4RZxYm1P86HLp5zyT+fdj4Cr82b9j6QpxGXEfm1jV
+QA1xr1ZkrV8fgETyaE0TBIKcdt6xNfv1mpI1RE5gaP/YzcCs/mL+G0kMar4l7pO/
+6OHXTvHz+W3G6Xlha7Wq1ADoqYz2K7VoL/OgSQhIxRNujyWR6lir7eladVrKkCzu
+uzFi/HECgYEA0vSNCIK3useSypMPHhYUVNbZ4hbK0WgqSAxfJQtL3nC7KviVMAXj
+IKVR90xuzJB+ih88KCJpH84JH90paMpW0Gq1yEae90bnWa8Nj7ULLS/Zuj0WrelU
++DEGbx47IUPOtiLBxooxFKyIVhX3hWRwZ0pokSQzbgb5zYnlM6tqZ3cCgYEA8Rb2
+wtt0XmqEQedFacs4fobJoVWMcETjpuxYp0m5Kje/4QkptZIbspXGBgNtPBBRGg51
+AYSu8wYkGEueI77KiFDgY8AAkpOk2MrMVPszjOhUiO1oEfbT6ynOY5RDOuXcY6jo
+8RpSk46VkfVxt6LVmappqcVFtVWcAjdGfXeSLmkCgYAWP7SgMSkvidzxgJEXmzyJ
+th9EuSKq81GCR8vBHG/kBf+3iIAzkGtkBgufCXCmIpc1+hVeJkLwF8rekXTMmIqP
+cLG7bbdWXSQJUW0cuvtyyJkuC0NZFELh6knDbmzOFVi33PKS/gAvLgMzER4J843n
+VvGwXSEPeazfAKwrxuhyAQKBgQCOm5TPYlyNVNhy20h18d2zCivOoPn3luhKXtd5
+7OP4kw2PIYpoesqjcnC2MeS1eLlgfli70y5hVqqXLHOYlUzcIWr51iMAkREbo6oG
+QqkVmoAWlsfOiICGRC5vPM4f0sPwt4NCyt05p0fWFKd1hn5u7Ryfba90OfWUYfny
+UX5IsQKBgQCswer4Qc3UepkiYxGwSTxgIh4kYlmamU2I00Kar4uFAr9JsCbk98f0
+kaCUNZjrrvTwgRmdhwcpMDiMW/F4QkNk0I2unHcoAvzNop6c22VhHJU2XJhrQ57h
+n1iPiw0NLXiA4RQwMUMjtt3nqlpLOTXGtsF8TmpWPcAN2QcTxOutzw==
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem
new file mode 100644
index 000000000000..c0b2cc8f3df2
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSzCCAjOgAwIBAgIITnUr3xFw4oEwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMCAXDTIzMDEzMDAzNDExOFoYDzIxMjMw
+MTMwMDM0MTE4WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA0ZTc1MmIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1SrJT9k3zXIXApEyL5UDlw7F6
+MMOqE5d+8ZwMccHbEKLu0ssNRY+j31tnNYQ/r5iCNeNgUZccKBgzdU0ysyw5n4tw
+0y+MTD9fCfUXYcc8pJRPRolo6zxYO9W7WJr0nfJZ+p7zFRAjRCmzXdnZjKz0EGcg
+x9mHwn//3SuLt1ItK1n3aZ6im9NlcVtunDe3lCSL0tRgy7wDGNvWDZMO49jk4AFU
+BlMqScuiNpUzYgCxNaaGMuH3M0f0YyRAxSs6FWewLtqTIaVql7HL+3PcGAhvlKEZ
+fvfaf80F9aWI88sbEddTA0s5837zEoDwGpZl3K5sPU/O3MVEHIhAY5ICG0IBAgMB
+AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
+BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRsl8kmCN4C/PHe
+xh5xjFOsKYgX9jAfBgNVHSMEGDAWgBRsl8kmCN4C/PHexh5xjFOsKYgX9jANBgkq
+hkiG9w0BAQsFAAOCAQEAmvgpU+q+TBbz+9Y2rdiIeTfeDXtMNPf+nKI3zxYztRGC
+MoKP6jCQaFSQra4BVumFLV38DoqR1pOV1ojkiyO5c/9Iym/1Wmm8LeqgsHNqSgyS
+C7wvBcb/N9PzIBQFq/RiboDoC7bqK/0zQguCmBtGceH+AVpQyfXM+P78B1EkHozu
+67igP8GfouPp2s4Vd5P2XGkA6vMgYCtFEnCbtmmo7C8B+ymhD/D9axpMKQ1OaBg9
+jfqLOlk+Rc2nYZuaDjnUmlTkYjC6EwCNe9weYkSJgQ9QzoGJLIRARsdQdsp3C2fZ
+l2UZKkDJ2GPrrc+TdaGXZTYi0uMmvQsEKZXtqAzorQ==
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem
new file mode 100644
index 000000000000..717948f5b879
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAtUqyU/ZN81yFwKRMi+VA5cOxejDDqhOXfvGcDHHB2xCi7tLL
+DUWPo99bZzWEP6+YgjXjYFGXHCgYM3VNMrMsOZ+LcNMvjEw/Xwn1F2HHPKSUT0aJ
+aOs8WDvVu1ia9J3yWfqe8xUQI0Qps13Z2Yys9BBnIMfZh8J//90ri7dSLStZ92me
+opvTZXFbbpw3t5Qki9LUYMu8Axjb1g2TDuPY5OABVAZTKknLojaVM2IAsTWmhjLh
+9zNH9GMkQMUrOhVnsC7akyGlapexy/tz3BgIb5ShGX732n/NBfWliPPLGxHXUwNL
+OfN+8xKA8BqWZdyubD1PztzFRByIQGOSAhtCAQIDAQABAoIBAQCLeAWs1kWtvTYg
+t8UzspC0slItAKrmgt//hvxYDoPmdewC8yPG+AbDOSfmRKOTIxGeyro79UjdHnNP
+0yQqpvCU/AqYJ7/inR37jXuCG3TdUHfQbSF1F9N6xb1tvYKoQYKaelYiB8g8eUnj
+dYYM+U5tDNlpvJW6/YTfYFUJzWRo3i8jj5lhbkjcJDvdOhVxMXNXJgJAymu1KysE
+N1da2l4fzmuoN82wFE9KMyYSn+LOLWBReQQmXHZPP+2LjRIVrWoFoV49k2Ylp9tH
+yeaFx1Ya/wVx3PRnSW+zebWDcc0bAua9XU3Fi42yRq5iXOyoXHyefDfJoId7+GAO
+IF2qRw9hAoGBAM1O1l4ceOEDsEBh7HWTvmfwVfkXgT6VHeI6LGEjb88FApXgT+wT
+1s1IWVVOigLl9OKQbrjqlg9xgzrPDHYRwu5/Oz3X2WaH6wlF+d+okoqls6sCEAeo
+GfzF3sKOHQyIYjttCXE5G38uhIgVFFFfK97AbUiY8egYBr0zjVXK7xINAoGBAOIN
+1pDBFBQIoKj64opm/G9lJBLUpWLBFdWXhXS6q2jNsdY1mLMRmu/RBaKSfGz7W1a/
+a2WBedjcnTWJ/84tBsn4Qj5tLl8xkcXiN/pslWzg724ZnVsbyxM9KvAdXAma3F0g
+2EsYq8mhvbAEkpE+aoM6jwOJBnMhTRZrNMKN2lbFAoGAHmZWB4lfvLG3H1FgmehO
+gUVs9X0tff7GdgD3IUsF+zlasKaOLv6hB7R2xdLjTJqQMBwCyQ6zOYYtUD/oMHNg
+0b+1HesgHbZybuUVorBrQmxWtjOP/BJABtWlrlkso/Zt1S7H/yPdlm9k4GF+qK3W
+6RzFEcLTzvH/zXQcsV9jFuECgYEAhaX+1KiC0XFkY2OpaoCHAOlAUa3NdjyIRzcF
+XUU8MINkgCxB8qUXAHCJL1wCGoDluL0FpwbM3m1YuR200tYGLIUNzVDJ2Ng6wk8E
+H5fxJGU8ydB1Gzescdx5NWt2Tet0G89ecc/NSTHKL3YUnbDUUm/dvA5YdNscc4PA
+tsIdc60CgYEArvU1MwqGQUTDKUmaM2t3qm70fbwmOViHfyTWpn4aAQR3sK16iJMm
+V+dka62L/VYs5CIbzXvCioyugUMZGJi/zIwrViRzqJQbNnPADAW4lG88UxXqHHAH
+q33ivjgd9omGFb37saKOmR44KmjUIDvSIZF4W3EPwAMEyl5mM31Ryns=
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix b/nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix
new file mode 100644
index 000000000000..9ef5d01cd65b
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix
@@ -0,0 +1,144 @@
+let
+  certs = import ./snakeoil-certs.nix;
+in
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-proxyprotocol";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      environment.systemPackages = [ pkgs.netcat ];
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+
+      networking.extraHosts = ''
+        127.0.0.5 proxy.test.nix
+        127.0.0.5 noproxy.test.nix
+        127.0.0.3 direct-nossl.test.nix
+        127.0.0.4 unsecure-nossl.test.nix
+        127.0.0.2 direct-noproxy.test.nix
+        127.0.0.1 direct-proxy.test.nix
+      '';
+      services.nginx = {
+        enable = true;
+        defaultListen = [
+          { addr = "127.0.0.1"; proxyProtocol = true; ssl = true; }
+          { addr = "127.0.0.2"; }
+          { addr = "127.0.0.3"; ssl = false; }
+          { addr = "127.0.0.4"; ssl = false; proxyProtocol = true; }
+        ];
+        commonHttpConfig = ''
+          log_format pcombined '(proxy_protocol=$proxy_protocol_addr) - (remote_addr=$remote_addr) - (realip=$realip_remote_addr) - (upstream=) - (remote_user=$remote_user) [$time_local] '
+                        '"$request" $status $body_bytes_sent '
+                        '"$http_referer" "$http_user_agent"';
+          access_log /var/log/nginx/access.log pcombined;
+          error_log /var/log/nginx/error.log;
+        '';
+        virtualHosts =
+        let
+          commonConfig = {
+           locations."/".return = "200 '$remote_addr'";
+           extraConfig = ''
+            set_real_ip_from 127.0.0.5/32;
+            real_ip_header proxy_protocol;
+           '';
+         };
+        in
+        {
+          "*.test.nix" = commonConfig // {
+            sslCertificate = certs."*.test.nix".cert;
+            sslCertificateKey = certs."*.test.nix".key;
+            forceSSL = true;
+          };
+          "direct-nossl.test.nix" = commonConfig;
+          "unsecure-nossl.test.nix" = commonConfig // {
+            extraConfig = ''
+              real_ip_header proxy_protocol;
+            '';
+          };
+        };
+      };
+
+      services.sniproxy = {
+        enable = true;
+        config = ''
+          error_log {
+            syslog daemon
+          }
+          access_log {
+            syslog daemon
+          }
+          listener 127.0.0.5:443 {
+            protocol tls
+            source 127.0.0.5
+          }
+          table {
+            ^proxy\.test\.nix$   127.0.0.1 proxy_protocol
+            ^noproxy\.test\.nix$ 127.0.0.2
+          }
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    def check_origin_ip(src_ip: str, dst_url: str, failure: bool = False, proxy_protocol: bool = False, expected_ip: str | None = None):
+      check = webserver.fail if failure else webserver.succeed
+      if expected_ip is None:
+        expected_ip = src_ip
+
+      return check(f"curl {'--haproxy-protocol' if proxy_protocol else '''} --interface {src_ip} --fail -L {dst_url} | grep '{expected_ip}'")
+
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_unit("sniproxy")
+    # This should be closed by virtue of ssl = true;
+    webserver.wait_for_closed_port(80, "127.0.0.1")
+    # This should be open by virtue of no explicit ssl
+    webserver.wait_for_open_port(80, "127.0.0.2")
+    # This should be open by virtue of ssl = true;
+    webserver.wait_for_open_port(443, "127.0.0.1")
+    # This should be open by virtue of no explicit ssl
+    webserver.wait_for_open_port(443, "127.0.0.2")
+    # This should be open by sniproxy
+    webserver.wait_for_open_port(443, "127.0.0.5")
+    # This should be closed by sniproxy
+    webserver.wait_for_closed_port(80, "127.0.0.5")
+
+    # Sanity checks for the NGINX module
+    # direct-HTTP connection to NGINX without TLS, this checks that ssl = false; works well.
+    check_origin_ip("127.0.0.10", "http://direct-nossl.test.nix/")
+    # webserver.execute("openssl s_client -showcerts -connect direct-noproxy.test.nix:443")
+    # direct-HTTP connection to NGINX with TLS
+    check_origin_ip("127.0.0.10", "http://direct-noproxy.test.nix/")
+    check_origin_ip("127.0.0.10", "https://direct-noproxy.test.nix/")
+    # Well, sniproxy is not listening on 80 and cannot redirect
+    check_origin_ip("127.0.0.10", "http://proxy.test.nix/", failure=True)
+    check_origin_ip("127.0.0.10", "http://noproxy.test.nix/", failure=True)
+
+    # Actual PROXY protocol related tests
+    # Connecting through sniproxy should passthrough the originating IP address.
+    check_origin_ip("127.0.0.10", "https://proxy.test.nix/")
+    # Connecting through sniproxy to a non-PROXY protocol enabled listener should not pass the originating IP address.
+    check_origin_ip("127.0.0.10", "https://noproxy.test.nix/", expected_ip="127.0.0.5")
+
+    # Attack tests against spoofing
+    # Let's try to spoof our IP address by connecting direct-y to the PROXY protocol listener.
+    # FIXME(RaitoBezarius): rewrite it using Python + (Scapy|something else) as this is too much broken unfortunately.
+    # Or wait for upstream curl patch.
+    # def generate_attacker_request(original_ip: str, target_ip: str, dst_url: str):
+    #     return f"""PROXY TCP4 {original_ip} {target_ip} 80 80
+    #     GET / HTTP/1.1
+    #     Host: {dst_url}
+
+    #     """
+    # def spoof(original_ip: str, target_ip: str, dst_url: str, tls: bool = False, expect_failure: bool = True):
+    #   method = webserver.fail if expect_failure else webserver.succeed
+    #   port = 443 if tls else 80
+    #   print(webserver.execute(f"cat <<EOF | nc {target_ip} {port}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF"))
+    #   return method(f"cat <<EOF | nc {target_ip} {port} | grep {original_ip}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF")
+
+    # check_origin_ip("127.0.0.10", "http://unsecure-nossl.test.nix", proxy_protocol=True)
+    # spoof("1.1.1.1", "127.0.0.4", "direct-nossl.test.nix")
+    # spoof("1.1.1.1", "127.0.0.4", "unsecure-nossl.test.nix", expect_failure=False)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix b/nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix
new file mode 100644
index 000000000000..b2315062035e
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix
@@ -0,0 +1,30 @@
+# Minica can provide a CA key and cert, plus a key
+# and cert for our fake CA server's Web Front End (WFE).
+{
+  pkgs ? import <nixpkgs> {},
+  minica ? pkgs.minica,
+  runCommandCC ? pkgs.runCommandCC,
+}:
+let
+  conf = import ./snakeoil-certs.nix;
+  domain = conf.domain;
+  domainSanitized = pkgs.lib.replaceStrings ["*"] ["_"] domain;
+in
+  runCommandCC "generate-tests-certs" {
+    buildInputs = [ (minica.overrideAttrs (old: {
+    postPatch = ''
+      sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
+    '';
+  })) ];
+
+  } ''
+    minica \
+      --ca-key ca.key.pem \
+      --ca-cert ca.cert.pem \
+      --domains "${domain}"
+
+    mkdir -p $out
+    mv ca.*.pem $out/
+    mv ${domainSanitized}/key.pem $out/${domainSanitized}.key.pem
+    mv ${domainSanitized}/cert.pem $out/${domainSanitized}.cert.pem
+  ''
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix b/nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix
new file mode 100644
index 000000000000..61af6351ca65
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix
@@ -0,0 +1,14 @@
+let
+  domain = "*.test.nix";
+  domainSanitized = "_.test.nix";
+in {
+  inherit domain;
+  ca = {
+    cert = ./ca.cert.pem;
+    key = ./ca.key.pem;
+  };
+  "${domain}" = {
+    cert = ./. + "/${domainSanitized}.cert.pem";
+    key = ./. + "/${domainSanitized}.key.pem";
+  };
+}
diff --git a/nixpkgs/nixos/tests/nginx.nix b/nixpkgs/nixos/tests/nginx.nix
index d9d073822a14..8b1f921ec520 100644
--- a/nixpkgs/nixos/tests/nginx.nix
+++ b/nixpkgs/nixos/tests/nginx.nix
@@ -67,10 +67,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   };
 
   testScript = { nodes, ... }: let
-    etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etagSystem";
-    justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/justReloadSystem";
-    reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadRestartSystem";
-    reloadWithErrorsSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadWithErrorsSystem";
+    etagSystem = "${nodes.webserver.system.build.toplevel}/specialisation/etagSystem";
+    justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/justReloadSystem";
+    reloadRestartSystem = "${nodes.webserver.system.build.toplevel}/specialisation/reloadRestartSystem";
+    reloadWithErrorsSystem = "${nodes.webserver.system.build.toplevel}/specialisation/reloadWithErrorsSystem";
   in ''
     url = "http://localhost/index.html"
 
@@ -87,15 +87,23 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         return etag
 
 
-    webserver.wait_for_unit("nginx")
-    webserver.wait_for_open_port(80)
+    def wait_for_nginx_on_port(port):
+        webserver.wait_for_unit("nginx")
+        webserver.wait_for_open_port(port)
+
+
+    # nginx can be ready before multi-user.target, in which case switching to
+    # a different configuration might not realize it needs to restart nginx.
+    webserver.wait_for_unit("multi-user.target")
+
+    wait_for_nginx_on_port(80)
 
     with subtest("check ETag if serving Nix store paths"):
         old_etag = check_etag()
         webserver.succeed(
             "${etagSystem}/bin/switch-to-configuration test >&2"
         )
-        webserver.sleep(1)
+        wait_for_nginx_on_port(80)
         new_etag = check_etag()
         assert old_etag != new_etag
 
@@ -103,7 +111,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         webserver.succeed(
             "${justReloadSystem}/bin/switch-to-configuration test >&2"
         )
-        webserver.wait_for_open_port(8080)
+        wait_for_nginx_on_port(8080)
         webserver.fail("journalctl -u nginx | grep -q -i stopped")
         webserver.succeed("journalctl -u nginx | grep -q -i reloaded")
 
@@ -111,7 +119,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         webserver.succeed(
             "${reloadRestartSystem}/bin/switch-to-configuration test >&2"
         )
-        webserver.wait_for_unit("nginx")
+        wait_for_nginx_on_port(80)
         webserver.succeed("journalctl -u nginx | grep -q -i stopped")
 
     with subtest("nixos-rebuild --switch should fail when there are configuration errors"):
diff --git a/nixpkgs/nixos/tests/nix-ld.nix b/nixpkgs/nixos/tests/nix-ld.nix
index ae5297ab87ea..8733f5b0c397 100644
--- a/nixpkgs/nixos/tests/nix-ld.nix
+++ b/nixpkgs/nixos/tests/nix-ld.nix
@@ -12,9 +12,6 @@ import ./make-test-python.nix ({ lib, pkgs, ...} :
   };
   testScript = ''
     start_all()
-    path = "${pkgs.stdenv.cc}/nix-support/dynamic-linker"
-    with open(path) as f:
-        real_ld = f.read().strip()
-    machine.succeed(f"NIX_LD={real_ld} hello")
+    machine.succeed("hello")
  '';
 })
diff --git a/nixpkgs/nixos/tests/nixops/default.nix b/nixpkgs/nixos/tests/nixops/default.nix
index 227b38815073..bd00e6143639 100644
--- a/nixpkgs/nixos/tests/nixops/default.nix
+++ b/nixpkgs/nixos/tests/nixops/default.nix
@@ -19,6 +19,7 @@ let
   });
 
   testLegacyNetwork = { nixopsPkg }: pkgs.nixosTest ({
+    name = "nixops-legacy-network";
     nodes = {
       deployer = { config, lib, nodes, pkgs, ... }: {
         imports = [ ../../modules/installer/cd-dvd/channel.nix ];
@@ -29,12 +30,10 @@ let
         virtualisation.additionalPaths = [
           pkgs.hello
           pkgs.figlet
-
-          # This includes build dependencies all the way down. Not efficient,
-          # but we do need build deps to an *arbitrary* depth, which is hard to
-          # determine.
-          (allDrvOutputs nodes.server.config.system.build.toplevel)
         ];
+
+        # TODO: make this efficient, https://github.com/NixOS/nixpkgs/issues/180529
+        system.includeBuildDependencies = true;
       };
       server = { lib, ... }: {
         imports = [ ./legacy/base-configuration.nix ];
diff --git a/nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix b/nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix
new file mode 100644
index 000000000000..444ff7a3b977
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix
@@ -0,0 +1,120 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nixos-rebuild-specialisations";
+
+  nodes = {
+    machine = { lib, pkgs, ... }: {
+      imports = [
+        ../modules/profiles/installation-device.nix
+        ../modules/profiles/base.nix
+      ];
+
+      nix.settings = {
+        substituters = lib.mkForce [ ];
+        hashed-mirrors = null;
+        connect-timeout = 1;
+      };
+
+      system.includeBuildDependencies = true;
+
+      system.extraDependencies = [
+        # Not part of the initial build apparently?
+        pkgs.grub2
+      ];
+
+      virtualisation = {
+        cores = 2;
+        memorySize = 2048;
+      };
+    };
+  };
+
+  testScript =
+    let
+      configFile = pkgs.writeText "configuration.nix" ''
+        { lib, pkgs, ... }: {
+          imports = [
+            ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          documentation.enable = false;
+
+          environment.systemPackages = [
+            (pkgs.writeShellScriptBin "parent" "")
+          ];
+
+          specialisation.foo = {
+            inheritParentConfig = true;
+
+            configuration = { ... }: {
+              environment.systemPackages = [
+                (pkgs.writeShellScriptBin "foo" "")
+              ];
+            };
+          };
+
+          specialisation.bar = {
+            inheritParentConfig = true;
+
+            configuration = { ... }: {
+              environment.systemPackages = [
+                (pkgs.writeShellScriptBin "bar" "")
+              ];
+            };
+          };
+        }
+      '';
+
+    in
+    ''
+      machine.start()
+      machine.succeed("udevadm settle")
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("nixos-generate-config")
+      machine.copy_from_host(
+          "${configFile}",
+          "/etc/nixos/configuration.nix",
+      )
+
+      with subtest("Switch to the base system"):
+          machine.succeed("nixos-rebuild switch")
+          machine.succeed("parent")
+          machine.fail("foo")
+          machine.fail("bar")
+
+      with subtest("Switch from base system into a specialization"):
+          machine.succeed("nixos-rebuild switch --specialisation foo")
+          machine.succeed("parent")
+          machine.succeed("foo")
+          machine.fail("bar")
+
+      with subtest("Switch from specialization into another specialization"):
+          machine.succeed("nixos-rebuild switch -c bar")
+          machine.succeed("parent")
+          machine.fail("foo")
+          machine.succeed("bar")
+
+      with subtest("Switch from specialization into the base system"):
+          machine.succeed("nixos-rebuild switch")
+          machine.succeed("parent")
+          machine.fail("foo")
+          machine.fail("bar")
+
+      with subtest("Switch into specialization using `nixos-rebuild test`"):
+          machine.succeed("nixos-rebuild test --specialisation foo")
+          machine.succeed("parent")
+          machine.succeed("foo")
+          machine.fail("bar")
+
+      with subtest("Make sure nonsense command combinations are forbidden"):
+          machine.fail("nixos-rebuild boot --specialisation foo")
+          machine.fail("nixos-rebuild boot -c foo")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/extra-python-packages.nix b/nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix
index 7a48077cf98b..1146bedd996f 100644
--- a/nixpkgs/nixos/tests/extra-python-packages.nix
+++ b/nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ ... }:
+import ../make-test-python.nix ({ ... }:
   {
     name = "extra-python-packages";
 
diff --git a/nixpkgs/nixos/tests/nixos-test-driver/node-name.nix b/nixpkgs/nixos/tests/nixos-test-driver/node-name.nix
new file mode 100644
index 000000000000..31386813a516
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-test-driver/node-name.nix
@@ -0,0 +1,33 @@
+{
+  name = "nixos-test-driver.node-name";
+  nodes = {
+    "ok" = { };
+
+    # Valid node name, but not a great host name.
+    "one_two" = { };
+
+    # Valid node name, good host name
+    "a-b" = { };
+
+    # TODO: would be nice to test these eval failures
+    # Not allowed by lib/testing/network.nix (yet?)
+    # "foo.bar" = { };
+    # Not allowed.
+    # "not ok" = { }; # not ok
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("python vars exist and machines are reachable through test backdoor"):
+      ok.succeed("true")
+      one_two.succeed("true")
+      a_b.succeed("true")
+
+    with subtest("hostname is derived from the node name"):
+      ok.succeed("hostname | tee /dev/stderr | grep '^ok$'")
+      one_two.succeed("hostname | tee /dev/stderr | grep '^onetwo$'")
+      a_b.succeed("hostname | tee /dev/stderr | grep '^a-b$'")
+
+  '';
+}
diff --git a/nixpkgs/nixos/tests/non-default-filesystems.nix b/nixpkgs/nixos/tests/non-default-filesystems.nix
index 7fa75aaad724..03cc5bf709a4 100644
--- a/nixpkgs/nixos/tests/non-default-filesystems.nix
+++ b/nixpkgs/nixos/tests/non-default-filesystems.nix
@@ -1,54 +1,106 @@
-import ./make-test-python.nix ({ lib, pkgs, ... }:
-{
-  name = "non-default-filesystems";
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
 
-  nodes.machine =
-    { config, pkgs, lib, ... }:
-    let
-      disk = config.virtualisation.bootDevice;
-    in
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+{
+  btrfs = makeTest
     {
-      virtualisation.useDefaultFilesystems = false;
+      name = "non-default-filesystems-btrfs";
 
-      boot.initrd.availableKernelModules = [ "btrfs" ];
-      boot.supportedFilesystems = [ "btrfs" ];
+      nodes.machine =
+        { config, pkgs, lib, ... }:
+        let
+          disk = config.virtualisation.rootDevice;
+        in
+        {
+          virtualisation.rootDevice = "/dev/vda";
+          virtualisation.useDefaultFilesystems = false;
 
-      boot.initrd.postDeviceCommands = ''
-        FSTYPE=$(blkid -o value -s TYPE ${disk} || true)
-        if test -z "$FSTYPE"; then
-          modprobe btrfs
-          ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk}
+          boot.initrd.availableKernelModules = [ "btrfs" ];
+          boot.supportedFilesystems = [ "btrfs" ];
 
-          mkdir /nixos
-          mount -t btrfs ${disk} /nixos
+          boot.initrd.postDeviceCommands = ''
+            FSTYPE=$(blkid -o value -s TYPE ${disk} || true)
+            if test -z "$FSTYPE"; then
+              modprobe btrfs
+              ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk}
 
-          ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root
-          ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home
+              mkdir /nixos
+              mount -t btrfs ${disk} /nixos
 
-          umount /nixos
-        fi
-      '';
+              ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root
+              ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home
+
+              umount /nixos
+            fi
+          '';
 
-      virtualisation.fileSystems = {
-        "/" = {
-          device = disk;
-          fsType = "btrfs";
-          options = [ "subvol=/root" ];
+          virtualisation.fileSystems = {
+            "/" = {
+              device = disk;
+              fsType = "btrfs";
+              options = [ "subvol=/root" ];
+            };
+
+            "/home" = {
+              device = disk;
+              fsType = "btrfs";
+              options = [ "subvol=/home" ];
+            };
+          };
         };
 
-        "/home" = {
-          device = disk;
-          fsType = "btrfs";
-          options = [ "subvol=/home" ];
+      testScript = ''
+        machine.wait_for_unit("multi-user.target")
+
+        with subtest("BTRFS filesystems are mounted correctly"):
+          machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts")
+          machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts")
+      '';
+    };
+
+  erofs =
+    let
+      fsImage = "/tmp/non-default-filesystem.img";
+    in
+    makeTest {
+      name = "non-default-filesystems-erofs";
+
+      nodes.machine = _: {
+        virtualisation.qemu.drives = [{
+          name = "non-default-filesystem";
+          file = fsImage;
+        }];
+
+        virtualisation.fileSystems."/non-default" = {
+          device = "/dev/vdb";
+          fsType = "erofs";
+          neededForBoot = true;
         };
       };
-    };
 
-  testScript = ''
-    machine.wait_for_unit("multi-user.target")
+      testScript = ''
+        import subprocess
+        import tempfile
 
-    with subtest("BTRFS filesystems are mounted correctly"):
-      machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts")
-      machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts")
-  '';
-})
+        with tempfile.TemporaryDirectory() as tmp_dir:
+          with open(f"{tmp_dir}/filesystem", "w") as f:
+              f.write("erofs")
+
+          subprocess.run([
+            "${pkgs.erofs-utils}/bin/mkfs.erofs",
+            "${fsImage}",
+            tmp_dir,
+          ])
+
+        machine.start()
+        machine.wait_for_unit("default.target")
+
+        file_contents = machine.succeed("cat /non-default/filesystem")
+        assert "erofs" in file_contents
+      '';
+    };
+}
diff --git a/nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix b/nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix
new file mode 100644
index 000000000000..678013cf3ab9
--- /dev/null
+++ b/nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "noto-fonts-cjk-qt";
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ];
+    fonts = {
+      enableDefaultFonts = false;
+      fonts = [ pkgs.noto-fonts-cjk-sans ];
+    };
+  };
+
+  testScript =
+    let
+      script = pkgs.writers.writePython3 "qt-default-weight" {
+        libraries = [ pkgs.python3Packages.pyqt6 ];
+      } ''
+        from PyQt6.QtWidgets import QApplication
+        from PyQt6.QtGui import QFont, QRawFont
+
+        app = QApplication([])
+        f = QRawFont.fromFont(QFont("Noto Sans CJK SC", 20))
+
+        assert f.styleName() == "Regular", f.styleName()
+      '';
+    in ''
+      machine.wait_for_x()
+      machine.succeed("${script}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/noto-fonts.nix b/nixpkgs/nixos/tests/noto-fonts.nix
index e4c33fe26a9e..0515f16d101c 100644
--- a/nixpkgs/nixos/tests/noto-fonts.nix
+++ b/nixpkgs/nixos/tests/noto-fonts.nix
@@ -1,8 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "noto-fonts";
-  meta = {
-    maintainers = with lib.maintainers; [ nickcao midchildan ];
-  };
+  meta.maintainers = with lib.maintainers; [ nickcao midchildan ];
 
   nodes.machine = {
     imports = [ ./common/x11.nix ];
diff --git a/nixpkgs/nixos/tests/nscd.nix b/nixpkgs/nixos/tests/nscd.nix
new file mode 100644
index 000000000000..356c6d2e2a54
--- /dev/null
+++ b/nixpkgs/nixos/tests/nscd.nix
@@ -0,0 +1,142 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # build a getent that itself doesn't see anything in /etc/hosts and
+  # /etc/nsswitch.conf, by using libredirect to steer its own requests to
+  # /dev/null.
+  # This means is /has/ to go via nscd to actuallly resolve any of the
+  # additionally configured hosts.
+  getent' = pkgs.writeScript "getent-without-etc-hosts" ''
+    export NIX_REDIRECTS=/etc/hosts=/dev/null:/etc/nsswitch.conf=/dev/null
+    export LD_PRELOAD=${pkgs.libredirect}/lib/libredirect.so
+    exec getent $@
+  '';
+in
+{
+  name = "nscd";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    networking.extraHosts = ''
+      2001:db8::1 somehost.test
+      192.0.2.1 somehost.test
+    '';
+
+    systemd.services.sockdump = {
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        # necessary for bcc to unpack kernel headers and invoke modprobe
+        pkgs.gnutar
+        pkgs.xz.bin
+        pkgs.kmod
+      ];
+      environment.PYTHONUNBUFFERED = "1";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.sockdump}/bin/sockdump /var/run/nscd/socket";
+        Restart = "on-failure";
+        RestartSec = "1";
+        Type = "simple";
+      };
+    };
+
+    specialisation = {
+      withGlibcNscd.configuration = { ... }: {
+        services.nscd.enableNsncd = false;
+      };
+      withUnscd.configuration = { ... }: {
+        services.nscd.enableNsncd = false;
+        services.nscd.package = pkgs.unscd;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      # Regression test for https://github.com/NixOS/nixpkgs/issues/50273
+      def test_dynamic_user():
+          with subtest("DynamicUser actually allocates a user"):
+              assert "iamatest" in machine.succeed(
+                  "systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami"
+              )
+
+      # Test resolution of somehost.test with getent', to make sure we go via
+      # nscd protocol
+      def test_host_lookups():
+          with subtest("host lookups via nscd protocol"):
+              # ahosts
+              output = machine.succeed("${getent'} ahosts somehost.test")
+              assert "192.0.2.1" in output
+              assert "2001:db8::1" in output
+
+              # ahostsv4
+              output = machine.succeed("${getent'} ahostsv4 somehost.test")
+              assert "192.0.2.1" in output
+              assert "2001:db8::1" not in output
+
+              # ahostsv6
+              output = machine.succeed("${getent'} ahostsv6 somehost.test")
+              assert "192.0.2.1" not in output
+              assert "2001:db8::1" in output
+
+              # reverse lookups (hosts)
+              assert "somehost.test" in machine.succeed("${getent'} hosts 2001:db8::1")
+              assert "somehost.test" in machine.succeed("${getent'} hosts 192.0.2.1")
+
+
+      # Test host resolution via nss modules works
+      # We rely on nss-myhostname in this case, which resolves *.localhost and
+      # _gateway.
+      # We don't need to use getent' here, as non-glibc nss modules can only be
+      # discovered via nscd.
+      def test_nss_myhostname():
+          with subtest("nss-myhostname provides hostnames (ahosts)"):
+              # ahosts
+              output = machine.succeed("getent ahosts foobar.localhost")
+              assert "::1" in output
+              assert "127.0.0.1" in output
+
+              # ahostsv4
+              output = machine.succeed("getent ahostsv4 foobar.localhost")
+              assert "::1" not in output
+              assert "127.0.0.1" in output
+
+              # ahostsv6
+              output = machine.succeed("getent ahostsv6 foobar.localhost")
+              assert "::1" in output
+              assert "127.0.0.1" not in output
+
+      start_all()
+      machine.wait_for_unit("default.target")
+
+      # give sockdump some time to finish attaching.
+      machine.sleep(5)
+
+      # Test all tests with glibc-nscd.
+      test_dynamic_user()
+      test_host_lookups()
+      test_nss_myhostname()
+
+      with subtest("glibc-nscd"):
+          machine.succeed('${specialisations}/withGlibcNscd/bin/switch-to-configuration test')
+          machine.wait_for_unit("default.target")
+
+          test_dynamic_user()
+          test_host_lookups()
+          test_nss_myhostname()
+
+      with subtest("unscd"):
+          machine.succeed('${specialisations}/withUnscd/bin/switch-to-configuration test')
+          machine.wait_for_unit("default.target")
+
+          # known to fail, unscd doesn't load external NSS modules
+          # test_dynamic_user()
+
+          test_host_lookups()
+
+          # known to fail, unscd doesn't load external NSS modules
+          # test_nss_myhostname()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/ntfy-sh.nix b/nixpkgs/nixos/tests/ntfy-sh.nix
new file mode 100644
index 000000000000..ec2e645bacb5
--- /dev/null
+++ b/nixpkgs/nixos/tests/ntfy-sh.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix {
+  name = "ntfy-sh";
+
+  nodes.machine = { ... }: {
+    services.ntfy-sh.enable = true;
+    services.ntfy-sh.settings.base-url = "http://localhost:2586";
+  };
+
+  testScript = ''
+    import json
+
+    msg = "Test notification"
+
+    machine.wait_for_unit("multi-user.target")
+
+    machine.wait_for_open_port(2586)
+
+    machine.succeed(f"curl -d '{msg}' localhost:2586/test")
+
+    notif = json.loads(machine.succeed("curl -s localhost:2586/test/json?poll=1"))
+
+    assert msg == notif["message"], "Wrong message"
+
+    machine.succeed("ntfy user list")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nzbget.nix b/nixpkgs/nixos/tests/nzbget.nix
index fe5a4bc3df91..e45031d5629c 100644
--- a/nixpkgs/nixos/tests/nzbget.nix
+++ b/nixpkgs/nixos/tests/nzbget.nix
@@ -35,7 +35,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         "${pkgs.nzbget}/bin/nzbget -n -o Control_iP=127.0.0.1 -o Control_port=6789 -o Control_password=tegbzn6789 -V"
     )
 
-    config = server.succeed("${nodes.server.config.systemd.services.nzbget.serviceConfig.ExecStart} --printconfig")
+    config = server.succeed("${nodes.server.systemd.services.nzbget.serviceConfig.ExecStart} --printconfig")
 
     # confirm the test settings are applied
     assert 'MainDir = "/var/lib/nzbget"' in config
diff --git a/nixpkgs/nixos/tests/nzbhydra2.nix b/nixpkgs/nixos/tests/nzbhydra2.nix
index c82c756c3a1c..e1d528cd9520 100644
--- a/nixpkgs/nixos/tests/nzbhydra2.nix
+++ b/nixpkgs/nixos/tests/nzbhydra2.nix
@@ -1,10 +1,7 @@
 import ./make-test-python.nix ({ lib, ... }:
-
-  with lib;
-
   {
     name = "nzbhydra2";
-    meta.maintainers = with maintainers; [ jamiemagee ];
+    meta.maintainers = with lib.maintainers; [ jamiemagee ];
 
     nodes.machine = { pkgs, ... }: { services.nzbhydra2.enable = true; };
 
diff --git a/nixpkgs/nixos/tests/oci-containers.nix b/nixpkgs/nixos/tests/oci-containers.nix
index 68077e3540a5..1afa9df36dfa 100644
--- a/nixpkgs/nixos/tests/oci-containers.nix
+++ b/nixpkgs/nixos/tests/oci-containers.nix
@@ -11,9 +11,8 @@ let
   mkOCITest = backend: makeTest {
     name = "oci-containers-${backend}";
 
-    meta = {
-      maintainers = with lib.maintainers; [ adisbladis benley ] ++ lib.teams.serokell.members;
-    };
+    meta.maintainers = lib.teams.serokell.members
+                       ++ (with lib.maintainers; [ adisbladis benley mkaito ]);
 
     nodes = {
       ${backend} = { pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/octoprint.nix b/nixpkgs/nixos/tests/octoprint.nix
new file mode 100644
index 000000000000..15a2d677d4cf
--- /dev/null
+++ b/nixpkgs/nixos/tests/octoprint.nix
@@ -0,0 +1,61 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  apikey = "testapikey";
+in
+{
+  name = "octoprint";
+  meta.maintainers = with lib.maintainers; [ gador ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ jq ];
+    services.octoprint = {
+      enable = true;
+      extraConfig = {
+        server = {
+          firstRun = false;
+        };
+        api = {
+          enabled = true;
+          key = apikey;
+        };
+        plugins = {
+          # these need internet access and pollute the output with connection failed errors
+          _disabled = [ "softwareupdate" "announcements" "pluginmanager" ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    @polling_condition
+    def octoprint_running():
+        machine.succeed("pgrep octoprint")
+
+    with subtest("Wait for octoprint service to start"):
+        machine.wait_for_unit("octoprint.service")
+        machine.wait_until_succeeds("pgrep octoprint")
+
+    with subtest("Wait for final boot"):
+        # this appears whe octoprint is almost finished starting
+        machine.wait_for_file("/var/lib/octoprint/uploads")
+
+    # octoprint takes some time to start. This makes sure we'll retry just in case it takes longer
+    # retry-all-errors in necessary, since octoprint will report a 404 error when not yet ready
+    curl_cmd = "curl --retry-all-errors --connect-timeout 5 --max-time 10 --retry 5 --retry-delay 0 \
+                --retry-max-time 40 -X GET --header 'X-API-Key: ${apikey}' "
+
+    # used to fail early, in case octoprint first starts and then crashes
+    with octoprint_running: # type: ignore[union-attr]
+        with subtest("Check for web interface"):
+            machine.wait_until_succeeds("curl -s localhost:5000")
+
+        with subtest("Check API"):
+            version = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/version"))
+            server = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/server"))
+            assert version["server"] == str("${pkgs.octoprint.version}")
+            assert server["safemode"] == None
+  '';
+})
diff --git a/nixpkgs/nixos/tests/odoo.nix b/nixpkgs/nixos/tests/odoo.nix
index 96e3405482b4..7c2cf31370f9 100644
--- a/nixpkgs/nixos/tests/odoo.nix
+++ b/nixpkgs/nixos/tests/odoo.nix
@@ -1,8 +1,6 @@
-import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
   name = "odoo";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ mkg20001 ];
-  };
+  meta.maintainers = with lib.maintainers; [ mkg20001 ];
 
   nodes = {
     server = { ... }: {
diff --git a/nixpkgs/nixos/tests/ombi.nix b/nixpkgs/nixos/tests/ombi.nix
index ce3064ce6ac6..fb3a37c978e3 100644
--- a/nixpkgs/nixos/tests/ombi.nix
+++ b/nixpkgs/nixos/tests/ombi.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "ombi";
-  meta.maintainers = with maintainers; [ woky ];
+  meta.maintainers = with lib.maintainers; [ woky ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/openldap.nix b/nixpkgs/nixos/tests/openldap.nix
index 075bb5d1f640..47d6a91843f1 100644
--- a/nixpkgs/nixos/tests/openldap.nix
+++ b/nixpkgs/nixos/tests/openldap.nix
@@ -118,7 +118,7 @@ in {
     };
   };
   testScript = { nodes, ... }: let
-    specializations = "${nodes.machine.config.system.build.toplevel}/specialisation";
+    specializations = "${nodes.machine.system.build.toplevel}/specialisation";
     changeRootPw = ''
       dn: olcDatabase={1}mdb,cn=config
       changetype: modify
diff --git a/nixpkgs/nixos/tests/opensearch.nix b/nixpkgs/nixos/tests/opensearch.nix
new file mode 100644
index 000000000000..c0caf950cb9c
--- /dev/null
+++ b/nixpkgs/nixos/tests/opensearch.nix
@@ -0,0 +1,52 @@
+let
+  opensearchTest =
+    import ./make-test-python.nix (
+      { pkgs, lib, extraSettings ? {} }: {
+        name = "opensearch";
+        meta.maintainers = with pkgs.lib.maintainers; [ shyim ];
+
+        nodes.machine = lib.mkMerge [
+          {
+            virtualisation.memorySize = 2048;
+            services.opensearch.enable = true;
+          }
+          extraSettings
+        ];
+
+        testScript = ''
+          machine.start()
+          machine.wait_for_unit("opensearch.service")
+          machine.wait_for_open_port(9200)
+
+          machine.succeed(
+              "curl --fail localhost:9200"
+          )
+        '';
+      });
+in
+{
+  opensearch = opensearchTest {};
+  opensearchCustomPathAndUser = opensearchTest {
+    extraSettings = {
+      services.opensearch.dataDir = "/var/opensearch_test";
+      services.opensearch.user = "open_search";
+      services.opensearch.group = "open_search";
+      system.activationScripts.createDirectory = {
+        text = ''
+          mkdir -p "/var/opensearch_test"
+          chown open_search:open_search /var/opensearch_test
+          chmod 0700 /var/opensearch_test
+        '';
+        deps = [ "users" "groups" ];
+      };
+      users = {
+        groups.open_search = {};
+        users.open_search = {
+          description = "OpenSearch daemon user";
+          group = "open_search";
+          isSystemUser = true;
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/openvscode-server.nix b/nixpkgs/nixos/tests/openvscode-server.nix
new file mode 100644
index 000000000000..cbff8e09c593
--- /dev/null
+++ b/nixpkgs/nixos/tests/openvscode-server.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+{
+  name = "openvscode-server";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.openvscode-server = {
+        enable = true;
+        withoutConnectionToken = true;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("openvscode-server.service")
+    machine.wait_for_open_port(3000)
+    machine.succeed("curl -k --fail http://localhost:3000", timeout=10)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+})
diff --git a/nixpkgs/nixos/tests/orangefs.nix b/nixpkgs/nixos/tests/orangefs.nix
index fe9f9cc37ea0..4e67a7fb8efe 100644
--- a/nixpkgs/nixos/tests/orangefs.nix
+++ b/nixpkgs/nixos/tests/orangefs.nix
@@ -62,7 +62,7 @@ in {
             "sudo -g orangefs -u orangefs pvfs2-server -f /etc/orangefs/server.conf"
         )
 
-    # start services after storage is formated on all machines
+    # start services after storage is formatted on all machines
     for server in server1, server2:
         server.succeed("systemctl start orangefs-server.service")
 
diff --git a/nixpkgs/nixos/tests/os-prober.nix b/nixpkgs/nixos/tests/os-prober.nix
index 1c89cf8c1c67..8f3e2494047c 100644
--- a/nixpkgs/nixos/tests/os-prober.nix
+++ b/nixpkgs/nixos/tests/os-prober.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({pkgs, lib, ...}:
 let
   # A filesystem image with a (presumably) bootable debian
-  debianImage = pkgs.vmTools.diskImageFuns.debian9i386 {
+  debianImage = pkgs.vmTools.diskImageFuns.debian11i386 {
     # os-prober cannot detect systems installed on disks without a partition table
     # so we create the disk ourselves
     createRootFS = with pkgs; ''
diff --git a/nixpkgs/nixos/tests/outline.nix b/nixpkgs/nixos/tests/outline.nix
new file mode 100644
index 000000000000..e45be37f5d3b
--- /dev/null
+++ b/nixpkgs/nixos/tests/outline.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  accessKey = "BKIKJAA5BMMU2RHO6IBB";
+  secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+  secretKeyFile = pkgs.writeText "outline-secret-key" ''
+    ${secretKey}
+  '';
+  rootCredentialsFile = pkgs.writeText "minio-credentials-full" ''
+    MINIO_ROOT_USER=${accessKey}
+    MINIO_ROOT_PASSWORD=${secretKey}
+  '';
+in
+{
+  name = "outline";
+
+  meta.maintainers = with lib.maintainers; [ xanderio ];
+
+  nodes = {
+    outline = { pkgs, config, ... }: {
+      nixpkgs.config.allowUnfree = true;
+      environment.systemPackages = [ pkgs.minio-client ];
+      services.outline = {
+        enable = true;
+        forceHttps = false;
+        storage = {
+          inherit accessKey secretKeyFile;
+          uploadBucketUrl = "http://localhost:9000";
+          uploadBucketName = "outline";
+          region = config.services.minio.region;
+        };
+      };
+      services.minio = {
+        enable = true;
+        inherit rootCredentialsFile;
+      };
+    };
+  };
+
+  testScript =
+    ''
+      machine.wait_for_unit("minio.service")
+      machine.wait_for_open_port(9000)
+
+      # Create a test bucket on the server
+      machine.succeed(
+          "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
+      )
+      machine.succeed("mc mb minio/outline")
+
+      outline.wait_for_unit("outline.service")
+      outline.wait_for_open_port(3000)
+      outline.succeed("curl --fail http://localhost:3000/")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/pam/pam-file-contents.nix b/nixpkgs/nixos/tests/pam/pam-file-contents.nix
index 86c61003aeb6..2bafd90618e9 100644
--- a/nixpkgs/nixos/tests/pam/pam-file-contents.nix
+++ b/nixpkgs/nixos/tests/pam/pam-file-contents.nix
@@ -2,6 +2,7 @@ let
   name = "pam";
 in
 import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "pam-file-contents";
 
   nodes.machine = { ... }: {
     imports = [ ../../modules/profiles/minimal.nix ];
diff --git a/nixpkgs/nixos/tests/pam/test_chfn.py b/nixpkgs/nixos/tests/pam/test_chfn.py
index b108a9423caf..a48438b8d305 100644
--- a/nixpkgs/nixos/tests/pam/test_chfn.py
+++ b/nixpkgs/nixos/tests/pam/test_chfn.py
@@ -8,7 +8,7 @@ expected_lines = {
     "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",
+    "password sufficient pam_unix.so nullok yescrypt",
     "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",
diff --git a/nixpkgs/nixos/tests/pam/zfs-key.nix b/nixpkgs/nixos/tests/pam/zfs-key.nix
new file mode 100644
index 000000000000..4f54c287e91a
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/zfs-key.nix
@@ -0,0 +1,83 @@
+import ../make-test-python.nix ({ ... }:
+
+  let
+    userPassword = "password";
+    mismatchPass = "mismatch";
+  in
+  {
+    name = "pam-zfs-key";
+
+    nodes.machine =
+      { ... }: {
+        boot.supportedFilesystems = [ "zfs" ];
+
+        networking.hostId = "12345678";
+
+        security.pam.zfs.enable = true;
+
+        users.users = {
+          alice = {
+            isNormalUser = true;
+            password = userPassword;
+          };
+          bob = {
+            isNormalUser = true;
+            password = userPassword;
+          };
+        };
+      };
+
+    testScript = { nodes, ... }:
+      let
+        homes = nodes.machine.security.pam.zfs.homes;
+        pool = builtins.head (builtins.split "/" homes);
+      in
+      ''
+        machine.wait_for_unit("multi-user.target")
+        machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+        with subtest("Create encrypted ZFS datasets"):
+          machine.succeed("truncate -s 64M /testpool.img")
+          machine.succeed("zpool create -O canmount=off '${pool}' /testpool.img")
+          machine.succeed("zfs create -o canmount=off -p '${homes}'")
+          machine.succeed("echo ${userPassword} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/alice'")
+          machine.succeed("zfs unload-key '${homes}/alice'")
+          machine.succeed("echo ${mismatchPass} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/bob'")
+          machine.succeed("zfs unload-key '${homes}/bob'")
+
+        with subtest("Switch to tty2"):
+          machine.fail("pgrep -f 'agetty.*tty2'")
+          machine.send_key("alt-f2")
+          machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+          machine.wait_for_unit("getty@tty2.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+        with subtest("Log in as user with home locked by login password"):
+          machine.wait_until_tty_matches("2", "login: ")
+          machine.send_chars("alice\n")
+          machine.wait_until_tty_matches("2", "login: alice")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("2", "Password: ")
+          machine.send_chars("${userPassword}\n")
+          machine.wait_until_succeeds("pgrep -u alice bash")
+          machine.succeed("mount | grep ${homes}/alice")
+
+        with subtest("Switch to tty3"):
+          machine.fail("pgrep -f 'agetty.*tty3'")
+          machine.send_key("alt-f3")
+          machine.wait_until_succeeds("[ $(fgconsole) = 3 ]")
+          machine.wait_for_unit("getty@tty3.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
+
+        with subtest("Log in as user with home locked by password different from login"):
+          machine.wait_until_tty_matches("3", "login: ")
+          machine.send_chars("bob\n")
+          machine.wait_until_tty_matches("3", "login: bob")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("3", "Password: ")
+          machine.send_chars("${userPassword}\n")
+          machine.wait_until_succeeds("pgrep -u bob bash")
+          machine.fail("mount | grep ${homes}/bob")
+      '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/pantheon.nix b/nixpkgs/nixos/tests/pantheon.nix
index 52f85f5c07da..653fcc6edad1 100644
--- a/nixpkgs/nixos/tests/pantheon.nix
+++ b/nixpkgs/nixos/tests/pantheon.nix
@@ -3,9 +3,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
 {
   name = "pantheon";
 
-  meta = with lib; {
-    maintainers = teams.pantheon.members;
-  };
+  meta.maintainers = lib.teams.pantheon.members;
 
   nodes.machine = { ... }:
 
@@ -15,13 +13,14 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     services.xserver.enable = true;
     services.xserver.desktopManager.pantheon.enable = true;
 
+    environment.systemPackages = [ pkgs.xdotool ];
   };
 
   enableOCR = true;
 
   testScript = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
-    bob = nodes.machine.config.users.users.bob;
+    user = nodes.machine.users.users.alice;
+    bob = nodes.machine.users.users.bob;
   in ''
     machine.wait_for_unit("display-manager.service")
 
@@ -29,6 +28,10 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         machine.wait_for_text("${user.description}")
         # OCR was struggling with this one.
         # machine.wait_for_text("${bob.description}")
+        # Ensure the password box is focused by clicking it.
+        # Workaround for https://github.com/NixOS/nixpkgs/issues/211366.
+        machine.succeed("XAUTHORITY=/var/lib/lightdm/.Xauthority DISPLAY=:0 xdotool mousemove 512 505 click 1")
+        machine.sleep(2)
         machine.screenshot("elementary_greeter_lightdm")
 
     with subtest("Login with elementary-greeter"):
@@ -40,7 +43,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     with subtest("Check that logging in has given the user ownership of devices"):
         machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
 
-    # TODO: DBus API could eliminate this? Pantheon uses Bamf.
     with subtest("Check if pantheon session components actually start"):
         machine.wait_until_succeeds("pgrep gala")
         machine.wait_for_window("gala")
@@ -49,6 +51,12 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         machine.wait_until_succeeds("pgrep plank")
         machine.wait_for_window("plank")
 
+    with subtest("Open system settings"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.switchboard >&2 &'")
+        # Wait for all plugins to be loaded before we check if the window is still there.
+        machine.sleep(5)
+        machine.wait_for_window("io.elementary.switchboard")
+
     with subtest("Open elementary terminal"):
         machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.terminal >&2 &'")
         machine.wait_for_window("io.elementary.terminal")
diff --git a/nixpkgs/nixos/tests/paperless.nix b/nixpkgs/nixos/tests/paperless.nix
index 12883cd62c60..7f36de4c29b7 100644
--- a/nixpkgs/nixos/tests/paperless.nix
+++ b/nixpkgs/nixos/tests/paperless.nix
@@ -26,6 +26,10 @@ import ./make-test-python.nix ({ lib, ... }: {
         # Wait until server accepts connections
         machine.wait_until_succeeds("curl -fs localhost:28981")
 
+    # Required for consuming documents via the web interface
+    with subtest("Task-queue gets ready"):
+        machine.wait_for_unit("paperless-task-queue.service")
+
     with subtest("Add a document via the web interface"):
         machine.succeed(
             "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
@@ -40,5 +44,13 @@ import ./make-test-python.nix ({ lib, ... }: {
         docs = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/"))['results']
         assert "2005-10-16" in docs[0]['created']
         assert "2005-10-16" in docs[1]['created']
+
+    # Detects gunicorn issues, see PR #190888
+    with subtest("Document metadata can be accessed"):
+        metadata = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/1/metadata/"))
+        assert "original_checksum" in metadata
+
+        metadata = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/2/metadata/"))
+        assert "original_checksum" in metadata
   '';
 })
diff --git a/nixpkgs/nixos/tests/parsedmarc/default.nix b/nixpkgs/nixos/tests/parsedmarc/default.nix
index 50b977723e9c..1feadcb7f39b 100644
--- a/nixpkgs/nixos/tests/parsedmarc/default.nix
+++ b/nixpkgs/nixos/tests/parsedmarc/default.nix
@@ -84,8 +84,6 @@ in
             };
           };
 
-          services.elasticsearch.package = pkgs.elasticsearch-oss;
-
           environment.systemPackages = [
             (sendEmail "dmarc@localhost")
             pkgs.jq
@@ -155,12 +153,9 @@ in
                   ssl = true;
                   user = "alice";
                   password = "${pkgs.writeText "imap-password" "foobar"}";
-                  watch = true;
                 };
               };
 
-              services.elasticsearch.package = pkgs.elasticsearch-oss;
-
               environment.systemPackages = [
                 pkgs.jq
               ];
diff --git a/nixpkgs/nixos/tests/pass-secret-service.nix b/nixpkgs/nixos/tests/pass-secret-service.nix
index a85a508bfe16..e0dddf0ad29e 100644
--- a/nixpkgs/nixos/tests/pass-secret-service.nix
+++ b/nixpkgs/nixos/tests/pass-secret-service.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "pass-secret-service";
-  meta.maintainers = with lib; [ aidalgol ];
+  meta.maintainers = [ lib.maintainers.aidalgol ];
 
   nodes.machine = { nodes, pkgs, ... }:
     {
diff --git a/nixpkgs/nixos/tests/patroni.nix b/nixpkgs/nixos/tests/patroni.nix
index f512fddcdbdd..1f15cd59677a 100644
--- a/nixpkgs/nixos/tests/patroni.nix
+++ b/nixpkgs/nixos/tests/patroni.nix
@@ -166,6 +166,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
       start_all()
 
+      etcd.wait_for_unit("etcd.service")
+
       with subtest("should bootstrap a new patroni cluster"):
           wait_for_all_nodes_ready()
 
diff --git a/nixpkgs/nixos/tests/peroxide.nix b/nixpkgs/nixos/tests/peroxide.nix
new file mode 100644
index 000000000000..12e196484164
--- /dev/null
+++ b/nixpkgs/nixos/tests/peroxide.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "peroxide";
+  meta.maintainers = with lib.maintainers; [ aidalgol ];
+
+  nodes.machine =
+    { config, pkgs, ... }: {
+      networking.hostName = "nixos";
+      services.peroxide.enable = true;
+    };
+
+  testScript = ''
+    machine.wait_for_unit("peroxide.service")
+    machine.wait_for_open_port(1143) # IMAP
+    machine.wait_for_open_port(1025) # SMTP
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgadmin4-standalone.nix b/nixpkgs/nixos/tests/pgadmin4-standalone.nix
deleted file mode 100644
index 442570c5306b..000000000000
--- a/nixpkgs/nixos/tests/pgadmin4-standalone.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
-  # This is seperate from pgadmin4 since we don't want both running at once
-
-  {
-    name = "pgadmin4-standalone";
-    meta.maintainers = with lib.maintainers; [ mkg20001 ];
-
-    nodes.machine = { pkgs, ... }: {
-      environment.systemPackages = with pkgs; [
-        curl
-      ];
-
-      services.postgresql = {
-        enable = true;
-
-        authentication = ''
-          host    all             all             localhost               trust
-        '';
-
-        ensureUsers = [
-          {
-            name = "postgres";
-            ensurePermissions = {
-              "DATABASE \"postgres\"" = "ALL PRIVILEGES";
-            };
-          }
-        ];
-      };
-
-      services.pgadmin = {
-        enable = true;
-        initialEmail = "bruh@localhost.de";
-        initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
-      };
-    };
-
-    testScript = ''
-      machine.wait_for_unit("postgresql")
-      machine.wait_for_unit("pgadmin")
-
-      machine.wait_until_succeeds("curl -s localhost:5050")
-    '';
-  })
diff --git a/nixpkgs/nixos/tests/pgadmin4.nix b/nixpkgs/nixos/tests/pgadmin4.nix
index 9f5ac3d8d922..cb8de87c9ee3 100644
--- a/nixpkgs/nixos/tests/pgadmin4.nix
+++ b/nixpkgs/nixos/tests/pgadmin4.nix
@@ -1,136 +1,61 @@
-import ./make-test-python.nix ({ pkgs, lib, buildDeps ? [ ], pythonEnv ? [ ], ... }:
-
-  /*
-  This test suite replaces the typical pytestCheckHook function in python
-  packages. Pgadmin4 test suite needs a running and configured postgresql
-  server. This is why this test exists.
-
-  To not repeat all the python dependencies needed, this test is called directly
-  from the pgadmin4 derivation, which also passes the currently
-  used propagatedBuildInputs and any python overrides.
-
-  Unfortunately, there doesn't seem to be an easy way to otherwise include
-  the needed packages here.
-
-  Due the the needed parameters a direct call to "nixosTests.pgadmin4" fails
-  and needs to be called as "pgadmin4.tests"
-
-  */
-
-  let
-    pgadmin4SrcDir = "/pgadmin";
-    pgadmin4Dir = "/var/lib/pgadmin";
-    pgadmin4LogDir = "/var/log/pgadmin";
-
-  in
-  {
-    name = "pgadmin4";
-    meta.maintainers = with lib.maintainers; [ gador ];
-
-    nodes.machine = { pkgs, ... }: {
-      imports = [ ./common/x11.nix ];
-      # needed because pgadmin 6.8 will fail, if those dependencies get updated
-      nixpkgs.overlays = [
-        (self: super: {
-          pythonPackages = pythonEnv;
-        })
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "pgadmin4";
+  meta.maintainers = with lib.maintainers; [ mkg20001 gador ];
+
+  nodes.machine = { pkgs, ... }: {
+
+    imports = [ ./common/user-account.nix ];
+
+    environment.systemPackages = with pkgs; [
+      wget
+      curl
+      pgadmin4-desktopmode
+    ];
+
+    services.postgresql = {
+      enable = true;
+      authentication = ''
+        host    all             all             localhost               trust
+      '';
+      ensureUsers = [
+        {
+          name = "postgres";
+          ensurePermissions = {
+            "DATABASE \"postgres\"" = "ALL PRIVILEGES";
+          };
+        }
       ];
+    };
 
-      environment.systemPackages = with pkgs; [
-        pgadmin4
-        postgresql
-        chromedriver
-        chromium
-        # include the same packages as in pgadmin minus speaklater3
-        (python3.withPackages
-          (ps: buildDeps ++
-            [
-              # test suite package requirements
-              pythonPackages.testscenarios
-              pythonPackages.selenium
-            ])
-        )
-      ];
-      services.postgresql = {
-        enable = true;
-        authentication = ''
-          host    all             all             localhost               trust
-        '';
-        ensureUsers = [
-          {
-            name = "postgres";
-            ensurePermissions = {
-              "DATABASE \"postgres\"" = "ALL PRIVILEGES";
-            };
-          }
-        ];
-      };
+    services.pgadmin = {
+      port = 5051;
+      enable = true;
+      initialEmail = "bruh@localhost.de";
+      initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
     };
+  };
 
-    testScript = ''
+  testScript = ''
+    with subtest("Check pgadmin module"):
       machine.wait_for_unit("postgresql")
-
-      # pgadmin4 needs its data and log directories
-      machine.succeed(
-          "mkdir -p ${pgadmin4Dir} \
-          && mkdir -p ${pgadmin4LogDir} \
-          && mkdir -p ${pgadmin4SrcDir}"
-      )
-
-      machine.succeed(
-           "tar xvzf ${pkgs.pgadmin4.src} -C ${pgadmin4SrcDir}"
-      )
-
-      machine.wait_for_file("${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/README.md")
-
-      # set paths and config for tests
-      machine.succeed(
-           "cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version} \
-           && cp -v web/regression/test_config.json.in web/regression/test_config.json \
-           && sed -i 's|PostgreSQL 9.4|PostgreSQL|' web/regression/test_config.json \
-           && sed -i 's|/opt/PostgreSQL/9.4/bin/|${pkgs.postgresql}/bin|' web/regression/test_config.json \
-           && sed -i 's|\"headless_chrome\": false|\"headless_chrome\": true|' web/regression/test_config.json"
-      )
-
-      # adapt chrome config to run within a sandbox without GUI
-      # see https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t#50642913
-      # add chrome binary path. use spaces to satisfy python indention (tabs throw an error)
-      # this works for selenium 3 (currently used), but will need to be updated
-      # to work with "from selenium.webdriver.chrome.service import Service" in selenium 4
-      machine.succeed(
-           "cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version} \
-           && sed -i '\|options.add_argument(\"--disable-infobars\")|a \ \ \ \ \ \ \ \ options.binary_location = \"${pkgs.chromium}/bin/chromium\"' web/regression/runtests.py \
-           && sed -i '\|options.add_argument(\"--no-sandbox\")|a \ \ \ \ \ \ \ \ options.add_argument(\"--headless\")' web/regression/runtests.py \
-           && sed -i '\|options.add_argument(\"--disable-infobars\")|a \ \ \ \ \ \ \ \ options.add_argument(\"--disable-dev-shm-usage\")' web/regression/runtests.py \
-           && sed -i 's|(chrome_options=options)|(executable_path=\"${pkgs.chromedriver}/bin/chromedriver\", chrome_options=options)|' web/regression/runtests.py \
-           && sed -i 's|driver_local.maximize_window()||' web/regression/runtests.py"
-      )
-
-      # don't bother to test LDAP authentification
-      # exclude resql test due to recent postgres 14.4 update
-      # see bugreport here https://redmine.postgresql.org/issues/7527
-      with subtest("run browser test"):
-          machine.succeed(
-               'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-               && python regression/runtests.py \
-               --pkg browser \
-               --exclude browser.tests.test_ldap_login.LDAPLoginTestCase,browser.tests.test_ldap_login,resql'
-          )
-
-      # fontconfig is necessary for chromium to run
-      # https://github.com/NixOS/nixpkgs/issues/136207
-      with subtest("run feature test"):
-          machine.succeed(
-              'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-               && export FONTCONFIG_FILE=${pkgs.makeFontsConf { fontDirectories = [];}} \
-               && python regression/runtests.py --pkg feature_tests'
-          )
-
-      # reactivate this test again, when the postgres 14.4 test has been fixed
-      # with subtest("run resql test"):
-      #    machine.succeed(
-      #         'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-      #         && python regression/runtests.py --pkg resql'
-      #    )
-    '';
-  })
+      machine.wait_for_unit("pgadmin")
+      machine.wait_until_succeeds("curl -sS localhost:5051")
+      machine.wait_until_succeeds("curl -sS localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      # check for missing support files (css, js etc). Should catch not-generated files during build. See e.g. https://github.com/NixOS/nixpkgs/pull/229184
+      machine.succeed("wget -nv --level=1 --spider --recursive localhost:5051/login")
+
+    # pgadmin4 module saves the configuration to /etc/pgadmin/config_system.py
+    # pgadmin4-desktopmode tries to read that as well. This normally fails with a PermissionError, as the config file
+    # is owned by the user of the pgadmin module. With the check-system-config-dir.patch this will just throw a warning
+    # but will continue and not read the file.
+    # If we run pgadmin4-desktopmode as root (something one really shouldn't do), it can read the config file and fail,
+    # because of the wrong config for desktopmode.
+    with subtest("Check pgadmin standalone desktop mode"):
+      machine.execute("sudo -u alice pgadmin4 >&2 &", timeout=60)
+      machine.wait_until_succeeds("curl -sS localhost:5050")
+      machine.wait_until_succeeds("curl -sS localhost:5050/browser/ | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      machine.succeed("wget -nv --level=1 --spider --recursive localhost:5050/browser")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/phosh.nix b/nixpkgs/nixos/tests/phosh.nix
new file mode 100644
index 000000000000..78d6da31beee
--- /dev/null
+++ b/nixpkgs/nixos/tests/phosh.nix
@@ -0,0 +1,70 @@
+import ./make-test-python.nix ({ pkgs, ...}: let
+  pin = "1234";
+in {
+  name = "phosh";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry zhaofengli ];
+  };
+
+  nodes = {
+    phone = { config, pkgs, ... }: {
+      users.users.nixos = {
+        isNormalUser = true;
+        password = pin;
+      };
+
+      services.xserver.desktopManager.phosh = {
+        enable = true;
+        user = "nixos";
+        group = "users";
+
+        phocConfig = {
+          outputs.Virtual-1 = {
+            scale = 2;
+          };
+        };
+      };
+
+      systemd.services.phosh = {
+        environment = {
+          # Accelerated graphics fail on phoc 0.20 (wlroots 0.15)
+          "WLR_RENDERER" = "pixman";
+        };
+      };
+
+      virtualisation.resolution = { x = 720; y = 1440; };
+      virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci,xres=720,yres=1440" ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    import time
+
+    start_all()
+    phone.wait_for_unit("phosh.service")
+
+    with subtest("Check that we can see the lock screen info page"):
+        # Saturday, January 1
+        phone.succeed("timedatectl set-time '2022-01-01 07:00'")
+
+        phone.wait_for_text("Saturday")
+        phone.screenshot("01lockinfo")
+
+    with subtest("Check that we can unlock the screen"):
+        phone.send_chars("${pin}", delay=0.2)
+        time.sleep(1)
+        phone.screenshot("02unlock")
+
+        phone.send_chars("\n")
+
+        phone.wait_for_text("All Apps")
+        phone.screenshot("03launcher")
+
+    with subtest("Check the on-screen keyboard shows"):
+        phone.send_chars("setting", delay=0.2)
+        phone.wait_for_text("123") # A button on the OSK
+        phone.screenshot("04osk")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/photoprism.nix b/nixpkgs/nixos/tests/photoprism.nix
new file mode 100644
index 000000000000..a77ab59f5c9a
--- /dev/null
+++ b/nixpkgs/nixos/tests/photoprism.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "photoprism";
+  meta.maintainers = with lib.maintainers; [ stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.photoprism = {
+      enable = true;
+      port = 8080;
+      originalsPath = "/media/photos/";
+      passwordFile = pkgs.writeText "password" "secret";
+    };
+    environment.extraInit = ''
+      mkdir -p /media/photos
+    '';
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(8080)
+    response = machine.succeed("curl -vvv -s -H 'Host: photoprism' http://127.0.0.1:8080/library/login")
+    assert '<title>PhotoPrism</title>' in response, "Login page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/php/pcre.nix b/nixpkgs/nixos/tests/php/pcre.nix
index 57407477f4b8..8e37d5dcf97b 100644
--- a/nixpkgs/nixos/tests/php/pcre.nix
+++ b/nixpkgs/nixos/tests/php/pcre.nix
@@ -1,7 +1,7 @@
 let
   testString = "can-use-subgroups";
 in
-import ../make-test-python.nix ({ lib, php, ... }: {
+import ../make-test-python.nix ({ pkgs, lib, php, ... }: {
   name = "php-${php.version}-httpd-pcre-jit-test";
   meta.maintainers = lib.teams.php.members;
 
@@ -31,12 +31,22 @@ import ../make-test-python.nix ({ lib, php, ... }: {
         '';
     };
   };
-  testScript = { ... }:
-    ''
+  testScript = let
+    # PCRE JIT SEAlloc feature does not play well with fork()
+    # The feature needs to either be disabled or PHP configured correctly
+    # More information in https://bugs.php.net/bug.php?id=78927 and https://bugs.php.net/bug.php?id=78630
+    pcreJitSeallocForkIssue = pkgs.writeText "pcre-jit-sealloc-issue.php" ''
+      <?php
+      preg_match('/nixos/', 'nixos');
+      $pid = pcntl_fork();
+      pcntl_wait($pid);
+    '';
+  in ''
       machine.wait_for_unit("httpd.service")
       # Ensure php evaluation by matching on the var_dump syntax
       response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/index.php")
       expected = 'string(${toString (builtins.stringLength testString)}) "${testString}"'
       assert expected in response, "Does not appear to be able to use subgroups."
+      machine.succeed("${php}/bin/php -f ${pcreJitSeallocForkIssue}")
     '';
 })
diff --git a/nixpkgs/nixos/tests/pinnwand.nix b/nixpkgs/nixos/tests/pinnwand.nix
index 0391c4133111..42b26e08c189 100644
--- a/nixpkgs/nixos/tests/pinnwand.nix
+++ b/nixpkgs/nixos/tests/pinnwand.nix
@@ -1,27 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ...}:
 let
-  pythonEnv = pkgs.python3.withPackages (py: with py; [ appdirs toml ]);
-
   port = 8000;
   baseUrl = "http://server:${toString port}";
-
-  configureSteck = pkgs.writeScript "configure.py" ''
-    #!${pythonEnv.interpreter}
-    import appdirs
-    import toml
-    import os
-
-    CONFIG = {
-      "base": "${baseUrl}/",
-      "confirm": False,
-      "magic": True,
-      "ignore": True
-    }
-
-    os.makedirs(appdirs.user_config_dir('steck'))
-    with open(os.path.join(appdirs.user_config_dir('steck'), 'steck.toml'), "w") as fd:
-        toml.dump(CONFIG, fd)
-    '';
 in
 {
   name = "pinnwand";
@@ -44,7 +24,32 @@ in
 
     client = { pkgs, ... }:
     {
-      environment.systemPackages = [ pkgs.steck ];
+      environment.systemPackages = [
+        pkgs.steck
+
+        (pkgs.writers.writePython3Bin "setup-steck.py" {
+          libraries = with pkgs.python3.pkgs; [ appdirs toml ];
+          flakeIgnore = [
+            "E501"
+          ];
+        }
+        ''
+          import appdirs
+          import toml
+          import os
+
+          CONFIG = {
+              "base": "${baseUrl}/",
+              "confirm": False,
+              "magic": True,
+              "ignore": True
+          }
+
+          os.makedirs(appdirs.user_config_dir('steck'))
+          with open(os.path.join(appdirs.user_config_dir('steck'), 'steck.toml'), "w") as fd:
+              toml.dump(CONFIG, fd)
+        '')
+      ];
     };
   };
 
@@ -55,7 +60,7 @@ in
     client.wait_for_unit("network.target")
 
     # create steck.toml config file
-    client.succeed("${configureSteck}")
+    client.succeed("setup-steck.py")
 
     # wait until the server running pinnwand is reachable
     client.wait_until_succeeds("ping -c1 server")
@@ -75,12 +80,6 @@ in
         if line.startswith("Removal link:"):
             removal_link = line.split(":", 1)[1]
 
-
-    # start the reaper, it shouldn't do anything meaningful here
-    server.systemctl("start pinnwand-reaper.service")
-    server.wait_until_fails("systemctl is-active -q pinnwand-reaper.service")
-    server.log(server.execute("journalctl -u pinnwand-reaper -e --no-pager")[1])
-
     # check whether paste matches what we sent
     client.succeed(f"curl {raw_url} > /tmp/machine-id")
     client.succeed("diff /tmp/machine-id /etc/machine-id")
@@ -89,6 +88,6 @@ in
     client.succeed(f"curl {removal_link}")
     client.fail(f"curl --fail {raw_url}")
 
-    server.log(server.succeed("systemd-analyze security pinnwand"))
+    server.log(server.execute("systemd-analyze security pinnwand | grep '✗'")[1])
   '';
 })
diff --git a/nixpkgs/nixos/tests/plasma-bigscreen.nix b/nixpkgs/nixos/tests/plasma-bigscreen.nix
new file mode 100644
index 000000000000..1c61cafcbff3
--- /dev/null
+++ b/nixpkgs/nixos/tests/plasma-bigscreen.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma-bigscreen";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ttuegel k900 ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    services.xserver.displayManager.defaultSession = "plasma-bigscreen-x11";
+    services.xserver.desktopManager.plasma5.bigscreen.enable = true;
+    services.xserver.displayManager.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
+
+    users.users.alice.extraGroups = ["uinput"];
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    xdo = "${pkgs.xdotool}/bin/xdotool";
+  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("Plasma Big Screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/please.nix b/nixpkgs/nixos/tests/please.nix
new file mode 100644
index 000000000000..af825ae4b9b3
--- /dev/null
+++ b/nixpkgs/nixos/tests/please.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "please";
+  meta.maintainers = with lib.maintainers; [ azahi ];
+
+  nodes.machine =
+    { ... }:
+    {
+      users.users = lib.mkMerge [
+        (lib.listToAttrs (map
+          (n: lib.nameValuePair n { isNormalUser = true; })
+          (lib.genList (x: "user${toString x}") 6)))
+        {
+          user0.extraGroups = [ "wheel" ];
+        }
+      ];
+
+      security.please = {
+        enable = true;
+        wheelNeedsPassword = false;
+        settings = {
+          user2_run_true_as_root = {
+            name = "user2";
+            target = "root";
+            rule = "/run/current-system/sw/bin/true";
+            require_pass = false;
+          };
+          user4_edit_etc_hosts_as_root = {
+            name = "user4";
+            type = "edit";
+            target = "root";
+            rule = "/etc/hosts";
+            editmode = 644;
+            require_pass = false;
+          };
+        };
+      };
+    };
+
+  testScript = ''
+    with subtest("root: can run anything by default"):
+        machine.succeed('please true')
+    with subtest("root: can edit anything by default"):
+        machine.succeed('EDITOR=cat pleaseedit /etc/hosts')
+
+    with subtest("user0: can run as root because it's in the wheel group"):
+        machine.succeed('su - user0 -c "please -u root true"')
+    with subtest("user1: cannot run as root because it's not in the wheel group"):
+        machine.fail('su - user1 -c "please -u root true"')
+
+    with subtest("user0: can edit as root"):
+        machine.succeed('su - user0 -c "EDITOR=cat pleaseedit /etc/hosts"')
+    with subtest("user1: cannot edit as root"):
+        machine.fail('su - user1 -c "EDITOR=cat pleaseedit /etc/hosts"')
+
+    with subtest("user2: can run 'true' as root"):
+        machine.succeed('su - user2 -c "please -u root true"')
+    with subtest("user3: cannot run 'true' as root"):
+        machine.fail('su - user3 -c "please -u root true"')
+
+    with subtest("user4: can edit /etc/hosts"):
+        machine.succeed('su - user4 -c "EDITOR=cat pleaseedit /etc/hosts"')
+    with subtest("user5: cannot edit /etc/hosts"):
+        machine.fail('su - user5 -c "EDITOR=cat pleaseedit /etc/hosts"')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pleroma.nix b/nixpkgs/nixos/tests/pleroma.nix
index 8998716243a2..4f1aef854146 100644
--- a/nixpkgs/nixos/tests/pleroma.nix
+++ b/nixpkgs/nixos/tests/pleroma.nix
@@ -170,8 +170,8 @@ import ./make-test-python.nix ({ pkgs, ... }:
   '';
 
   hosts = nodes: ''
-    ${nodes.pleroma.config.networking.primaryIPAddress} pleroma.nixos.test
-    ${nodes.client.config.networking.primaryIPAddress} client.nixos.test
+    ${nodes.pleroma.networking.primaryIPAddress} pleroma.nixos.test
+    ${nodes.client.networking.primaryIPAddress} client.nixos.test
   '';
   in {
   name = "pleroma";
diff --git a/nixpkgs/nixos/tests/podman/default.nix b/nixpkgs/nixos/tests/podman/default.nix
index 67c7823c5a31..0e1f420f2a7d 100644
--- a/nixpkgs/nixos/tests/podman/default.nix
+++ b/nixpkgs/nixos/tests/podman/default.nix
@@ -1,5 +1,3 @@
-# This test runs podman and checks if simple container starts
-
 import ../make-test-python.nix (
   { pkgs, lib, ... }: {
     name = "podman";
@@ -8,31 +6,45 @@ import ../make-test-python.nix (
     };
 
     nodes = {
-      podman =
-        { pkgs, ... }:
-        {
-          virtualisation.podman.enable = true;
-
-          # To test docker socket support
-          virtualisation.podman.dockerSocket.enable = true;
-          environment.systemPackages = [
-            pkgs.docker-client
-          ];
-
-          users.users.alice = {
-            isNormalUser = true;
-            home = "/home/alice";
-            description = "Alice Foobar";
-            extraGroups = [ "podman" ];
-          };
-
-          users.users.mallory = {
-            isNormalUser = true;
-            home = "/home/mallory";
-            description = "Mallory Foobar";
-          };
+      rootful = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        # hack to ensure that podman built with and without zfs in extraPackages is cached
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "00000000";
+      };
+      rootless = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        users.users.alice = {
+          isNormalUser = true;
+        };
+      };
+      dns = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
+
+        networking.firewall.allowedUDPPorts = [ 53 ];
+      };
+      docker = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        virtualisation.podman.dockerSocket.enable = true;
+
+        environment.systemPackages = [
+          pkgs.docker-client
+        ];
+
+        users.users.alice = {
+          isNormalUser = true;
+          extraGroups = [ "podman" ];
+        };
 
+        users.users.mallory = {
+          isNormalUser = true;
         };
+      };
     };
 
     testScript = ''
@@ -44,98 +56,125 @@ import ../make-test-python.nix (
           return f"su {user} -l -c {cmd}"
 
 
-      podman.wait_for_unit("sockets.target")
+      rootful.wait_for_unit("sockets.target")
+      rootless.wait_for_unit("sockets.target")
+      dns.wait_for_unit("sockets.target")
+      docker.wait_for_unit("sockets.target")
       start_all()
 
       with subtest("Run container as root with runc"):
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
               "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("podman stop sleeping")
-          podman.succeed("podman rm sleeping")
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
 
       with subtest("Run container as root with crun"):
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
               "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("podman stop sleeping")
-          podman.succeed("podman rm sleeping")
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
 
       with subtest("Run container as root with the default backend"):
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
               "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("podman stop sleeping")
-          podman.succeed("podman rm sleeping")
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
 
-      # create systemd session for rootless
-      podman.succeed("loginctl enable-linger alice")
+      # start systemd session for rootless
+      rootless.succeed("loginctl enable-linger alice")
+      rootless.succeed(su_cmd("whoami"))
+      rootless.sleep(1)
 
       with subtest("Run container rootless with runc"):
-          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
-          podman.succeed(
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.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"
               )
           )
-          podman.succeed(su_cmd("podman ps | grep sleeping"))
-          podman.succeed(su_cmd("podman stop sleeping"))
-          podman.succeed(su_cmd("podman rm sleeping"))
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
 
       with subtest("Run container rootless with crun"):
-          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
-          podman.succeed(
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.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"
               )
           )
-          podman.succeed(su_cmd("podman ps | grep sleeping"))
-          podman.succeed(su_cmd("podman stop sleeping"))
-          podman.succeed(su_cmd("podman rm sleeping"))
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
 
       with subtest("Run container rootless with the default backend"):
-          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
-          podman.succeed(
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
               su_cmd(
                   "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
               )
           )
-          podman.succeed(su_cmd("podman ps | grep sleeping"))
-          podman.succeed(su_cmd("podman stop sleeping"))
-          podman.succeed(su_cmd("podman rm sleeping"))
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
+
+      with subtest("rootlessport"):
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
+              su_cmd(
+                  "podman run -d -p 9000:8888 --name=rootlessport -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8888"
+              )
+          )
+          rootless.succeed(su_cmd("podman ps | grep rootlessport"))
+          rootless.wait_until_succeeds(su_cmd("${pkgs.curl}/bin/curl localhost:9000 | grep Testing"))
+          rootless.succeed(su_cmd("podman stop rootlessport"))
+          rootless.succeed(su_cmd("podman rm rootlessport"))
 
       with subtest("Run container with init"):
-          podman.succeed(
+          rootful.succeed(
               "tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - busybox"
           )
-          pid = podman.succeed("podman run --rm busybox readlink /proc/self").strip()
+          pid = rootful.succeed("podman run --rm busybox readlink /proc/self").strip()
           assert pid == "1"
-          pid = podman.succeed("podman run --rm --init busybox readlink /proc/self").strip()
+          pid = rootful.succeed("podman run --rm --init busybox readlink /proc/self").strip()
           assert pid == "2"
 
+      with subtest("aardvark-dns"):
+          dns.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          dns.succeed(
+              "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8000"
+          )
+          dns.succeed("podman ps | grep webserver")
+          dns.wait_until_succeeds(
+              "podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${pkgs.curl}/bin/curl http://webserver:8000 | grep Testing"
+          )
+          dns.succeed("podman stop webserver")
+          dns.succeed("podman rm webserver")
+
       with subtest("A podman member can use the docker cli"):
-          podman.succeed(su_cmd("docker version"))
+          docker.succeed(su_cmd("docker version"))
 
       with subtest("Run container via docker cli"):
-          podman.succeed("docker network create default")
-          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
-          podman.succeed(
+          docker.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          docker.succeed(
             "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin localhost/scratchimg /bin/sleep 10"
           )
-          podman.succeed("docker ps | grep sleeping")
-          podman.succeed("podman ps | grep sleeping")
-          podman.succeed("docker stop sleeping")
-          podman.succeed("docker rm sleeping")
-          podman.succeed("docker network rm default")
+          docker.succeed("docker ps | grep sleeping")
+          docker.succeed("podman ps | grep sleeping")
+          docker.succeed("docker stop sleeping")
+          docker.succeed("docker rm sleeping")
 
       with subtest("A podman non-member can not use the docker cli"):
-          podman.fail(su_cmd("docker version", user="mallory"))
+          docker.fail(su_cmd("docker version", user="mallory"))
 
       # TODO: add docker-compose test
 
diff --git a/nixpkgs/nixos/tests/podman/dnsname.nix b/nixpkgs/nixos/tests/podman/dnsname.nix
deleted file mode 100644
index 3768ae79e067..000000000000
--- a/nixpkgs/nixos/tests/podman/dnsname.nix
+++ /dev/null
@@ -1,42 +0,0 @@
-import ../make-test-python.nix (
-  { pkgs, lib, ... }:
-  let
-    inherit (pkgs) writeTextDir python3 curl;
-    webroot = writeTextDir "index.html" "<h1>Hi</h1>";
-  in
-  {
-    name = "podman-dnsname";
-    meta = {
-      maintainers = with lib.maintainers; [ roberth ] ++ lib.teams.podman.members;
-    };
-
-    nodes = {
-      podman = { pkgs, ... }: {
-        virtualisation.podman.enable = true;
-        virtualisation.podman.defaultNetwork.dnsname.enable = true;
-      };
-    };
-
-    testScript = ''
-      podman.wait_for_unit("sockets.target")
-
-      with subtest("DNS works"): # also tests inter-container tcp routing
-        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"
-        )
-        podman.succeed("podman ps | grep webserver")
-        podman.succeed("""
-          for i in `seq 0 120`; do
-            podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${curl}/bin/curl http://webserver:8000 >/dev/console \
-              && exit 0
-            sleep 0.5
-          done
-          exit 1
-        """)
-        podman.succeed("podman stop webserver")
-        podman.succeed("podman rm webserver")
-
-    '';
-  }
-)
diff --git a/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix b/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
index 268a55701ccf..52c31dc21f10 100644
--- a/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
+++ b/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
@@ -113,9 +113,6 @@ import ../make-test-python.nix (
       podman.wait_for_unit("sockets.target")
       podman.wait_for_unit("ghostunnel-server-podman-socket.service")
 
-      with subtest("Create default network"):
-          podman.succeed("docker network create default")
-
       with subtest("Root docker cli also works"):
           podman.succeed("docker version")
 
diff --git a/nixpkgs/nixos/tests/polaris.nix b/nixpkgs/nixos/tests/polaris.nix
index fb2e67f075aa..bb105d600032 100644
--- a/nixpkgs/nixos/tests/polaris.nix
+++ b/nixpkgs/nixos/tests/polaris.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "polaris";
-  meta.maintainers = with maintainers; [ pbsds ];
+  meta.maintainers = with lib.maintainers; [ pbsds ];
 
   nodes.machine =
     { pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/pomerium.nix b/nixpkgs/nixos/tests/pomerium.nix
index 7af828326448..abaf56c518e0 100644
--- a/nixpkgs/nixos/tests/pomerium.nix
+++ b/nixpkgs/nixos/tests/pomerium.nix
@@ -20,6 +20,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   }; in {
     pomerium = { pkgs, lib, ... }: {
       imports = [ (base "192.168.1.1") ];
+      environment.systemPackages = with pkgs; [ chromium ];
       services.pomerium = {
         enable = true;
         settings = {
@@ -98,5 +99,11 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         pomerium.succeed(
             "curl -L --resolve login.required:80:127.0.0.1 http://login.required | grep 'hello I am login page'"
         )
+
+    with subtest("ui"):
+        pomerium.succeed(
+          # check for a string that only appears if the UI is displayed correctly
+            "chromium --no-sandbox --headless --disable-gpu --dump-dom --host-resolver-rules='MAP login.required 127.0.0.1:80' http://login.required/.pomerium | grep 'contact your administrator'"
+        )
   '';
 })
diff --git a/nixpkgs/nixos/tests/portunus.nix b/nixpkgs/nixos/tests/portunus.nix
new file mode 100644
index 000000000000..6fcae7e1c4ce
--- /dev/null
+++ b/nixpkgs/nixos/tests/portunus.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "portunus";
+  meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
+
+  nodes.machine = _: {
+    services.portunus = {
+      enable = true;
+      ldap.suffix = "dc=example,dc=org";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("portunus.service")
+    machine.succeed("curl --fail -vvv http://localhost:8080/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/postgresql-jit.nix b/nixpkgs/nixos/tests/postgresql-jit.nix
new file mode 100644
index 000000000000..baf26b8da2b3
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgresql-jit.nix
@@ -0,0 +1,48 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+  packages = builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs);
+
+  mkJitTest = packageName: makeTest {
+    name = "${packageName}";
+    meta.maintainers = with lib.maintainers; [ ma27 ];
+    nodes.machine = { pkgs, lib, ... }: {
+      services.postgresql = {
+        enable = true;
+        enableJIT = true;
+        package = pkgs.${packageName};
+        initialScript = pkgs.writeText "init.sql" ''
+          create table demo (id int);
+          insert into demo (id) select generate_series(1, 5);
+        '';
+      };
+    };
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("postgresql.service")
+
+      with subtest("JIT is enabled"):
+          machine.succeed("sudo -u postgres psql <<<'show jit;' | grep 'on'")
+
+      with subtest("Test JIT works fine"):
+          output = machine.succeed(
+              "cat ${pkgs.writeText "test.sql" ''
+                set jit_above_cost = 1;
+                EXPLAIN ANALYZE SELECT CONCAT('jit result = ', SUM(id)) FROM demo;
+                SELECT CONCAT('jit result = ', SUM(id)) from demo;
+              ''} | sudo -u postgres psql"
+          )
+          assert "JIT:" in output
+          assert "jit result = 15" in output
+
+      machine.shutdown()
+    '';
+  };
+in
+lib.genAttrs packages mkJitTest
diff --git a/nixpkgs/nixos/tests/postgresql-wal-receiver.nix b/nixpkgs/nixos/tests/postgresql-wal-receiver.nix
index ae2708546f5d..b0bd7711dbcd 100644
--- a/nixpkgs/nixos/tests/postgresql-wal-receiver.nix
+++ b/nixpkgs/nixos/tests/postgresql-wal-receiver.nix
@@ -116,4 +116,4 @@ let
     };
 
 # Maps the generic function over all attributes of PostgreSQL packages
-in builtins.listToAttrs (map makePostgresqlWalReceiverTest (builtins.attrNames (import ../../pkgs/servers/sql/postgresql { })))
+in builtins.listToAttrs (map makePostgresqlWalReceiverTest (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs)))
diff --git a/nixpkgs/nixos/tests/postgresql.nix b/nixpkgs/nixos/tests/postgresql.nix
index 7864f5d6ff32..b44849e0a14e 100644
--- a/nixpkgs/nixos/tests/postgresql.nix
+++ b/nixpkgs/nixos/tests/postgresql.nix
@@ -130,8 +130,97 @@ let
     '';
 
   };
+
+  mk-ensure-clauses-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ zagy ];
+    };
+
+    nodes.machine = {...}:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          ensureUsers = [
+            {
+              name = "all-clauses";
+              ensureClauses = {
+                superuser = true;
+                createdb = true;
+                createrole = true;
+                "inherit" = true;
+                login = true;
+                replication = true;
+                bypassrls = true;
+              };
+            }
+            {
+              name = "default-clauses";
+            }
+          ];
+        };
+      };
+
+    testScript = let
+      getClausesQuery = user: pkgs.lib.concatStringsSep " "
+        [
+          "SELECT row_to_json(row)"
+          "FROM ("
+          "SELECT"
+            "rolsuper,"
+            "rolinherit,"
+            "rolcreaterole,"
+            "rolcreatedb,"
+            "rolcanlogin,"
+            "rolreplication,"
+            "rolbypassrls"
+          "FROM pg_roles"
+          "WHERE rolname = '${user}'"
+          ") row;"
+        ];
+    in ''
+      import json
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("All user permissions are set according to the ensureClauses attr"):
+          clauses = json.loads(
+            machine.succeed(
+                "sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\""
+            )
+          )
+          print(clauses)
+          assert clauses['rolsuper'], 'expected user with clauses to have superuser clause'
+          assert clauses['rolinherit'], 'expected user with clauses to have inherit clause'
+          assert clauses['rolcreaterole'], 'expected user with clauses to have create role clause'
+          assert clauses['rolcreatedb'], 'expected user with clauses to have create db clause'
+          assert clauses['rolcanlogin'], 'expected user with clauses to have login clause'
+          assert clauses['rolreplication'], 'expected user with clauses to have replication clause'
+          assert clauses['rolbypassrls'], 'expected user with clauses to have bypassrls clause'
+
+      with subtest("All user permissions default when ensureClauses is not provided"):
+          clauses = json.loads(
+            machine.succeed(
+                "sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\""
+            )
+          )
+          assert not clauses['rolsuper'], 'expected user with no clauses set to have default superuser clause'
+          assert clauses['rolinherit'], 'expected user with no clauses set to have default inherit clause'
+          assert not clauses['rolcreaterole'], 'expected user with no clauses set to have default create role clause'
+          assert not clauses['rolcreatedb'], 'expected user with no clauses set to have default create db clause'
+          assert clauses['rolcanlogin'], 'expected user with no clauses set to have default login clause'
+          assert not clauses['rolreplication'], 'expected user with no clauses set to have default replication clause'
+          assert not clauses['rolbypassrls'], 'expected user with no clauses set to have default bypassrls clause'
+
+      machine.shutdown()
+    '';
+  };
 in
-  (mapAttrs' (name: package: { inherit name; value=make-postgresql-test name package false;}) postgresql-versions) // {
+  concatMapAttrs (name: package: {
+    ${name} = make-postgresql-test name package false;
+    ${name + "-clauses"} = mk-ensure-clauses-test name package;
+  }) postgresql-versions
+  // {
     postgresql_11-backup-all = make-postgresql-test "postgresql_11-backup-all" postgresql-versions.postgresql_11 true;
   }
-
diff --git a/nixpkgs/nixos/tests/power-profiles-daemon.nix b/nixpkgs/nixos/tests/power-profiles-daemon.nix
index 278e94711830..c887cde4b829 100644
--- a/nixpkgs/nixos/tests/power-profiles-daemon.nix
+++ b/nixpkgs/nixos/tests/power-profiles-daemon.nix
@@ -6,6 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ mvnetbiz ];
   };
   nodes.machine = { pkgs, ... }: {
+    security.polkit.enable = true;
     services.power-profiles-daemon.enable = true;
     environment.systemPackages = [ pkgs.glib ];
   };
diff --git a/nixpkgs/nixos/tests/powerdns-admin.nix b/nixpkgs/nixos/tests/powerdns-admin.nix
index 4d763c9c6f6e..d7bacb24eec5 100644
--- a/nixpkgs/nixos/tests/powerdns-admin.nix
+++ b/nixpkgs/nixos/tests/powerdns-admin.nix
@@ -10,6 +10,7 @@ let
   defaultConfig = ''
     BIND_ADDRESS = '127.0.0.1'
     PORT = 8000
+    CAPTCHA_ENABLE = False
   '';
 
   makeAppTest = name: configs: makeTest {
@@ -98,7 +99,30 @@ let
       tcp = {
         services.powerdns-admin.extraArgs = [ "-b" "127.0.0.1:8000" ];
         system.build.testScript = ''
+          set -euxo pipefail
           curl -sSf http://127.0.0.1:8000/
+
+          # Create account to check that the database migrations ran
+          csrf_token="$(curl -sSfc session http://127.0.0.1:8000/register | grep _csrf_token | cut -d\" -f6)"
+          # Outputs 'Redirecting' if successful
+          curl -sSfb session http://127.0.0.1:8000/register \
+            -F "_csrf_token=$csrf_token" \
+            -F "firstname=first" \
+            -F "lastname=last" \
+            -F "email=a@example.com" \
+            -F "username=user" \
+            -F "password=password" \
+            -F "rpassword=password" | grep Redirecting
+
+          # Login
+          # Outputs 'Redirecting' if successful
+          curl -sSfb session http://127.0.0.1:8000/login \
+            -F "_csrf_token=$csrf_token" \
+            -F "username=user" \
+            -F "password=password" | grep Redirecting
+
+          # Check that we are logged in, this redirects to /admin/setting/pdns if we are
+          curl -sSfb session http://127.0.0.1:8000/dashboard/ | grep /admin/setting
         '';
       };
       unix = {
diff --git a/nixpkgs/nixos/tests/pppd.nix b/nixpkgs/nixos/tests/pppd.nix
index bda0aa75bb50..d599f918036f 100644
--- a/nixpkgs/nixos/tests/pppd.nix
+++ b/nixpkgs/nixos/tests/pppd.nix
@@ -5,6 +5,8 @@ import ./make-test-python.nix (
       mode = "0640";
     };
   in {
+    name = "pppd";
+
     nodes = {
       server = {config, pkgs, ...}: {
         config = {
@@ -26,7 +28,7 @@ import ./make-test-python.nix (
             "ppp/pppoe-server-options".text = ''
               lcp-echo-interval 10
               lcp-echo-failure 2
-              plugin rp-pppoe.so
+              plugin pppoe.so
               require-chap
               nobsdcomp
               noccp
@@ -41,7 +43,7 @@ import ./make-test-python.nix (
           enable = true;
           peers.test = {
             config = ''
-              plugin rp-pppoe.so eth1
+              plugin pppoe.so eth1
               name "flynn"
               noipdefault
               persist
diff --git a/nixpkgs/nixos/tests/predictable-interface-names.nix b/nixpkgs/nixos/tests/predictable-interface-names.nix
index 08773120bc12..42183625c7c9 100644
--- a/nixpkgs/nixos/tests/predictable-interface-names.nix
+++ b/nixpkgs/nixos/tests/predictable-interface-names.nix
@@ -8,25 +8,48 @@ let
   testCombinations = pkgs.lib.cartesianProductOfSets {
     predictable = [true false];
     withNetworkd = [true false];
+    systemdStage1 = [true false];
   };
-in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: {
+in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd, systemdStage1 }: {
   name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
-       + pkgs.lib.optionalString withNetworkd "Networkd";
+       + pkgs.lib.optionalString withNetworkd "Networkd"
+       + pkgs.lib.optionalString systemdStage1 "SystemdStage1";
   value = makeTest {
-    name = "${if predictable then "" else "un"}predictableInterfaceNames${if withNetworkd then "-with-networkd" else ""}";
+    name = pkgs.lib.optionalString (!predictable) "un" + "predictableInterfaceNames"
+         + pkgs.lib.optionalString withNetworkd "-with-networkd"
+         + pkgs.lib.optionalString systemdStage1 "-systemd-stage-1";
     meta = {};
 
-    nodes.machine = { lib, ... }: {
+    nodes.machine = { lib, ... }: let
+      script = ''
+        ip link
+        if ${lib.optionalString predictable "!"} ip link show eth0; then
+          echo Success
+        else
+          exit 1
+        fi
+      '';
+    in {
       networking.usePredictableInterfaceNames = lib.mkForce predictable;
       networking.useNetworkd = withNetworkd;
       networking.dhcpcd.enable = !withNetworkd;
       networking.useDHCP = !withNetworkd;
 
       # Check if predictable interface names are working in stage-1
-      boot.initrd.postDeviceCommands = ''
-        ip link
-        ip link show eth0 ${if predictable then "&&" else "||"} exit 1
-      '';
+      boot.initrd.postDeviceCommands = script;
+
+      boot.initrd.systemd = lib.mkIf systemdStage1 {
+        enable = true;
+        initrdBin = [ pkgs.iproute2 ];
+        services.systemd-udev-settle.wantedBy = ["initrd.target"];
+        services.check-interfaces = {
+          requiredBy = ["initrd.target"];
+          after = ["systemd-udev-settle.service"];
+          serviceConfig.Type = "oneshot";
+          path = [ pkgs.iproute2 ];
+          inherit script;
+        };
+      };
     };
 
     testScript = ''
diff --git a/nixpkgs/nixos/tests/printing.nix b/nixpkgs/nixos/tests/printing.nix
index 6338fd8d8ac1..7df042e72e90 100644
--- a/nixpkgs/nixos/tests/printing.nix
+++ b/nixpkgs/nixos/tests/printing.nix
@@ -1,18 +1,31 @@
 # Test printing via CUPS.
 
-import ./make-test-python.nix ({pkgs, ... }:
-let
-  printingServer = startWhenNeeded: {
-    services.printing.enable = true;
-    services.printing.startWhenNeeded = startWhenNeeded;
-    services.printing.listenAddresses = [ "*:631" ];
-    services.printing.defaultShared = true;
-    services.printing.extraConf = ''
-      <Location />
-        Order allow,deny
-        Allow from all
-      </Location>
-    '';
+import ./make-test-python.nix (
+{ pkgs
+, socket ? true # whether to use socket activation
+, ...
+}:
+
+{
+  name = "printing";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ domenkozar eelco matthewbauer ];
+  };
+
+  nodes.server = { ... }: {
+    services.printing = {
+      enable = true;
+      stateless = true;
+      startWhenNeeded = socket;
+      listenAddresses = [ "*:631" ];
+      defaultShared = true;
+      extraConf = ''
+        <Location />
+          Order allow,deny
+          Allow from all
+        </Location>
+      '';
+    };
     networking.firewall.allowedTCPPorts = [ 631 ];
     # Add a HP Deskjet printer connected via USB to the server.
     hardware.printers.ensurePrinters = [{
@@ -21,32 +34,19 @@ let
       model = "drv:///sample.drv/deskjet.ppd";
     }];
   };
-  printingClient = startWhenNeeded: {
+
+  nodes.client = { ... }: {
     services.printing.enable = true;
-    services.printing.startWhenNeeded = startWhenNeeded;
+    services.printing.startWhenNeeded = socket;
     # Add printer to the client as well, via IPP.
     hardware.printers.ensurePrinters = [{
       name = "DeskjetRemote";
-      deviceUri = "ipp://${if startWhenNeeded then "socketActivatedServer" else "serviceServer"}/printers/DeskjetLocal";
+      deviceUri = "ipp://server/printers/DeskjetLocal";
       model = "drv:///sample.drv/deskjet.ppd";
     }];
     hardware.printers.ensureDefaultPrinter = "DeskjetRemote";
   };
 
-in {
-  name = "printing";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ domenkozar eelco matthewbauer ];
-  };
-
-  nodes = {
-    socketActivatedServer = { ... }: (printingServer true);
-    serviceServer = { ... }: (printingServer false);
-
-    socketActivatedClient = { ... }: (printingClient true);
-    serviceClient = { ... }: (printingClient false);
-  };
-
   testScript = ''
     import os
     import re
@@ -54,75 +54,69 @@ in {
     start_all()
 
     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")
-        socketActivatedClient.wait_for_unit("ensure-printers.service")
-
+        server.wait_for_unit("cups.${if socket then "socket" else "service"}")
+        client.wait_for_unit("cups.${if socket then "socket" else "service"}")
+
+    assert "scheduler is running" in client.succeed("lpstat -r")
+
+    with subtest("UNIX socket is used for connections"):
+        assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
+
+    with subtest("HTTP server is available too"):
+        client.succeed("curl --fail http://localhost:631/")
+        client.succeed(f"curl --fail http://{server.name}:631/")
+        server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
+
+    with subtest("LP status checks"):
+        assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
+        assert "DeskjetLocal accepting requests" in client.succeed(
+            f"lpstat -h {server.name}:631 -a"
+        )
+        client.succeed("cupsdisable DeskjetRemote")
+        out = client.succeed("lpq")
+        print(out)
+        assert re.search(
+            "DeskjetRemote is not ready.*no entries",
+            client.succeed("lpq"),
+            flags=re.DOTALL,
+        )
+        client.succeed("cupsenable DeskjetRemote")
+        assert re.match(
+            "DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
+        )
+
+    # Test printing various file types.
+    for file in [
+        "${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
+        "${pkgs.groff.doc}/share/doc/*/meref.ps",
+        "${pkgs.cups.out}/share/doc/cups/images/cups.png",
+        "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
+    ]:
+        file_name = os.path.basename(file)
+        with subtest(f"print {file_name}"):
+            # Print the file on the client.
+            print(client.succeed("lpq"))
+            client.succeed(f"lp {file}")
+            client.wait_until_succeeds(
+                f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
+            )
 
-    def test_printing(client, server):
-        assert "scheduler is running" in client.succeed("lpstat -r")
+            # Ensure that a raw PCL file appeared in the server's queue
+            # (showing that the right filters have been applied).  Of
+            # course, since there is no actual USB printer attached, the
+            # file will stay in the queue forever.
+            server.wait_for_file("/var/spool/cups/d*-001")
+            server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
 
-        with subtest("UNIX socket is used for connections"):
-            assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
-        with subtest("HTTP server is available too"):
-            client.succeed("curl --fail http://localhost:631/")
-            client.succeed(f"curl --fail http://{server.name}:631/")
-            server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
+            # Delete the job on the client.  It should disappear on the
+            # server as well.
+            client.succeed("lprm")
+            client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
 
-        with subtest("LP status checks"):
-            assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
-            assert "DeskjetLocal accepting requests" in client.succeed(
-                f"lpstat -h {server.name}:631 -a"
-            )
-            client.succeed("cupsdisable DeskjetRemote")
-            out = client.succeed("lpq")
-            print(out)
-            assert re.search(
-                "DeskjetRemote is not ready.*no entries",
-                client.succeed("lpq"),
-                flags=re.DOTALL,
-            )
-            client.succeed("cupsenable DeskjetRemote")
-            assert re.match(
-                "DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
-            )
+            retry(lambda _: "no entries" in server.succeed("lpq -a"))
 
-        # Test printing various file types.
-        for file in [
-            "${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
-            "${pkgs.groff.doc}/share/doc/*/meref.ps",
-            "${pkgs.cups.out}/share/doc/cups/images/cups.png",
-            "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
-        ]:
-            file_name = os.path.basename(file)
-            with subtest(f"print {file_name}"):
-                # Print the file on the client.
-                print(client.succeed("lpq"))
-                client.succeed(f"lp {file}")
-                client.wait_until_succeeds(
-                    f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
-                )
-
-                # Ensure that a raw PCL file appeared in the server's queue
-                # (showing that the right filters have been applied).  Of
-                # course, since there is no actual USB printer attached, the
-                # file will stay in the queue forever.
-                server.wait_for_file("/var/spool/cups/d*-001")
-                server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
-
-                # Delete the job on the client.  It should disappear on the
-                # server as well.
-                client.succeed("lprm")
-                client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
-
-                retry(lambda _: "no entries" in server.succeed("lpq -a"))
-
-                # The queue is empty already, so this should be safe.
-                # Otherwise, pairs of "c*"-"d*-001" files might persist.
-                server.execute("rm /var/spool/cups/*")
-
-
-    test_printing(serviceClient, serviceServer)
-    test_printing(socketActivatedClient, socketActivatedServer)
+            # The queue is empty already, so this should be safe.
+            # Otherwise, pairs of "c*"-"d*-001" files might persist.
+            server.execute("rm /var/spool/cups/*")
   '';
 })
diff --git a/nixpkgs/nixos/tests/prometheus-exporters.nix b/nixpkgs/nixos/tests/prometheus-exporters.nix
index 4fdff7dbdab8..a69f2347b54b 100644
--- a/nixpkgs/nixos/tests/prometheus-exporters.nix
+++ b/nixpkgs/nixos/tests/prometheus-exporters.nix
@@ -6,7 +6,7 @@
 let
   inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
   inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
-    removeSuffix replaceChars singleton splitString;
+    removeSuffix replaceStrings singleton splitString;
 
   /*
     * The attrset `exporterTests` contains one attribute
@@ -182,7 +182,7 @@ let
         enable = true;
         extraFlags = [ "--web.collectd-push-path /collectd" ];
       };
-      exporterTest = let postData = replaceChars [ "\n" ] [ "" ] ''
+      exporterTest = let postData = replaceStrings [ "\n" ] [ "" ] ''
         [{
           "values":[23],
           "dstypes":["gauge"],
@@ -234,9 +234,7 @@ let
       exporterTest = ''
         wait_for_unit("prometheus-domain-exporter.service")
         wait_for_open_port(9222)
-        succeed(
-            "curl -sSf 'http://localhost:9222/probe?target=nixos.org' | grep 'domain_probe_success 0'"
-        )
+        succeed("curl -sSf 'http://localhost:9222/probe?target=nixos.org'")
       '';
     };
 
@@ -286,6 +284,29 @@ let
       '';
     };
 
+    graphite = {
+      exporterConfig = {
+        enable = true;
+        port = 9108;
+        graphitePort = 9109;
+        mappingSettings.mappings = [{
+          match = "test.*.*";
+          name = "testing";
+          labels = {
+            protocol = "$1";
+            author = "$2";
+          };
+        }];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-graphite-exporter.service")
+        wait_for_open_port(9108)
+        wait_for_open_port(9109)
+        succeed("echo test.tcp.foo-bar 1234 $(date +%s) | nc -w1 localhost 9109")
+        succeed("curl -sSf http://localhost:9108/metrics | grep 'testing{author=\"foo-bar\",protocol=\"tcp\"} 1234'")
+      '';
+    };
+
     influxdb = {
       exporterConfig = {
         enable = true;
@@ -307,6 +328,19 @@ let
       '';
     };
 
+    ipmi = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-ipmi-exporter.service")
+        wait_for_open_port(9290)
+        succeed(
+          "curl -sSf http://localhost:9290/metrics | grep 'ipmi_scrape_duration_seconds'"
+        )
+      '';
+    };
+
     jitsi = {
       exporterConfig = {
         enable = true;
@@ -315,7 +349,7 @@ let
         systemd.services.prometheus-jitsi-exporter.after = [ "jitsi-videobridge2.service" ];
         services.jitsi-videobridge = {
           enable = true;
-          apis = [ "colibri" "rest" ];
+          colibriRestApi = true;
         };
       };
       exporterTest = ''
@@ -335,9 +369,13 @@ let
         enable = true;
         url = "http://localhost";
         configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON {
-          metrics = [
-            { name = "json_test_metric"; path = "{ .test }"; }
-          ];
+          modules = {
+            default = {
+              metrics = [
+                { name = "json_test_metric"; path = "{ .test }"; }
+              ];
+            };
+          };
         });
       };
       metricProvider = {
@@ -361,25 +399,34 @@ let
     };
 
     kea = let
-      controlSocketPath = "/run/kea/dhcp6.sock";
+      controlSocketPathV4 = "/run/kea/dhcp4.sock";
+      controlSocketPathV6 = "/run/kea/dhcp6.sock";
     in
     {
       exporterConfig = {
         enable = true;
         controlSocketPaths = [
-          controlSocketPath
+          controlSocketPathV4
+          controlSocketPathV6
         ];
       };
       metricProvider = {
-        systemd.services.prometheus-kea-exporter.after = [ "kea-dhcp6-server.service" ];
-
         services.kea = {
+          dhcp4 = {
+            enable = true;
+            settings = {
+              control-socket = {
+                socket-type = "unix";
+                socket-name = controlSocketPathV4;
+              };
+            };
+          };
           dhcp6 = {
             enable = true;
             settings = {
               control-socket = {
                 socket-type = "unix";
-                socket-name = controlSocketPath;
+                socket-name = controlSocketPathV6;
               };
             };
           };
@@ -387,8 +434,10 @@ let
       };
 
       exporterTest = ''
+        wait_for_unit("kea-dhcp4-server.service")
         wait_for_unit("kea-dhcp6-server.service")
-        wait_for_file("${controlSocketPath}")
+        wait_for_file("${controlSocketPathV4}")
+        wait_for_file("${controlSocketPathV6}")
         wait_for_unit("prometheus-kea-exporter.service")
         wait_for_open_port(9547)
         succeed(
@@ -1036,6 +1085,20 @@ let
       '';
     };
 
+    shelly = {
+      exporterConfig = {
+        enable = true;
+        metrics-file = "${pkgs.writeText "test.json" ''{}''}";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-shelly-exporter.service")
+        wait_for_open_port(9784)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:9784/metrics'"
+        )
+      '';
+    };
+
     script = {
       exporterConfig = {
         enable = true;
@@ -1062,13 +1125,8 @@ let
         ];
       };
       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"'
+            'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Device unavailable"'
         )
       '';
     };
@@ -1153,6 +1211,27 @@ let
       '';
     };
 
+    statsd = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-statsd-exporter.service")
+        wait_for_open_port(9102)
+        succeed("curl http://localhost:9102/metrics | grep 'statsd_exporter_build_info{'")
+        wait_until_succeeds(
+          "echo 'test.udp:1|c' > /dev/udp/localhost/9125 && \
+          curl http://localhost:9102/metrics | grep 'test_udp 1'",
+          timeout=10
+        )
+        wait_until_succeeds(
+          "echo 'test.tcp:1|c' > /dev/tcp/localhost/9125 && \
+          curl http://localhost:9102/metrics | grep 'test_tcp 1'",
+          timeout=10
+        )
+      '';
+    };
+
     surfboard = {
       exporterConfig = {
         enable = true;
@@ -1220,15 +1299,13 @@ let
       '';
     };
 
-    unifi-poller = {
-      nodeName = "unifi_poller";
+    unpoller = {
+      nodeName = "unpoller";
       exporterConfig.enable = true;
       exporterConfig.controllers = [{ }];
       exporterTest = ''
-        wait_for_unit("prometheus-unifi-poller-exporter.service")
-        wait_for_open_port(9130)
-        succeed(
-            "curl -sSf localhost:9130/metrics | grep 'unifipoller_build_info{.\\+} 1'"
+        wait_until_succeeds(
+            'journalctl -eu prometheus-unpoller-exporter.service -o cat | grep "Connection Error"'
         )
       '';
     };
@@ -1256,6 +1333,67 @@ let
       '';
     };
 
+    v2ray = {
+      exporterConfig = {
+        enable = true;
+      };
+
+      metricProvider = {
+        systemd.services.prometheus-nginx-exporter.after = [ "v2ray.service" ];
+        services.v2ray = {
+          enable = true;
+          config = {
+            stats = {};
+            api = {
+              tag = "api";
+              services = [ "StatsService" ];
+            };
+            inbounds = [
+              {
+                port = 1080;
+                listen = "127.0.0.1";
+                protocol = "http";
+              }
+              {
+                listen = "127.0.0.1";
+                port = 54321;
+                protocol = "dokodemo-door";
+                settings = { address = "127.0.0.1"; };
+                tag = "api";
+              }
+            ];
+            outbounds = [
+              {
+                protocol = "freedom";
+              }
+              {
+                protocol = "freedom";
+                settings = {};
+                tag = "api";
+              }
+            ];
+            routing = {
+              strategy = "rules";
+              settings = {
+                rules = [
+                  {
+                    inboundTag = [ "api" ];
+                    outboundTag = "api";
+                    type = "field";
+                  }
+                ];
+              };
+            };
+          };
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-v2ray-exporter.service")
+        wait_for_open_port(9299)
+        succeed("curl -sSf localhost:9299/scrape | grep 'v2ray_up 1'")
+      '';
+    };
+
     varnish = {
       exporterConfig = {
         enable = true;
@@ -1285,7 +1423,10 @@ let
       '';
     };
 
-    wireguard = let snakeoil = import ./wireguard/snakeoil-keys.nix; in
+    wireguard = let
+      snakeoil = import ./wireguard/snakeoil-keys.nix;
+      publicKeyWithoutNewlines = replaceStrings [ "\n" ] [ "" ] snakeoil.peer1.publicKey;
+    in
       {
         exporterConfig.enable = true;
         metricProvider = {
@@ -1307,10 +1448,26 @@ let
           wait_for_unit("prometheus-wireguard-exporter.service")
           wait_for_open_port(9586)
           wait_until_succeeds(
-              "curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'"
+              "curl -sSf http://localhost:9586/metrics | grep '${publicKeyWithoutNewlines}'"
           )
         '';
       };
+
+    zfs = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "7327ded7";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-zfs-exporter.service")
+        wait_for_unit("zfs.target")
+        wait_for_open_port(9134)
+        wait_until_succeeds("curl -f localhost:9134/metrics | grep 'zfs_scrape_collector_success{.*} 1'")
+      '';
+    };
   };
 in
 mapAttrs
diff --git a/nixpkgs/nixos/tests/promscale.nix b/nixpkgs/nixos/tests/promscale.nix
new file mode 100644
index 000000000000..d4825b6d7f55
--- /dev/null
+++ b/nixpkgs/nixos/tests/promscale.nix
@@ -0,0 +1,60 @@
+# mostly copied from ./timescaledb.nix which was copied from ./postgresql.nix
+# as it seemed unapproriate to test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE USER promscale SUPERUSER PASSWORD 'promscale';
+    CREATE DATABASE promscale OWNER promscale;
+  '';
+
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ anpin ];
+    };
+
+    nodes.machine = { config, pkgs, ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = with postgresql-package.pkgs; [
+            timescaledb
+            promscale_extension
+          ];
+          settings = { shared_preload_libraries = "timescaledb, promscale"; };
+        };
+        environment.systemPackages = with pkgs; [ promscale ];
+      };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("postgresql")
+      with subtest("Postgresql with extensions timescaledb and promscale is available just after unit start"):
+          print(machine.succeed("sudo -u postgres psql -f ${test-sql}"))
+          machine.succeed("sudo -u postgres psql promscale -c 'SHOW shared_preload_libraries;' | grep promscale")
+          machine.succeed(
+            "promscale --db.name promscale --db.password promscale --db.user promscale --db.ssl-mode allow --startup.install-extensions --startup.only"
+          )
+      machine.succeed("sudo -u postgres psql promscale -c 'SELECT ps_trace.get_trace_retention_period();' | grep '(1 row)'")
+      machine.shutdown()
+    '';
+  };
+  #version 15 is not supported yet
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12" && !(versionAtLeast value.version "15")) postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions
diff --git a/nixpkgs/nixos/tests/prowlarr.nix b/nixpkgs/nixos/tests/prowlarr.nix
index 144cbd5fc95d..af669afd5700 100644
--- a/nixpkgs/nixos/tests/prowlarr.nix
+++ b/nixpkgs/nixos/tests/prowlarr.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "prowlarr";
-  meta.maintainers = with maintainers; [ jdreaver ];
+  meta.maintainers = with lib.maintainers; [ jdreaver ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/public-inbox.nix b/nixpkgs/nixos/tests/public-inbox.nix
index 7de40400fcbf..784ef9e3dc28 100644
--- a/nixpkgs/nixos/tests/public-inbox.nix
+++ b/nixpkgs/nixos/tests/public-inbox.nix
@@ -14,7 +14,7 @@ in
 
   meta.maintainers = with pkgs.lib.maintainers; [ julm ];
 
-  machine = { config, pkgs, nodes, ... }: let
+  nodes.machine = { config, pkgs, nodes, ... }: let
     inherit (config.services) gitolite public-inbox;
     # Git repositories paths in Gitolite.
     # Only their baseNameOf is used for configuring public-inbox.
@@ -201,7 +201,7 @@ in
 
       This is a testing mail.
     ''}")
-    machine.sleep(5)
+    machine.sleep(10)
     machine.succeed("curl -L 'https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u' | grep 'This is a testing mail.'")
 
     # Read a mail through public-inbox-imapd
@@ -221,7 +221,7 @@ in
     # Delete a mail.
     # Note that the use of an extension not listed in the addresses
     # require to use --all
-    machine.succeed("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all")
-    machine.fail("curl -L https://machine.example.localdomain/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'")
+    machine.succeed("curl -L https://machine.${domain}/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all")
+    machine.fail("curl -L https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'")
   '';
 })
diff --git a/nixpkgs/nixos/tests/pufferpanel.nix b/nixpkgs/nixos/tests/pufferpanel.nix
new file mode 100644
index 000000000000..e7b09c13f90b
--- /dev/null
+++ b/nixpkgs/nixos/tests/pufferpanel.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "pufferpanel";
+  meta.maintainers = [ lib.maintainers.tie ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.pufferpanel ];
+    services.pufferpanel = {
+      enable = true;
+      extraPackages = [ pkgs.netcat ];
+      environment = {
+        PUFFER_PANEL_REGISTRATIONENABLED = "false";
+        PUFFER_PANEL_SETTINGS_COMPANYNAME = "NixOS";
+      };
+    };
+  };
+
+  testScript = ''
+    import shlex
+    import json
+
+    curl = "curl --fail-with-body --silent"
+    baseURL = "http://localhost:8080"
+    adminName = "admin"
+    adminEmail = "admin@nixos.org"
+    adminPass = "admin"
+    adminCreds = json.dumps({
+      "email": adminEmail,
+      "password": adminPass,
+    })
+    stopCode = 9 # SIGKILL
+    serverPort = 1337
+    serverDefinition = json.dumps({
+      "name": "netcat",
+      "node": 0,
+      "users": [
+        adminName,
+      ],
+      "type": "netcat",
+      "run": {
+        "stopCode": stopCode,
+        "command": f"nc -l {serverPort}",
+      },
+      "environment": {
+        "type": "standard",
+      },
+    })
+
+    start_all()
+
+    machine.wait_for_unit("pufferpanel.service")
+    machine.wait_for_open_port(5657) # SFTP
+    machine.wait_for_open_port(8080) # HTTP
+
+    # Note that PufferPanel does not initialize database unless necessary.
+    # /api/config endpoint creates database file and triggers migrations.
+    # On success, we run a command to create administrator user that we use to
+    # interact with HTTP API.
+    resp = json.loads(machine.succeed(f"{curl} {baseURL}/api/config"))
+    assert resp["branding"]["name"] == "NixOS", "Invalid company name in configuration"
+    assert resp["registrationEnabled"] == False, "Expected registration to be disabled"
+
+    machine.succeed(f"pufferpanel --workDir /var/lib/pufferpanel user add --admin --name {adminName} --email {adminEmail} --password {adminPass}")
+
+    resp = json.loads(machine.succeed(f"{curl} -d '{adminCreds}' {baseURL}/auth/login"))
+    assert "servers.admin" in resp["scopes"], "User is not administrator"
+    token = resp["session"]
+    authHeader = shlex.quote(f"Authorization: Bearer {token}")
+
+    resp = json.loads(machine.succeed(f"{curl} -H {authHeader} -H 'Content-Type: application/json' -d '{serverDefinition}' {baseURL}/api/servers"))
+    serverID = resp["id"]
+    machine.succeed(f"{curl} -X POST -H {authHeader} {baseURL}/proxy/daemon/server/{serverID}/start")
+    machine.wait_for_open_port(serverPort)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pulseaudio.nix b/nixpkgs/nixos/tests/pulseaudio.nix
index cfdc61bc6c2b..dc8e33ccd559 100644
--- a/nixpkgs/nixos/tests/pulseaudio.nix
+++ b/nixpkgs/nixos/tests/pulseaudio.nix
@@ -1,10 +1,10 @@
 let
-  mkTest = { systemWide ? false }:
+  mkTest = { systemWide ? false , fullVersion ? 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";
+            "https://file-examples.com/storage/fe5947fd2362fc197a3c2df/2017/11/file_example_MP3_700KB.mp3";
           hash = "sha256-+iggJW8s0/LfA/okfXsB550/55Q0Sq3OoIzuBrzOPJQ=";
         };
 
@@ -22,7 +22,7 @@ let
           testPlay32 = { inherit (pkgs.pkgsi686Linux) sox alsa-utils; };
         };
       in {
-        name = "pulseaudio${lib.optionalString systemWide "-systemWide"}";
+        name = "pulseaudio${lib.optionalString fullVersion "Full"}${lib.optionalString systemWide "-systemWide"}";
         meta = with pkgs.lib.maintainers; {
           maintainers = [ synthetica ] ++ pkgs.pulseaudio.meta.maintainers;
         };
@@ -35,12 +35,14 @@ let
               enable = true;
               support32Bit = true;
               inherit systemWide;
+            } // lib.optionalAttrs fullVersion {
+              package = pkgs.pulseaudioFull;
             };
 
             environment.systemPackages = [ testers.testPlay pkgs.pavucontrol ]
               ++ lib.optional pkgs.stdenv.isx86_64 testers.testPlay32;
           } // lib.optionalAttrs systemWide {
-            users.users.alice.extraGroups = [ "audio" ];
+            users.users.alice.extraGroups = [ "pulse-access" ];
             systemd.services.pulseaudio.wantedBy = [ "multi-user.target" ];
           };
 
@@ -58,14 +60,21 @@ let
           ''}
           machine.screenshot("testPlay")
 
+          ${lib.optionalString (!systemWide) ''
+            machine.send_chars("pacmd info && touch /tmp/pacmd_success\n")
+            machine.wait_for_file("/tmp/pacmd_success")
+          ''}
+
           # Pavucontrol only loads when Pulseaudio is running. If it isn't, the
-          # text "Playback" (one of the tabs) will never show.
+          # text "Dummy Output" (sound device name) will never show.
           machine.send_chars("pavucontrol\n")
-          machine.wait_for_text("Playback")
+          machine.wait_for_text("Dummy Output")
           machine.screenshot("Pavucontrol")
         '';
       });
 in builtins.mapAttrs (key: val: mkTest val) {
-  user = { systemWide = false; };
-  system = { systemWide = true; };
+  user = { systemWide = false; fullVersion = false; };
+  system = { systemWide = true; fullVersion = false; };
+  userFull = { systemWide = false; fullVersion = true; };
+  systemFull = { systemWide = true; fullVersion = true; };
 }
diff --git a/nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix b/nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix
new file mode 100644
index 000000000000..49a105ef1076
--- /dev/null
+++ b/nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({
+  name = "qemu-vm-restrictnetwork";
+
+  nodes = {
+    unrestricted = { config, pkgs, ... }: {
+      virtualisation.restrictNetwork = false;
+    };
+
+    restricted = { config, pkgs, ... }: {
+      virtualisation.restrictNetwork = true;
+    };
+  };
+
+  testScript = ''
+    import os
+
+    if os.fork() == 0:
+      # Start some HTTP server on the qemu host to test guest isolation.
+      from http.server import HTTPServer, BaseHTTPRequestHandler
+      HTTPServer(("", 8000), BaseHTTPRequestHandler).serve_forever()
+
+    else:
+      start_all()
+      unrestricted.wait_for_unit("network-online.target")
+      restricted.wait_for_unit("network-online.target")
+
+      # Guests should be able to reach each other on the same VLAN.
+      unrestricted.succeed("ping -c1 restricted")
+      restricted.succeed("ping -c1 unrestricted")
+
+      # Only the unrestricted guest should be able to reach host services.
+      # 10.0.2.2 is the gateway mapping to the host's loopback interface.
+      unrestricted.succeed("curl -s http://10.0.2.2:8000")
+      restricted.fail("curl -s http://10.0.2.2:8000")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/quake3.nix b/nixpkgs/nixos/tests/quake3.nix
new file mode 100644
index 000000000000..2d8c5207001c
--- /dev/null
+++ b/nixpkgs/nixos/tests/quake3.nix
@@ -0,0 +1,95 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+
+  # Build Quake with coverage instrumentation.
+  overrides = pkgs:
+    {
+      quake3game = pkgs.quake3game.override (args: {
+        stdenv = pkgs.stdenvAdapters.addCoverageInstrumentation args.stdenv;
+      });
+    };
+
+  # Only allow the demo data to be used (only if it's unfreeRedistributable).
+  unfreePredicate = pkg: let
+    allowPackageNames = [ "quake3-demodata" "quake3-pointrelease" ];
+    allowLicenses = [ lib.licenses.unfreeRedistributable ];
+  in lib.elem pkg.pname allowPackageNames &&
+     lib.elem (pkg.meta.license or null) allowLicenses;
+
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      hardware.opengl.driSupport = true;
+      environment.systemPackages = [ pkgs.quake3demo ];
+      nixpkgs.config.packageOverrides = overrides;
+      nixpkgs.config.allowUnfreePredicate = unfreePredicate;
+    };
+
+in
+
+rec {
+  name = "quake3";
+  meta = with lib.maintainers; {
+    maintainers = [ domenkozar eelco ];
+  };
+
+  # TODO: lcov doesn't work atm
+  #makeCoverageReport = true;
+
+  nodes =
+    { server =
+        { pkgs, ... }:
+
+        { systemd.services.quake3-server =
+            { wantedBy = [ "multi-user.target" ];
+              script =
+                "${pkgs.quake3demo}/bin/quake3-server +set g_gametype 0 " +
+                "+map q3dm7 +addbot grunt +addbot daemia 2> /tmp/log";
+            };
+          nixpkgs.config.packageOverrides = overrides;
+          nixpkgs.config.allowUnfreePredicate = unfreePredicate;
+          networking.firewall.allowedUDPPorts = [ 27960 ];
+        };
+
+      client1 = client;
+      client2 = client;
+    };
+
+  testScript =
+    ''
+      start_all()
+
+      server.wait_for_unit("quake3-server")
+      client1.wait_for_x()
+      client2.wait_for_x()
+
+      client1.execute("quake3 +set r_fullscreen 0 +set name Foo +connect server &")
+      client2.execute("quake3 +set r_fullscreen 0 +set name Bar +connect server &")
+
+      server.wait_until_succeeds("grep -q 'Foo.*entered the game' /tmp/log")
+      server.wait_until_succeeds("grep -q 'Bar.*entered the game' /tmp/log")
+
+      server.sleep(10)  # wait for a while to get a nice screenshot
+
+      client1.block()
+
+      server.sleep(20)
+
+      client1.screenshot("screen1")
+      client2.screenshot("screen2")
+
+      client1.unblock()
+
+      server.sleep(10)
+
+      client1.screenshot("screen3")
+      client2.screenshot("screen4")
+
+      client1.shutdown()
+      client2.shutdown()
+      server.stop_job("quake3-server")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/rabbitmq.nix b/nixpkgs/nixos/tests/rabbitmq.nix
index f8e8e61c47d2..040679e68d98 100644
--- a/nixpkgs/nixos/tests/rabbitmq.nix
+++ b/nixpkgs/nixos/tests/rabbitmq.nix
@@ -1,6 +1,12 @@
 # This test runs rabbitmq and checks if rabbitmq is up and running.
 
-import ./make-test-python.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # in real life, you would keep this out of your repo and deploy it to a safe
+  # location using safe means.
+  configKeyPath = pkgs.writeText "fake-config-key" "hOjWzSEn2Z7cHzKOcf6i183O2NdjurSuoMDIIv01";
+in
+{
   name = "rabbitmq";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ eelco offline ];
@@ -10,6 +16,29 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     services.rabbitmq = {
       enable = true;
       managementPlugin.enable = true;
+
+      # To encrypt:
+      # rabbitmqctl --quiet encode --cipher blowfish_cfb64 --hash sha256 \
+      #   --iterations 10000 '<<"dJT8isYu6t0Xb6u56rPglSj1vK51SlNVlXfwsRxw">>' \
+      #   "hOjWzSEn2Z7cHzKOcf6i183O2NdjurSuoMDIIv01" ;
+      config = ''
+        [ { rabbit
+          , [ {default_user, <<"alice">>}
+            , { default_pass
+              , {encrypted,<<"oKKxyTze9PYmsEfl6FG1MxIUhxY7WPQL7HBoMPRC/1ZOdOZbtr9+DxjWW3e1D5SL48n3D9QOsGD0cOgYG7Qdvb7Txrepw8w=">>}
+              }
+            , {config_entry_decoder
+              , [ {passphrase, {file, <<"${configKeyPath}">>}}
+                , {cipher, blowfish_cfb64}
+                , {hash, sha256}
+                , {iterations, 10000}
+                ]
+              }
+            % , {rabbitmq_management, [{path_prefix, "/_queues"}]}
+            ]
+          }
+        ].
+      '';
     };
     # Ensure there is sufficient extra disk space for rabbitmq to be happy
     virtualisation.diskSize = 1024;
@@ -23,5 +52,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         'su -s ${pkgs.runtimeShell} rabbitmq -c "rabbitmqctl status"'
     )
     machine.wait_for_open_port(15672)
+
+    # The password is the plaintext that was encrypted with rabbitmqctl encode above.
+    machine.wait_until_succeeds(
+        '${pkgs.rabbitmq-java-client}/bin/PerfTest --time 10 --uri amqp://alice:dJT8isYu6t0Xb6u56rPglSj1vK51SlNVlXfwsRxw@localhost'
+    )
   '';
 })
diff --git a/nixpkgs/nixos/tests/radarr.nix b/nixpkgs/nixos/tests/radarr.nix
index 85fd6572f061..bf9eb11c2b12 100644
--- a/nixpkgs/nixos/tests/radarr.nix
+++ b/nixpkgs/nixos/tests/radarr.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "radarr";
-  meta.maintainers = with maintainers; [ etu ];
+  meta.maintainers = with lib.maintainers; [ etu ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/readarr.nix b/nixpkgs/nixos/tests/readarr.nix
new file mode 100644
index 000000000000..7c144e2ee02f
--- /dev/null
+++ b/nixpkgs/nixos/tests/readarr.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "readarr";
+  meta.maintainers = with lib.maintainers; [ jocelynthode ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.readarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("readarr.service")
+    machine.wait_for_open_port(8787)
+    machine.succeed("curl --fail http://localhost:8787/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/redis.nix b/nixpkgs/nixos/tests/redis.nix
index abea1657f3ea..94b50d07be6d 100644
--- a/nixpkgs/nixos/tests/redis.nix
+++ b/nixpkgs/nixos/tests/redis.nix
@@ -1,19 +1,17 @@
-import ./make-test-python.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, lib, ... }:
 {
   name = "redis";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ flokli ];
-  };
+  meta.maintainers = with lib.maintainers; [ flokli ];
 
   nodes = {
     machine =
-      { pkgs, lib, ... }: with lib;
+      { pkgs, lib, ... }:
 
       {
         services.redis.servers."".enable = true;
         services.redis.servers."test".enable = true;
 
-        users.users = listToAttrs (map (suffix: nameValuePair "member${suffix}" {
+        users.users = lib.listToAttrs (map (suffix: lib.nameValuePair "member${suffix}" {
           createHome = false;
           description = "A member of the redis${suffix} group";
           isNormalUser = true;
diff --git a/nixpkgs/nixos/tests/resolv.nix b/nixpkgs/nixos/tests/resolv.nix
deleted file mode 100644
index f0aa7e42aaf3..000000000000
--- a/nixpkgs/nixos/tests/resolv.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-# Test whether DNS resolving returns multiple records and all address families.
-import ./make-test-python.nix ({ pkgs, ... } : {
-  name = "resolv";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ckauhaus ];
-  };
-
-  nodes.resolv = { ... }: {
-    networking.extraHosts = ''
-      # IPv4 only
-      192.0.2.1 host-ipv4.example.net
-      192.0.2.2 host-ipv4.example.net
-      # IP6 only
-      2001:db8::2:1 host-ipv6.example.net
-      2001:db8::2:2 host-ipv6.example.net
-      # dual stack
-      192.0.2.1 host-dual.example.net
-      192.0.2.2 host-dual.example.net
-      2001:db8::2:1 host-dual.example.net
-      2001:db8::2:2 host-dual.example.net
-    '';
-  };
-
-  testScript = ''
-    def addrs_in(hostname, addrs):
-        res = resolv.succeed("getent ahosts {}".format(hostname))
-        for addr in addrs:
-            assert addr in res, "Expected output '{}' not found in\n{}".format(addr, res)
-
-
-    start_all()
-    resolv.wait_for_unit("nscd")
-
-    ipv4 = ["192.0.2.1", "192.0.2.2"]
-    ipv6 = ["2001:db8::2:1", "2001:db8::2:2"]
-
-    with subtest("IPv4 resolves"):
-        addrs_in("host-ipv4.example.net", ipv4)
-
-    with subtest("IPv6 resolves"):
-        addrs_in("host-ipv6.example.net", ipv6)
-
-    with subtest("Dual stack resolves"):
-        addrs_in("host-dual.example.net", ipv4 + ipv6)
-  '';
-})
diff --git a/nixpkgs/nixos/tests/restic.nix b/nixpkgs/nixos/tests/restic.nix
index 75fffe9d9a84..626049e73341 100644
--- a/nixpkgs/nixos/tests/restic.nix
+++ b/nixpkgs/nixos/tests/restic.nix
@@ -2,24 +2,32 @@ import ./make-test-python.nix (
   { pkgs, ... }:
 
   let
-    password = "some_password";
-    repository = "/tmp/restic-backup";
-    repositoryFile = "${pkgs.writeText "repositoryFile" "/tmp/restic-backup-from-file"}";
-    rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
+    remoteRepository = "/root/restic-backup";
+    remoteFromFileRepository = "/root/restic-backup-from-file";
+    rcloneRepository = "rclone:local:/root/restic-rclone-backup";
 
     backupPrepareCommand = ''
-      touch /opt/backupPrepareCommand
-      test ! -e /opt/backupCleanupCommand
+      touch /root/backupPrepareCommand
+      test ! -e /root/backupCleanupCommand
     '';
 
     backupCleanupCommand = ''
-      rm /opt/backupPrepareCommand
-      touch /opt/backupCleanupCommand
+      rm /root/backupPrepareCommand
+      touch /root/backupCleanupCommand
     '';
 
+    testDir = pkgs.stdenvNoCC.mkDerivation {
+      name = "test-files-to-backup";
+      unpackPhase = "true";
+      installPhase = ''
+        mkdir $out
+        touch $out/some_file
+      '';
+    };
+
     passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
-    initialize = true;
     paths = [ "/opt" ];
+    exclude = [ "/opt/excluded_file_*" ];
     pruneOpts = [
       "--keep-daily 2"
       "--keep-weekly 1"
@@ -40,12 +48,18 @@ import ./make-test-python.nix (
         {
           services.restic.backups = {
             remotebackup = {
-              inherit repository passwordFile initialize paths pruneOpts backupPrepareCommand backupCleanupCommand;
+              inherit passwordFile paths exclude pruneOpts backupPrepareCommand backupCleanupCommand;
+              repository = remoteRepository;
+              initialize = true;
             };
-            remotebackup-from-file = {
-              inherit repositoryFile passwordFile initialize paths pruneOpts;
+            remote-from-file-backup = {
+              inherit passwordFile paths exclude pruneOpts;
+              initialize = true;
+              repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository;
             };
             rclonebackup = {
+              inherit passwordFile paths exclude pruneOpts;
+              initialize = true;
               repository = rcloneRepository;
               rcloneConfig = {
                 type = "local";
@@ -57,17 +71,21 @@ import ./make-test-python.nix (
                 [local]
                 type=ftp
               '';
-              inherit passwordFile initialize paths pruneOpts;
             };
             remoteprune = {
-              inherit repository passwordFile;
+              inherit passwordFile;
+              repository = remoteRepository;
               pruneOpts = [ "--keep-last 1" ];
             };
             custompackage = {
-              inherit repository passwordFile paths;
+              inherit passwordFile paths;
+              repository = "some-fake-repository";
               package = pkgs.writeShellScriptBin "restic" ''
-                echo "$@" >> /tmp/fake-restic.log;
+                echo "$@" >> /root/fake-restic.log;
               '';
+
+              pruneOpts = [ "--keep-last 1" ];
+              checkOpts = [ "--some-check-option" ];
             };
           };
 
@@ -79,49 +97,74 @@ import ./make-test-python.nix (
       server.start()
       server.wait_for_unit("dbus.socket")
       server.fail(
-          "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
-          '${pkgs.restic}/bin/restic --repository-file ${repositoryFile} -p ${passwordFile} snapshots"',
+          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots",
+          '${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots"',
           "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
-          "grep 'backup .* /opt' /tmp/fake-restic.log",
+          "grep 'backup.* /opt' /root/fake-restic.log",
       )
       server.succeed(
-          "mkdir -p /opt",
-          "touch /opt/some_file",
-          "mkdir -p /tmp/restic-rclone-backup",
+          # set up
+          "cp -rT ${testDir} /opt",
+          "touch /opt/excluded_file_1 /opt/excluded_file_2",
+          "mkdir -p /root/restic-rclone-backup",
+
+          # test that remotebackup runs custom commands and produces a snapshot
           "timedatectl set-time '2016-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /opt/backupCleanupCommand",
-          "systemctl start restic-backups-remotebackup-from-file.service",
+          "rm /root/backupCleanupCommand",
+          '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that restoring that snapshot produces the same directory
+          "mkdir /tmp/restore-1",
+          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-1",
+          "diff -ru ${testDir} /tmp/restore-1/opt",
+
+          # test that remote-from-file-backup produces a snapshot
+          "systemctl start restic-backups-remote-from-file-backup.service",
+          '${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that rclonebackup produces a snapshot
           "systemctl start restic-backups-rclonebackup.service",
-          '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-          '${pkgs.restic}/bin/restic --repository-file ${repositoryFile} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-          '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+          '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that custompackage runs both `restic backup` and `restic check` with reasonable commandlines
           "systemctl start restic-backups-custompackage.service",
-          "grep 'backup .* /opt' /tmp/fake-restic.log",
+          "grep 'backup.* /opt' /root/fake-restic.log",
+          "grep 'check.* --some-check-option' /root/fake-restic.log",
+
+          # test that we can create four snapshots in remotebackup and rclonebackup
           "timedatectl set-time '2017-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /opt/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
+
           "timedatectl set-time '2018-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /opt/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
+
           "timedatectl set-time '2018-12-14 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /opt/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
+
           "timedatectl set-time '2018-12-15 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /opt/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
+
           "timedatectl set-time '2018-12-16 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /opt/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
-          '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
-          '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
+
+          '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"',
+          '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"',
+
+          # test that remoteprune brings us back to 1 snapshot in remotebackup
           "systemctl start restic-backups-remoteprune.service",
-          '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+          '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
       )
     '';
   }
diff --git a/nixpkgs/nixos/tests/retroarch.nix b/nixpkgs/nixos/tests/retroarch.nix
index c506ed02da89..f4bf232ea725 100644
--- a/nixpkgs/nixos/tests/retroarch.nix
+++ b/nixpkgs/nixos/tests/retroarch.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
   {
     name = "retroarch";
-    meta = with pkgs.lib.maintainers; { maintainers = [ j0hax ]; };
+    meta = with pkgs.lib; { maintainers = teams.libretro.members ++ [ maintainers.j0hax ]; };
 
     nodes.machine = { ... }:
 
@@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
         services.xserver.enable = true;
         services.xserver.desktopManager.retroarch = {
           enable = true;
-          package = pkgs.retroarchFull;
+          package = pkgs.retroarchBare;
         };
         services.xserver.displayManager = {
           sddm.enable = true;
diff --git a/nixpkgs/nixos/tests/rshim.nix b/nixpkgs/nixos/tests/rshim.nix
new file mode 100644
index 000000000000..bb5cce028ae7
--- /dev/null
+++ b/nixpkgs/nixos/tests/rshim.nix
@@ -0,0 +1,25 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  basic = makeTest {
+    name = "rshim";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.rshim.enable = true;
+    };
+
+    testScript = { nodes, ... }: ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      print(machine.succeed("systemctl status rshim.service"))
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/sanoid.nix b/nixpkgs/nixos/tests/sanoid.nix
index 97833c37e6ef..411ebcead9f6 100644
--- a/nixpkgs/nixos/tests/sanoid.nix
+++ b/nixpkgs/nixos/tests/sanoid.nix
@@ -34,6 +34,7 @@ in {
           autosnap = true;
         };
         datasets."pool/sanoid".use_template = [ "test" ];
+        datasets."pool/compat".useTemplate = [ "test" ];
         extraArgs = [ "--verbose" ];
       };
 
@@ -51,6 +52,12 @@ in {
 
           # Test pool without parent (regression test for https://github.com/NixOS/nixpkgs/pull/180111)
           "pool".target = "root@target:pool/full-pool";
+
+          # Test backward compatible options (regression test for https://github.com/NixOS/nixpkgs/issues/181561)
+          "pool/compat" = {
+            target = "root@target:pool/compat";
+            extraArgs = [ "--no-sync-snap" ];
+          };
         };
       };
     };
@@ -70,6 +77,7 @@ in {
         "udevadm settle",
         "zpool create pool -R /mnt /dev/vdb1",
         "zfs create pool/sanoid",
+        "zfs create pool/compat",
         "zfs create pool/syncoid",
         "udevadm settle",
     )
@@ -94,6 +102,7 @@ in {
 
     # Take snapshot with sanoid
     source.succeed("touch /mnt/pool/sanoid/test.txt")
+    source.succeed("touch /mnt/pool/compat/test.txt")
     source.systemctl("start --wait sanoid.service")
 
     assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after snapshotting"
@@ -111,6 +120,9 @@ in {
     source.systemctl("start --wait syncoid-pool.service")
     target.succeed("[[ -d /mnt/pool/full-pool/syncoid ]]")
 
+    source.systemctl("start --wait syncoid-pool-compat.service")
+    target.succeed("cat /mnt/pool/compat/test.txt")
+
     assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after syncing snapshots"
     assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after syncing snapshots"
     assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after syncing snapshots"
diff --git a/nixpkgs/nixos/tests/schleuder.nix b/nixpkgs/nixos/tests/schleuder.nix
index a9e4cc325bc7..e57ef66bb8f9 100644
--- a/nixpkgs/nixos/tests/schleuder.nix
+++ b/nixpkgs/nixos/tests/schleuder.nix
@@ -82,9 +82,7 @@ import ./make-test-python.nix {
     # Since we don't have internet here, use dnsmasq to provide MX records from /etc/hosts
     services.dnsmasq = {
       enable = true;
-      extraConfig = ''
-        selfmx
-      '';
+      settings.selfmx = true;
     };
 
     networking.extraHosts = ''
diff --git a/nixpkgs/nixos/tests/seafile.nix b/nixpkgs/nixos/tests/seafile.nix
index 6eec8b1fbe55..78e735f4fed7 100644
--- a/nixpkgs/nixos/tests/seafile.nix
+++ b/nixpkgs/nixos/tests/seafile.nix
@@ -79,18 +79,14 @@ import ./make-test-python.nix ({ pkgs, ... }:
               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.wait_until_succeeds("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")
+          client1.wait_until_succeeds("seaf-cli status |grep synchronized >&2")
 
       with subtest("client2 sync"):
           client2.wait_for_unit("default.target")
@@ -110,9 +106,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
               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.wait_until_succeeds("seaf-cli status |grep synchronized >&2")
 
           client2.succeed("ls -la test01 >&2")
 
diff --git a/nixpkgs/nixos/tests/sftpgo.nix b/nixpkgs/nixos/tests/sftpgo.nix
new file mode 100644
index 000000000000..ca55b9c962a0
--- /dev/null
+++ b/nixpkgs/nixos/tests/sftpgo.nix
@@ -0,0 +1,384 @@
+# SFTPGo NixOS test
+#
+# This NixOS test sets up a basic test scenario for the SFTPGo module
+# and covers the following scenarios:
+# - uploading a file via sftp
+# - downloading the file over sftp
+# - assert that the ACLs are respected
+# - share a file between alice and bob (using sftp)
+# - assert that eve cannot acceess the shared folder between alice and bob.
+#
+# Additional test coverage for the remaining protocols (i.e. ftp, http and webdav)
+# would be a nice to have for the future.
+{ pkgs, lib, ...  }:
+
+with lib;
+
+let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  # Returns an attributeset of users who are not system users.
+  normalUsers = config:
+    filterAttrs (name: user: user.isNormalUser) config.users.users;
+
+  # Returns true if a user is a member of the given group
+  isMemberOf =
+    config:
+    # str
+    groupName:
+    # users.users attrset
+    user:
+      any (x: x == user.name) config.users.groups.${groupName}.members;
+
+  # Generates a valid SFTPGo user configuration for a given user
+  # Will be converted to JSON and loaded on application startup.
+  generateUserAttrSet =
+    config:
+    # attrset returned by config.users.users.<username>
+    user: {
+      # 0: user is disabled, login is not allowed
+      # 1: user is enabled
+      status = 1;
+
+      username = user.name;
+      password = ""; # disables password authentication
+      public_keys = user.openssh.authorizedKeys.keys;
+      email = "${user.name}@example.com";
+
+      # User home directory on the local filesystem
+      home_dir = "${config.services.sftpgo.dataDir}/users/${user.name}";
+
+      # Defines a mapping between virtual SFTPGo paths and filesystem paths outside the user home directory.
+      #
+      # Supported for local filesystem only. If one or more of the specified folders are not
+      # inside the dataprovider they will be automatically created.
+      # You have to create the folder on the filesystem yourself
+      virtual_folders =
+        optional (isMemberOf config sharedFolderName user) {
+          name = sharedFolderName;
+          mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}";
+          virtual_path = "/${sharedFolderName}";
+        };
+
+      # Defines the ACL on the virtual filesystem
+      permissions =
+        recursiveUpdate {
+          "/" = [ "list" ];     # read-only top level directory
+          "/private" = [ "*" ]; # private subdirectory, not shared with others
+        } (optionalAttrs (isMemberOf config "shared" user) {
+          "/shared" = [ "*" ];
+        });
+
+      filters = {
+        allowed_ip = [];
+        denied_ip = [];
+        web_client = [
+          "password-change-disabled"
+          "password-reset-disabled"
+          "api-key-auth-change-disabled"
+        ];
+      };
+
+      upload_bandwidth = 0; # unlimited
+      download_bandwidth = 0; # unlimited
+      expiration_date = 0; # means no expiration
+      max_sessions = 0;
+      quota_size = 0;
+      quota_files = 0;
+    };
+
+  # Generates a json file containing a static configuration
+  # of users and folders to import to SFTPGo.
+  loadDataJson = config: pkgs.writeText "users-and-folders.json" (builtins.toJSON {
+    users =
+      mapAttrsToList (name: user: generateUserAttrSet config user) (normalUsers config);
+
+    folders = [
+      {
+        name = sharedFolderName;
+        description = "shared folder";
+
+        # 0: local filesystem
+        # 1: AWS S3 compatible
+        # 2: Google Cloud Storage
+        filesystem.provider = 0;
+
+        # Mapped path on the local filesystem
+        mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}";
+
+        # All users in the matching group gain access
+        users = config.users.groups.${sharedFolderName}.members;
+      }
+    ];
+  });
+
+  # Generated Host Key for connecting to SFTPGo's sftp subsystem.
+  snakeOilHostKey = pkgs.writeText "sftpgo_ed25519_host_key" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBOtQu6U135yxtrvUqPoozUymkjoNNPVK6rqjS936RLtQAAAJAXOMoSFzjK
+    EgAAAAtzc2gtZWQyNTUxOQAAACBOtQu6U135yxtrvUqPoozUymkjoNNPVK6rqjS936RLtQ
+    AAAEAoRLEV1VD80mg314ObySpfrCcUqtWoOSS3EtMPPhx08U61C7pTXfnLG2u9So+ijNTK
+    aSOg009UrquqNL3fpEu1AAAADHNmdHBnb0BuaXhvcwE=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  adminUsername = "admin";
+  adminPassword = "secretadminpassword";
+  aliceUsername = "alice";
+  alicePassword = "secretalicepassword";
+  bobUsername = "bob";
+  bobPassword = "secretbobpassword";
+  eveUsername = "eve";
+  evePassword = "secretevepassword";
+  sharedFolderName = "shared";
+
+  # A file for testing uploading via SFTP
+  testFile = pkgs.writeText "test.txt" "hello world";
+  sharedFile = pkgs.writeText "shared.txt" "shared content";
+
+  # Define the for exposing SFTP
+  sftpPort = 2022;
+
+  # Define the for exposing HTTP
+  httpPort = 8080;
+in
+{
+  name = "sftpgo";
+
+  meta.maintainers = with maintainers; [ yayayayaka ];
+
+  nodes = {
+    server = { nodes, ... }: {
+      networking.firewall.allowedTCPPorts = [ sftpPort httpPort ];
+
+      # nodes.server.configure postgresql database
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "sftpgo" ];
+        ensureUsers = [{
+          name = "sftpgo";
+          ensurePermissions."DATABASE sftpgo" = "ALL PRIVILEGES";
+        }];
+      };
+
+      services.sftpgo = {
+        enable = true;
+
+        loadDataFile = (loadDataJson nodes.server);
+
+        settings = {
+          data_provider = {
+            driver = "postgresql";
+            name = "sftpgo";
+            username = "sftpgo";
+            host = "/run/postgresql";
+            port = 5432;
+
+            # Enables the possibility to create an initial admin user on first startup.
+            create_default_admin = true;
+          };
+
+          httpd.bindings = [
+            {
+              address = ""; # listen on all interfaces
+              port = httpPort;
+              enable_https = false;
+
+              enable_web_client = true;
+              enable_web_admin = true;
+            }
+          ];
+
+          # Enable sftpd
+          sftpd = {
+            bindings = [{
+              address = ""; # listen on all interfaces
+              port = sftpPort;
+            }];
+            host_keys = [ snakeOilHostKey ];
+            password_authentication = false;
+            keyboard_interactive_authentication = false;
+          };
+        };
+      };
+
+      systemd.services.sftpgo = {
+        after = [ "postgresql.service"];
+        environment = {
+          # Update existing users
+          SFTPGO_LOADDATA_MODE = "0";
+          SFTPGO_DEFAULT_ADMIN_USERNAME = adminUsername;
+
+          # This will end up in cleartext in the systemd service.
+          # Don't use this approach in production!
+          SFTPGO_DEFAULT_ADMIN_PASSWORD = adminPassword;
+        };
+      };
+
+      # Sets up the folder hierarchy on the local filesystem
+      systemd.tmpfiles.rules =
+        let
+          sftpgoUser = nodes.server.services.sftpgo.user;
+          sftpgoGroup = nodes.server.services.sftpgo.group;
+          statePath = nodes.server.services.sftpgo.dataDir;
+        in [
+          # Create state directory
+          "d ${statePath} 0750 ${sftpgoUser} ${sftpgoGroup} -"
+          "d ${statePath}/users 0750 ${sftpgoUser} ${sftpgoGroup} -"
+
+          # Created shared folder directories
+          "d ${statePath}/${sharedFolderName} 2770 ${sftpgoUser} ${sharedFolderName}   -"
+        ]
+        ++ mapAttrsToList (name: user:
+          # Create private user directories
+          ''
+            d ${statePath}/users/${user.name} 0700 ${sftpgoUser} ${sftpgoGroup} -
+            d ${statePath}/users/${user.name}/private 0700 ${sftpgoUser} ${sftpgoGroup} -
+          ''
+        ) (normalUsers nodes.server);
+
+      users.users =
+        let
+          commonAttrs = {
+            isNormalUser = true;
+            openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+          };
+        in {
+          # SFTPGo admin user
+          admin = commonAttrs // {
+            password = adminPassword;
+          };
+
+          # Alice and bob share folders with each other
+          alice = commonAttrs // {
+            password = alicePassword;
+            extraGroups = [ sharedFolderName ];
+          };
+
+          bob = commonAttrs // {
+            password = bobPassword;
+            extraGroups = [ sharedFolderName ];
+          };
+
+          # Eve has no shared folders
+          eve = commonAttrs // {
+            password = evePassword;
+          };
+        };
+
+      users.groups.${sharedFolderName} = {};
+
+      specialisation = {
+        # A specialisation for asserting that SFTPGo can bind to privileged ports.
+        privilegedPorts.configuration = { ... }: {
+          networking.firewall.allowedTCPPorts = [ 22 80 ];
+          services.sftpgo = {
+            settings = {
+              sftpd.bindings = mkForce [{
+                address = "";
+                port = 22;
+              }];
+
+              httpd.bindings = mkForce [{
+                address = "";
+                port = 80;
+              }];
+            };
+          };
+        };
+      };
+    };
+
+    client = { nodes, ... }: {
+      # Add the SFTPGo host key to the global known_hosts file
+      programs.ssh.knownHosts =
+        let
+          commonAttrs = {
+            publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE61C7pTXfnLG2u9So+ijNTKaSOg009UrquqNL3fpEu1";
+          };
+        in {
+          "server" = commonAttrs;
+          "[server]:2022" = commonAttrs;
+        };
+      };
+  };
+
+  testScript = { nodes, ... }: let
+    # A function to generate test cases for wheter
+    # a specified username is expected to access the shared folder.
+    accessSharedFoldersSubtest =
+      { # The username to run as
+        username
+        # Whether the tests are expected to succeed or not
+      , shouldSucceed ? true
+      }: ''
+        with subtest("Test whether ${username} can access shared folders"):
+            client.${if shouldSucceed then "succeed" else "fail"}("sftp -P ${toString sftpPort} -b ${
+              pkgs.writeText "${username}-ls-${sharedFolderName}" ''
+                ls ${sharedFolderName}
+              ''
+            } ${username}@server")
+      '';
+      statePath = nodes.server.services.sftpgo.dataDir;
+  in ''
+    start_all()
+
+    client.wait_for_unit("default.target")
+    server.wait_for_unit("sftpgo.service")
+
+    with subtest("web client"):
+        client.wait_until_succeeds("curl -sSf http://server:${toString httpPort}/web/client/login")
+
+        # Ensure sftpgo found the static folder
+        client.wait_until_succeeds("curl -o /dev/null -sSf http://server:${toString httpPort}/static/favicon.ico")
+
+    with subtest("Setup SSH keys"):
+        client.succeed("mkdir -m 700 /root/.ssh")
+        client.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa")
+        client.succeed("chmod 600 /root/.ssh/id_ecdsa")
+
+    with subtest("Copy a file over sftp"):
+        client.wait_until_succeeds("scp -P ${toString sftpPort} ${toString testFile} alice@server:/private/${testFile.name}")
+        server.succeed("test -s ${statePath}/users/alice/private/${testFile.name}")
+
+        # The configured ACL should prevent uploading files to the root directory
+        client.fail("scp -P ${toString sftpPort} ${toString testFile} alice@server:/")
+
+    with subtest("Attempting an interactive SSH sessions must fail"):
+        client.fail("ssh -p ${toString sftpPort} alice@server")
+
+    ${accessSharedFoldersSubtest {
+      username = "alice";
+      shouldSucceed = true;
+    }}
+
+    ${accessSharedFoldersSubtest {
+      username = "bob";
+      shouldSucceed = true;
+    }}
+
+    ${accessSharedFoldersSubtest {
+      username = "eve";
+      shouldSucceed = false;
+    }}
+
+    with subtest("Test sharing files"):
+        # Alice uploads a file to shared folder
+        client.succeed("scp -P ${toString sftpPort} ${toString sharedFile} alice@server:/${sharedFolderName}/${sharedFile.name}")
+        server.succeed("test -s ${statePath}/${sharedFolderName}/${sharedFile.name}")
+
+        # Bob downloads the file from shared folder
+        client.succeed("scp -P ${toString sftpPort} bob@server:/shared/${sharedFile.name} ${sharedFile.name}")
+        client.succeed("test -s ${sharedFile.name}")
+
+        # Eve should not get the file from shared folder
+        client.fail("scp -P ${toString sftpPort} eve@server:/shared/${sharedFile.name}")
+
+    server.succeed("/run/current-system/specialisation/privilegedPorts/bin/switch-to-configuration test")
+
+    client.wait_until_succeeds("sftp -P 22 -b ${pkgs.writeText "get-hello-world.txt" ''
+      get /private/${testFile.name}
+    ''} alice@server")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/sgtpuzzles.nix b/nixpkgs/nixos/tests/sgtpuzzles.nix
new file mode 100644
index 000000000000..b8d25d42d312
--- /dev/null
+++ b/nixpkgs/nixos/tests/sgtpuzzles.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+{
+  name = "sgtpuzzles";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = with pkgs; [
+      sgtpuzzles
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+  ''
+    start_all()
+    machine.wait_for_x()
+
+    machine.execute("mines >&2 &")
+
+    machine.wait_for_window("Mines")
+    machine.wait_for_text("Marked")
+    machine.screenshot("mines")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/shadow.nix b/nixpkgs/nixos/tests/shadow.nix
index 50a9f7124646..c9a04088e870 100644
--- a/nixpkgs/nixos/tests/shadow.nix
+++ b/nixpkgs/nixos/tests/shadow.nix
@@ -3,6 +3,9 @@ let
   password2 = "helloworld";
   password3 = "bazqux";
   password4 = "asdf123";
+  hashed_bcrypt = "$2b$05$8xIEflrk2RxQtcVXbGIxs.Vl0x7dF1/JSv3cyX6JJt0npzkTCWvxK"; # fnord
+  hashed_yeshash = "$y$j9T$d8Z4EAf8P1SvM/aDFbxMS0$VnTXMp/Hnc7QdCBEaLTq5ZFOAFo2/PM0/xEAFuOE88."; # fnord
+  hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow
 in import ./make-test-python.nix ({ pkgs, ... }: {
   name = "shadow";
   meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
@@ -27,6 +30,22 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         password = password4;
         shell = pkgs.bash;
       };
+      users.berta = {
+        isNormalUser = true;
+        hashedPassword = hashed_bcrypt;
+        shell = pkgs.bash;
+      };
+      users.yesim = {
+        isNormalUser = true;
+        hashedPassword = hashed_yeshash;
+        shell = pkgs.bash;
+      };
+      users.leo = {
+        isNormalUser = true;
+        initialHashedPassword = "!";
+        hashedPassword = hashed_sha512crypt; # should take precedence over initialHashedPassword
+        shell = pkgs.bash;
+      };
     };
   };
 
@@ -115,5 +134,39 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         shadow.wait_until_succeeds("pgrep login")
         shadow.send_chars("${password2}\n")
         shadow.wait_until_tty_matches("5", "login:")
+
+    with subtest("check alternate password hashes"):
+        shadow.send_key("alt-f6")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
+        for u in ["berta", "yesim"]:
+            shadow.wait_for_unit("getty@tty6.service")
+            shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
+            shadow.wait_until_tty_matches("6", "login: ")
+            shadow.send_chars(f"{u}\n")
+            shadow.wait_until_tty_matches("6", f"login: {u}")
+            shadow.wait_until_succeeds("pgrep login")
+            shadow.sleep(2)
+            shadow.send_chars("fnord\n")
+            shadow.send_chars(f"whoami > /tmp/{u}\n")
+            shadow.wait_for_file(f"/tmp/{u}")
+            print(shadow.succeed(f"cat /tmp/{u}"))
+            assert u in shadow.succeed(f"cat /tmp/{u}")
+            shadow.send_chars("logout\n")
+
+    with subtest("Ensure hashedPassword does not get overridden by initialHashedPassword"):
+        shadow.send_key("alt-f6")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
+        shadow.wait_for_unit("getty@tty6.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
+        shadow.wait_until_tty_matches("6", "login: ")
+        shadow.send_chars("leo\n")
+        shadow.wait_until_tty_matches("6", "login: leo")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("meow\n")
+        shadow.send_chars("whoami > /tmp/leo\n")
+        shadow.wait_for_file("/tmp/leo")
+        assert "leo" in shadow.succeed("cat /tmp/leo")
+        shadow.send_chars("logout\n")
   '';
 })
diff --git a/nixpkgs/nixos/tests/shadowsocks/common.nix b/nixpkgs/nixos/tests/shadowsocks/common.nix
index 8cbbc3f20684..82a63771b03a 100644
--- a/nixpkgs/nixos/tests/shadowsocks/common.nix
+++ b/nixpkgs/nixos/tests/shadowsocks/common.nix
@@ -69,6 +69,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: {
       start_all()
 
       server.wait_for_unit("shadowsocks-libev.service")
+      server.wait_for_unit("nginx.service")
       client.wait_for_unit("shadowsocks-client.service")
 
       client.fail(
diff --git a/nixpkgs/nixos/tests/signal-desktop.nix b/nixpkgs/nixos/tests/signal-desktop.nix
index 5e2b648c7cf5..f146804a958d 100644
--- a/nixpkgs/nixos/tests/signal-desktop.nix
+++ b/nixpkgs/nixos/tests/signal-desktop.nix
@@ -43,7 +43,7 @@ in {
     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
+    # are run sandboxed and therefore with no internet, we can not wait
     # for the message "Link your phone ...". Nor should we wait for
     # the "Failed to connect to server" message, because when manually
     # running this test it will be not sandboxed.
diff --git a/nixpkgs/nixos/tests/smokeping.nix b/nixpkgs/nixos/tests/smokeping.nix
index ccacf60cfe4b..04f813964291 100644
--- a/nixpkgs/nixos/tests/smokeping.nix
+++ b/nixpkgs/nixos/tests/smokeping.nix
@@ -28,6 +28,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     sm.wait_for_unit("thttpd")
     sm.wait_for_file("/var/lib/smokeping/data/Local/LocalMachine.rrd")
     sm.succeed("curl -s -f localhost:8081/smokeping.fcgi?target=Local")
+    # Check that there's a helpful page without explicit path as well.
+    sm.succeed("curl -s -f localhost:8081")
     sm.succeed("ls /var/lib/smokeping/cache/Local/LocalMachine_mini.png")
     sm.succeed("ls /var/lib/smokeping/cache/index.html")
   '';
diff --git a/nixpkgs/nixos/tests/soapui.nix b/nixpkgs/nixos/tests/soapui.nix
index e4ce3888fd43..3a2d11a16756 100644
--- a/nixpkgs/nixos/tests/soapui.nix
+++ b/nixpkgs/nixos/tests/soapui.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "soapui";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ asbachb ];
+    maintainers = [ ];
   };
 
   nodes.machine = { config, pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/solr.nix b/nixpkgs/nixos/tests/solr.nix
deleted file mode 100644
index 33afe9d788f7..000000000000
--- a/nixpkgs/nixos/tests/solr.nix
+++ /dev/null
@@ -1,56 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-{
-  name = "solr";
-  meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
-
-  nodes.machine =
-    { config, pkgs, ... }:
-    {
-      # Ensure the virtual machine has enough memory for Solr to avoid the following error:
-      #
-      #   OpenJDK 64-Bit Server VM warning:
-      #     INFO: os::commit_memory(0x00000000e8000000, 402653184, 0)
-      #     failed; error='Cannot allocate memory' (errno=12)
-      #
-      #   There is insufficient memory for the Java Runtime Environment to continue.
-      #   Native memory allocation (mmap) failed to map 402653184 bytes for committing reserved memory.
-      virtualisation.memorySize = 2000;
-
-      services.solr.enable = true;
-    };
-
-  testScript = ''
-    start_all()
-
-    machine.wait_for_unit("solr.service")
-    machine.wait_for_open_port(8983)
-    machine.succeed("curl --fail http://localhost:8983/solr/")
-
-    # adapted from pkgs.solr/examples/films/README.txt
-    machine.succeed("sudo -u solr solr create -c films")
-    assert '"status":0' in machine.succeed(
-        """
-      curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:application/json' --data-binary '{
-        "add-field" : {
-          "name":"name",
-          "type":"text_general",
-          "multiValued":false,
-          "stored":true
-        },
-        "add-field" : {
-          "name":"initial_release_date",
-          "type":"pdate",
-          "stored":true
-        }
-      }'
-    """
-    )
-    machine.succeed(
-        "sudo -u solr post -c films ${pkgs.solr}/example/films/films.json"
-    )
-    assert '"name":"Batman Begins"' in machine.succeed(
-        "curl http://localhost:8983/solr/films/query?q=name:batman"
-    )
-  '';
-})
diff --git a/nixpkgs/nixos/tests/sonarr.nix b/nixpkgs/nixos/tests/sonarr.nix
index bdfc8916cb7f..57e6b72db3a3 100644
--- a/nixpkgs/nixos/tests/sonarr.nix
+++ b/nixpkgs/nixos/tests/sonarr.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-with lib;
-
 {
   name = "sonarr";
-  meta.maintainers = with maintainers; [ etu ];
+  meta.maintainers = with lib.maintainers; [ etu ];
 
   nodes.machine =
     { pkgs, ... }:
diff --git a/nixpkgs/nixos/tests/sourcehut.nix b/nixpkgs/nixos/tests/sourcehut.nix
index d52fbddd20f3..87e6d82bdd8f 100644
--- a/nixpkgs/nixos/tests/sourcehut.nix
+++ b/nixpkgs/nixos/tests/sourcehut.nix
@@ -18,8 +18,10 @@ let
           # passwordless ssh server
           services.openssh = {
             enable = true;
-            permitRootLogin = "yes";
-            extraConfig = "PermitEmptyPasswords yes";
+            settings = {
+              PermitRootLogin = "yes";
+              PermitEmptyPasswords = true;
+            };
           };
 
           users = {
@@ -35,7 +37,7 @@ let
           };
 
           security.sudo.wheelNeedsPassword = false;
-          nix.trustedUsers = [ "root" "build" ];
+          nix.settings.trusted-users = [ "root" "build" ];
           documentation.nixos.enable = false;
 
           # builds.sr.ht-image-specific network settings
diff --git a/nixpkgs/nixos/tests/spark/default.nix b/nixpkgs/nixos/tests/spark/default.nix
index 025c5a5222e7..462f0d23a403 100644
--- a/nixpkgs/nixos/tests/spark/default.nix
+++ b/nixpkgs/nixos/tests/spark/default.nix
@@ -7,6 +7,7 @@ import ../make-test-python.nix ({...}: {
         enable = true;
         master = "master:7077";
       };
+      virtualisation.memorySize = 2048;
     };
     master = { config, pkgs, ... }: {
       services.spark.master = {
diff --git a/nixpkgs/nixos/tests/specialisation.nix b/nixpkgs/nixos/tests/specialisation.nix
deleted file mode 100644
index b8d4b8279f4d..000000000000
--- a/nixpkgs/nixos/tests/specialisation.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-import ./make-test-python.nix {
-  name = "specialisation";
-  nodes =  {
-    inheritconf = { pkgs, ... }: {
-      environment.systemPackages = [ pkgs.cowsay ];
-      specialisation.inheritconf.configuration = { pkgs, ... }: {
-        environment.systemPackages = [ pkgs.hello ];
-      };
-    };
-    noinheritconf = { pkgs, ... }: {
-      environment.systemPackages = [ pkgs.cowsay ];
-      specialisation.noinheritconf = {
-        inheritParentConfig = false;
-        configuration = { pkgs, ... }: {
-          environment.systemPackages = [ pkgs.hello ];
-        };
-      };
-    };
-  };
-  testScript = ''
-    inheritconf.wait_for_unit("default.target")
-    inheritconf.succeed("cowsay hey")
-    inheritconf.fail("hello")
-
-    with subtest("Nested clones do inherit from parent"):
-        inheritconf.succeed(
-            "/run/current-system/specialisation/inheritconf/bin/switch-to-configuration test"
-        )
-        inheritconf.succeed("cowsay hey")
-        inheritconf.succeed("hello")
-
-        noinheritconf.wait_for_unit("default.target")
-        noinheritconf.succeed("cowsay hey")
-        noinheritconf.fail("hello")
-
-    with subtest("Nested children do not inherit from parent"):
-        noinheritconf.succeed(
-            "/run/current-system/specialisation/noinheritconf/bin/switch-to-configuration test"
-        )
-        noinheritconf.fail("cowsay hey")
-        noinheritconf.succeed("hello")
-  '';
-}
diff --git a/nixpkgs/nixos/tests/sqlite3-to-mysql.nix b/nixpkgs/nixos/tests/sqlite3-to-mysql.nix
new file mode 100644
index 000000000000..029058187df3
--- /dev/null
+++ b/nixpkgs/nixos/tests/sqlite3-to-mysql.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+/*
+  This test suite replaces the typical pytestCheckHook function in
+  sqlite3-to-mysql due to the need of a running mysql instance.
+*/
+
+{
+  name = "sqlite3-to-mysql";
+  meta.maintainers = with lib.maintainers; [ gador ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [
+      sqlite3-to-mysql
+      # create one coherent python environment
+      (python3.withPackages
+        (ps: sqlite3-to-mysql.propagatedBuildInputs ++
+          [
+            python3Packages.pytest
+            python3Packages.pytest-mock
+            python3Packages.pytest-timeout
+            python3Packages.factory_boy
+            python3Packages.docker # only needed so import does not fail
+            sqlite3-to-mysql
+          ])
+      )
+    ];
+    services.mysql = {
+      package = pkgs.mariadb;
+      enable = true;
+      # from https://github.com/techouse/sqlite3-to-mysql/blob/master/tests/conftest.py
+      # and https://github.com/techouse/sqlite3-to-mysql/blob/master/.github/workflows/test.yml
+      initialScript = pkgs.writeText "mysql-init.sql" ''
+        create database test_db DEFAULT CHARACTER SET utf8mb4;
+        create user tester identified by 'testpass';
+        grant all on test_db.* to tester;
+        create user tester@localhost identified by 'testpass';
+        grant all on test_db.* to tester@localhost;
+      '';
+      settings = {
+        mysqld = {
+          character-set-server = "utf8mb4";
+          collation-server = "utf8mb4_unicode_ci";
+          log_warnings = 1;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("mysql")
+
+    machine.succeed(
+         "sqlite3mysql --version | grep ${pkgs.sqlite3-to-mysql.version}"
+    )
+
+    # invalid_database_name: assert '1045 (28000): Access denied' in "1044 (42000): Access denied [...]
+    # invalid_database_user: does not return non-zero exit for some reason
+    # test_version: has problems importing sqlite3_to_mysql and determining the version
+    machine.succeed(
+         "cd ${pkgs.sqlite3-to-mysql.src} \
+          && pytest -v --no-docker -k \"not test_invalid_database_name and not test_invalid_database_user and not test_version\""
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sssd-ldap.nix b/nixpkgs/nixos/tests/sssd-ldap.nix
index 27dce6ceb98c..60f3b1a415da 100644
--- a/nixpkgs/nixos/tests/sssd-ldap.nix
+++ b/nixpkgs/nixos/tests/sssd-ldap.nix
@@ -6,17 +6,33 @@ let
   ldapRootPassword = "foobar";
 
   testUser = "alice";
-in import ./make-test-python.nix ({pkgs, ...}: {
+  testPassword = "foobar";
+  testNewPassword = "barfoo";
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "sssd-ldap";
 
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ bbigras ];
+    maintainers = [ bbigras s1341 ];
   };
 
   nodes.machine = { pkgs, ... }: {
+    security.pam.services.systemd-user.makeHomeDir = true;
+    environment.etc."cert.pem".text = builtins.readFile ./common/acme/server/acme.test.cert.pem;
+    environment.etc."key.pem".text = builtins.readFile ./common/acme/server/acme.test.key.pem;
     services.openldap = {
       enable = true;
+      urlList = [ "ldap:///" "ldaps:///" ];
       settings = {
+        attrs = {
+          olcTLSCACertificateFile = "/etc/cert.pem";
+          olcTLSCertificateFile = "/etc/cert.pem";
+          olcTLSCertificateKeyFile = "/etc/key.pem";
+          olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL";
+          olcTLSCRLCheck = "none";
+          olcTLSVerifyClient = "never";
+          olcTLSProtocolMin = "3.1";
+        };
         children = {
           "cn=schema".includes = [
             "${pkgs.openldap}/etc/schema/core.ldif"
@@ -32,6 +48,23 @@ in import ./make-test-python.nix ({pkgs, ...}: {
               olcSuffix = dbSuffix;
               olcRootDN = "cn=${ldapRootUser},${dbSuffix}";
               olcRootPW = ldapRootPassword;
+              olcAccess = [
+                /*
+                  custom access rules for userPassword attributes
+                  */
+                ''
+                  {0}to attrs=userPassword
+                                    by self write
+                                    by anonymous auth
+                                    by * none''
+
+                /*
+                  allow read on anything else
+                  */
+                ''
+                  {1}to *
+                                    by * read''
+              ];
             };
           };
         };
@@ -55,7 +88,7 @@ in import ./make-test-python.nix ({pkgs, ...}: {
           dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix}
           objectClass: person
           objectClass: posixAccount
-          # userPassword: somePasswordHash
+          userPassword: ${testPassword}
           homeDirectory: /home/${testUser}
           uidNumber: 1234
           gidNumber: 1234
@@ -78,7 +111,9 @@ in import ./make-test-python.nix ({pkgs, ...}: {
         [domain/${dbDomain}]
         auth_provider = ldap
         id_provider = ldap
-        ldap_uri = ldap://127.0.0.1:389
+        ldap_uri = ldaps://127.0.0.1:636
+        ldap_tls_reqcert = allow
+        ldap_tls_cacert = /etc/cert.pem
         ldap_search_base = ${dbSuffix}
         ldap_default_bind_dn = cn=${ldapRootUser},${dbSuffix}
         ldap_default_authtok_type = password
@@ -91,6 +126,48 @@ in import ./make-test-python.nix ({pkgs, ...}: {
     machine.start()
     machine.wait_for_unit("openldap.service")
     machine.wait_for_unit("sssd.service")
-    machine.succeed("getent passwd ${testUser}")
+    result = machine.execute("getent passwd ${testUser}")
+    if result[0] == 0:
+      assert "${testUser}" in result[1]
+    else:
+      machine.wait_for_console_text("Backend is online")
+      machine.succeed("getent passwd ${testUser}")
+
+    with subtest("Log in as ${testUser}"):
+        machine.wait_until_tty_matches("1", "login: ")
+        machine.send_chars("${testUser}\n")
+        machine.wait_until_tty_matches("1", "login: ${testUser}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches("1", "Password: ")
+        machine.send_chars("${testPassword}\n")
+        machine.wait_until_succeeds("pgrep -u ${testUser} bash")
+        machine.send_chars("touch done\n")
+        machine.wait_for_file("/home/${testUser}/done")
+
+    with subtest("Change ${testUser}'s password"):
+        machine.send_chars("passwd\n")
+        machine.wait_until_tty_matches("1", "Current Password: ")
+        machine.send_chars("${testPassword}\n")
+        machine.wait_until_tty_matches("1", "New Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_tty_matches("1", "Reenter new Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_tty_matches("1", "passwd: password updated successfully")
+
+    with subtest("Log in as ${testUser} with new password in virtual console 2"):
+        machine.send_key("alt-f2")
+        machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+        machine.wait_for_unit("getty@tty2.service")
+        machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+        machine.wait_until_tty_matches("2", "login: ")
+        machine.send_chars("${testUser}\n")
+        machine.wait_until_tty_matches("2", "login: ${testUser}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches("2", "Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_succeeds("pgrep -u ${testUser} bash")
+        machine.send_chars("touch done2\n")
+        machine.wait_for_file("/home/${testUser}/done2")
   '';
 })
diff --git a/nixpkgs/nixos/tests/sssd.nix b/nixpkgs/nixos/tests/sssd.nix
index 25527cb59a59..c8d356e074ad 100644
--- a/nixpkgs/nixos/tests/sssd.nix
+++ b/nixpkgs/nixos/tests/sssd.nix
@@ -13,5 +13,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
       start_all()
       machine.wait_for_unit("multi-user.target")
       machine.wait_for_unit("sssd.service")
+      machine.succeed("sssctl config-check")
     '';
 })
diff --git a/nixpkgs/nixos/tests/stratis/default.nix b/nixpkgs/nixos/tests/stratis/default.nix
new file mode 100644
index 000000000000..42daadd5fcaa
--- /dev/null
+++ b/nixpkgs/nixos/tests/stratis/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+
+{
+  simple = import ./simple.nix { inherit system pkgs; };
+  encryption = import ./encryption.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/stratis/encryption.nix b/nixpkgs/nixos/tests/stratis/encryption.nix
new file mode 100644
index 000000000000..a555ff8a8e85
--- /dev/null
+++ b/nixpkgs/nixos/tests/stratis/encryption.nix
@@ -0,0 +1,32 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "stratis";
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nickcao ];
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      services.stratis.enable = true;
+      virtualisation.emptyDiskImages = [ 2048 ];
+    };
+
+    testScript =
+      let
+        testkey1 = pkgs.writeText "testkey1" "supersecret1";
+        testkey2 = pkgs.writeText "testkey2" "supersecret2";
+      in
+      ''
+        machine.wait_for_unit("stratisd")
+        # test creation of encrypted pool and filesystem
+        machine.succeed("stratis key  set    testkey1  --keyfile-path ${testkey1}")
+        machine.succeed("stratis key  set    testkey2  --keyfile-path ${testkey2}")
+        machine.succeed("stratis pool create testpool /dev/vdb --key-desc testkey1")
+        machine.succeed("stratis fs   create testpool testfs")
+        # test rebinding encrypted pool
+        machine.succeed("stratis pool rebind keyring  testpool testkey2")
+        # test restarting encrypted pool
+        machine.succeed("stratis pool stop   testpool")
+        machine.succeed("stratis pool start  --name testpool --unlock-method keyring")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/stratis/simple.nix b/nixpkgs/nixos/tests/stratis/simple.nix
new file mode 100644
index 000000000000..543789f59c05
--- /dev/null
+++ b/nixpkgs/nixos/tests/stratis/simple.nix
@@ -0,0 +1,39 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "stratis";
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nickcao ];
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      services.stratis.enable = true;
+      virtualisation.emptyDiskImages = [ 2048 1024 1024 1024 ];
+    };
+
+    testScript = ''
+      machine.wait_for_unit("stratisd")
+      # test pool creation
+      machine.succeed("stratis pool create     testpool /dev/vdb")
+      machine.succeed("stratis pool add-data   testpool /dev/vdc")
+      machine.succeed("stratis pool init-cache testpool /dev/vdd")
+      machine.succeed("stratis pool add-cache  testpool /dev/vde")
+      # test filesystem creation and rename
+      machine.succeed("stratis filesystem create testpool testfs0")
+      machine.succeed("stratis filesystem rename testpool testfs0 testfs1")
+      # test snapshot
+      machine.succeed("mkdir -p /mnt/testfs1 /mnt/testfs2")
+      machine.wait_for_file("/dev/stratis/testpool/testfs1")
+      machine.succeed("mount /dev/stratis/testpool/testfs1 /mnt/testfs1")
+      machine.succeed("echo test0 > /mnt/testfs1/test0")
+      machine.succeed("echo test1 > /mnt/testfs1/test1")
+      machine.succeed("stratis filesystem snapshot testpool testfs1 testfs2")
+      machine.succeed("echo test2 > /mnt/testfs1/test1")
+      machine.wait_for_file("/dev/stratis/testpool/testfs2")
+      machine.succeed("mount /dev/stratis/testpool/testfs2 /mnt/testfs2")
+      assert "test0" in machine.succeed("cat /mnt/testfs1/test0")
+      assert "test0" in machine.succeed("cat /mnt/testfs2/test0")
+      assert "test2" in machine.succeed("cat /mnt/testfs1/test1")
+      assert "test1" in machine.succeed("cat /mnt/testfs2/test1")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/sudo.nix b/nixpkgs/nixos/tests/sudo.nix
index 661fe9989e7a..3b6028c6f0b0 100644
--- a/nixpkgs/nixos/tests/sudo.nix
+++ b/nixpkgs/nixos/tests/sudo.nix
@@ -2,17 +2,13 @@
 
 let
   password = "helloworld";
-
 in
-  import ./make-test-python.nix ({ pkgs, ...} : {
+  import ./make-test-python.nix ({ lib, pkgs, ...} : {
     name = "sudo";
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ lschuermann ];
-    };
+    meta.maintainers = with lib.maintainers; [ lschuermann ];
 
     nodes.machine =
       { lib, ... }:
-      with lib;
       {
         users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
         users.users = {
diff --git a/nixpkgs/nixos/tests/swap-file-btrfs.nix b/nixpkgs/nixos/tests/swap-file-btrfs.nix
new file mode 100644
index 000000000000..35b9fb4fa50a
--- /dev/null
+++ b/nixpkgs/nixos/tests/swap-file-btrfs.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "swap-file-btrfs";
+
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.rootDevice = "/dev/vda";
+
+      boot.initrd.postDeviceCommands = ''
+        ${pkgs.btrfs-progs}/bin/mkfs.btrfs --label root /dev/vda
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "btrfs";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/var/swapfile";
+          size = 1; # 1MiB.
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit('var-swapfile.swap')
+    # Ensure the swap file creation script ran to completion without failing when creating the swap file
+    machine.fail("systemctl is-failed --quiet mkswap-var-swapfile.service")
+    machine.succeed("stat --file-system --format=%T /var/swapfile | grep btrfs")
+    # First run. Auto creation.
+    machine.succeed("swapon --show | grep /var/swapfile")
+
+    machine.shutdown()
+    machine.start()
+
+    # Second run. Use it as-is.
+    machine.wait_for_unit('var-swapfile.swap')
+    # Ensure the swap file creation script ran to completion without failing when the swap file already exists
+    machine.fail("systemctl is-failed --quiet mkswap-var-swapfile.service")
+    machine.succeed("swapon --show | grep /var/swapfile")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/swap-partition.nix b/nixpkgs/nixos/tests/swap-partition.nix
index 2279630b57b8..ddcaeb95453e 100644
--- a/nixpkgs/nixos/tests/swap-partition.nix
+++ b/nixpkgs/nixos/tests/swap-partition.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
     {
       virtualisation.useDefaultFilesystems = false;
 
-      virtualisation.bootDevice = "/dev/vda1";
+      virtualisation.rootDevice = "/dev/vda1";
 
       boot.initrd.postDeviceCommands = ''
         if ! test -b /dev/vda1; then
diff --git a/nixpkgs/nixos/tests/swap-random-encryption.nix b/nixpkgs/nixos/tests/swap-random-encryption.nix
new file mode 100644
index 000000000000..9e919db65dde
--- /dev/null
+++ b/nixpkgs/nixos/tests/swap-random-encryption.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "swap-random-encryption";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    {
+      environment.systemPackages = [ pkgs.cryptsetup ];
+
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.rootDevice = "/dev/vda1";
+
+      boot.initrd.postDeviceCommands = ''
+        if ! test -b /dev/vda1; then
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mklabel msdos
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary 1MiB -250MiB
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary -250MiB 100%
+          sync
+        fi
+
+        FSTYPE=$(blkid -o value -s TYPE /dev/vda1 || true)
+        if test -z "$FSTYPE"; then
+          ${pkgs.e2fsprogs}/bin/mke2fs -t ext4 -L root /dev/vda1
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "ext4";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/dev/vda2";
+
+          randomEncryption = {
+            enable = true;
+            cipher = "aes-xts-plain64";
+            keySize = 512;
+            sectorSize = 4096;
+          };
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Swap is active"):
+      # Doesn't matter if the numbers reported by `free` are slightly off due to unit conversions.
+      machine.succeed("free -h | grep -E 'Swap:\s+2[45][0-9]Mi'")
+
+    with subtest("Swap device has 4k sector size"):
+      import json
+      result = json.loads(machine.succeed("lsblk -Jo PHY-SEC,LOG-SEC /dev/mapper/dev-vda2"))
+      block_devices = result["blockdevices"]
+      if len(block_devices) != 1:
+        raise Exception ("lsblk output did not report exactly one block device")
+
+      swapDevice = block_devices[0];
+      if not (swapDevice["phy-sec"] == 4096 and swapDevice["log-sec"] == 4096):
+        raise Exception ("swap device does not have the sector size specified in the configuration")
+
+    with subtest("Swap encrypt has assigned cipher and keysize"):
+      import re
+
+      results = machine.succeed("cryptsetup status dev-vda2").splitlines()
+
+      cipher_pattern = re.compile(r"\s*cipher:\s+aes-xts-plain64\s*")
+      if not any(cipher_pattern.fullmatch(line) for line in results):
+        raise Exception ("swap device encryption does not use the cipher specified in the configuration")
+
+      key_size_pattern = re.compile(r"\s*keysize:\s+512\s+bits\s*")
+      if not any(key_size_pattern.fullmatch(line) for line in results):
+        raise Exception ("swap device encryption does not use the key size specified in the configuration")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/switch-test.nix b/nixpkgs/nixos/tests/switch-test.nix
index 0198866b6ff8..f891a2cb2f4c 100644
--- a/nixpkgs/nixos/tests/switch-test.nix
+++ b/nixpkgs/nixos/tests/switch-test.nix
@@ -214,6 +214,25 @@ in {
           systemd.services."escaped\\x2ddash".serviceConfig.X-Test = "test";
         };
 
+        unitStartingWithDash.configuration = {
+          systemd.services."-" = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        unitStartingWithDashModified.configuration = {
+          imports = [ unitStartingWithDash.configuration ];
+          systemd.services."-" = {
+            reloadIfChanged = true;
+            serviceConfig.ExecReload = "${pkgs.coreutils}/bin/true";
+          };
+        };
+
         unitWithRequirement.configuration = {
           systemd.services.required-service = {
             wantedBy = [ "multi-user.target" ];
@@ -637,9 +656,27 @@ in {
         assert_contains(out, "\nstarting the following units: escaped\\x2ddash.service\n")
         assert_lacks(out, "the following new units were started:")
 
+        # Ensure units can start with a dash
+        out = switch_to_specialisation("${machine}", "unitStartingWithDash")
+        assert_contains(out, "stopping the following units: escaped\\x2ddash.service\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: -.service\n")
+
+        # The regression only occurs when reloading units
+        out = switch_to_specialisation("${machine}", "unitStartingWithDashModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: -.service")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
         # Ensure units that require changed units are properly reloaded
         out = switch_to_specialisation("${machine}", "unitWithRequirement")
-        assert_contains(out, "stopping the following units: escaped\\x2ddash.service\n")
+        assert_contains(out, "stopping the following units: -.service\n")
         assert_lacks(out, "NOT restarting the following changed units:")
         assert_lacks(out, "reloading the following units:")
         assert_lacks(out, "\nrestarting the following units:")
diff --git a/nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch b/nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch
new file mode 100644
index 000000000000..ef547c02f918
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch
@@ -0,0 +1,25 @@
+From d87a7513c6f2f2824203032ef27caeb84892ed7e Mon Sep 17 00:00:00 2001
+From: Will Fancher <elvishjerricco@gmail.com>
+Date: Tue, 30 May 2023 16:53:20 -0400
+Subject: [PATCH] Intentionally break the fat driver
+
+---
+ FatPkg/EnhancedFatDxe/ReadWrite.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/FatPkg/EnhancedFatDxe/ReadWrite.c b/FatPkg/EnhancedFatDxe/ReadWrite.c
+index 8f525044d1f1..32c62ff7817b 100644
+--- a/FatPkg/EnhancedFatDxe/ReadWrite.c
++++ b/FatPkg/EnhancedFatDxe/ReadWrite.c
+@@ -216,6 +216,11 @@ FatIFileAccess (
+   Volume = OFile->Volume;

+   Task   = NULL;

+ 

++  if (*BufferSize > (10U * 1024U * 1024U)) {

++    IFile->Position += 10U * 1024U * 1024U;

++    return EFI_BAD_BUFFER_SIZE;

++  }

++

+   //

+   // Write to a directory is unsupported

+   //

diff --git a/nixpkgs/nixos/tests/systemd-boot.nix b/nixpkgs/nixos/tests/systemd-boot.nix
index 039e6bdd9d5a..814cdc5f1443 100644
--- a/nixpkgs/nixos/tests/systemd-boot.nix
+++ b/nixpkgs/nixos/tests/systemd-boot.nix
@@ -101,13 +101,13 @@ in
       # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c
       machine.succeed(
           """
-        find /boot -iname '*.efi' -print0 | \
+        find /boot -iname '*boot*.efi' -print0 | \
         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 (000.0-1-notnixos) to " in output
+      assert "updating systemd-boot from 000.0-1-notnixos to " in output
     '';
   };
 
@@ -207,11 +207,17 @@ in
     nodes = {
       inherit common;
 
-      machine = { pkgs, ... }: {
+      machine = { pkgs, nodes, ... }: {
         imports = [ common ];
         boot.loader.systemd-boot.extraFiles = {
           "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
         };
+
+        # These are configs for different nodes, but we'll use them here in `machine`
+        system.extraDependencies = [
+          nodes.common.system.build.toplevel
+          nodes.with_netbootxyz.system.build.toplevel
+        ];
       };
 
       with_netbootxyz = { pkgs, ... }: {
@@ -221,9 +227,9 @@ in
     };
 
     testScript = { nodes, ... }: let
-      originalSystem = nodes.machine.config.system.build.toplevel;
-      baseSystem = nodes.common.config.system.build.toplevel;
-      finalSystem = nodes.with_netbootxyz.config.system.build.toplevel;
+      originalSystem = nodes.machine.system.build.toplevel;
+      baseSystem = nodes.common.system.build.toplevel;
+      finalSystem = nodes.with_netbootxyz.system.build.toplevel;
     in ''
       machine.succeed("test -e /boot/efi/fruits/tomato.efi")
       machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
@@ -251,4 +257,29 @@ in
           machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
     '';
   };
+
+  # See: [Firmware file size bug] in systemd/default.nix
+  uefiLargeFileWorkaround = makeTest {
+    name = "uefi-large-file-workaround";
+
+    nodes.machine = { pkgs, ... }: {
+      imports = [common];
+      virtualisation.efi.OVMF = pkgs.OVMF.overrideAttrs (old: {
+        # This patch deliberately breaks the FAT driver in EDK2 to
+        # exhibit (part of) the firmware bug that we are testing
+        # for. Files greater than 10MiB will fail to be read in a
+        # single Read() call, so systemd-boot will fail to load the
+        # initrd without a workaround. The number 10MiB was chosen
+        # because if it were smaller than the kernel size, even the
+        # LoadImage call would fail, which is not the failure mode
+        # we're testing for. It needs to be between the kernel size
+        # and the initrd size.
+        patches = old.patches or [] ++ [ ./systemd-boot-ovmf-broken-fat-driver.patch ];
+      });
+    };
+
+    testScript = ''
+      machine.wait_for_unit("multi-user.target")
+    '';
+  };
 }
diff --git a/nixpkgs/nixos/tests/systemd-bpf.nix b/nixpkgs/nixos/tests/systemd-bpf.nix
new file mode 100644
index 000000000000..e11347a2a817
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-bpf.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-bpf";
+  meta = with lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+  nodes = {
+    node1 = {
+      virtualisation.vlans = [ 1 ];
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    node2 = {
+      virtualisation.vlans = [ 1 ];
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    node1.wait_for_unit("systemd-networkd-wait-online.service")
+    node2.wait_for_unit("systemd-networkd-wait-online.service")
+
+    with subtest("test RestrictNetworkInterfaces= works"):
+      node1.succeed("ping -c 5 192.168.1.2")
+      node1.succeed("systemd-run -t -p RestrictNetworkInterfaces='eth1' ping -c 5 192.168.1.2")
+      node1.fail("systemd-run -t -p RestrictNetworkInterfaces='lo' ping -c 5 192.168.1.2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-confinement.nix b/nixpkgs/nixos/tests/systemd-confinement.nix
index bde5b770ea50..428888d41a20 100644
--- a/nixpkgs/nixos/tests/systemd-confinement.nix
+++ b/nixpkgs/nixos/tests/systemd-confinement.nix
@@ -153,7 +153,7 @@ import ./make-test-python.nix {
 
     options.__testSteps = lib.mkOption {
       type = lib.types.lines;
-      description = "All of the test steps combined as a single script.";
+      description = lib.mdDoc "All of the test steps combined as a single script.";
     };
 
     config.environment.systemPackages = lib.singleton testClient;
diff --git a/nixpkgs/nixos/tests/systemd-credentials-tpm2.nix b/nixpkgs/nixos/tests/systemd-credentials-tpm2.nix
new file mode 100644
index 000000000000..d2dc1fd7b615
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-credentials-tpm2.nix
@@ -0,0 +1,124 @@
+import ./make-test-python.nix ({ lib, pkgs, system, ... }:
+
+let
+  tpmSocketPath = "/tmp/swtpm-sock";
+  tpmDeviceModels = {
+    x86_64-linux = "tpm-tis";
+    aarch64-linux = "tpm-tis-device";
+  };
+in
+
+{
+  name = "systemd-credentials-tpm2";
+
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ tmarkus ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation = {
+      qemu.options = [
+        "-chardev socket,id=chrtpm,path=${tpmSocketPath}"
+        "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
+        "-device ${tpmDeviceModels.${system}},tpmdev=tpm_dev_0"
+      ];
+    };
+
+    boot.initrd.availableKernelModules = [ "tpm_tis" ];
+
+    environment.systemPackages = with pkgs; [ diffutils ];
+  };
+
+  testScript = ''
+    import subprocess
+    from tempfile import TemporaryDirectory
+
+    # From systemd-initrd-luks-tpm2.nix
+    class Tpm:
+        def __init__(self):
+            self.state_dir = TemporaryDirectory()
+            self.start()
+
+        def start(self):
+            self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
+                "socket",
+                "--tpmstate", f"dir={self.state_dir.name}",
+                "--ctrl", "type=unixio,path=${tpmSocketPath}",
+                "--tpm2",
+                ])
+
+            # Check whether starting swtpm failed
+            try:
+                exit_code = self.proc.wait(timeout=0.2)
+                if exit_code is not None and exit_code != 0:
+                    raise Exception("failed to start swtpm")
+            except subprocess.TimeoutExpired:
+                pass
+
+        """Check whether the swtpm process exited due to an error"""
+        def check(self):
+            exit_code = self.proc.poll()
+            if exit_code is not None and exit_code != 0:
+                raise Exception("swtpm process died")
+
+    CRED_NAME = "testkey"
+    CRED_RAW_FILE = f"/root/{CRED_NAME}"
+    CRED_FILE = f"/root/{CRED_NAME}.cred"
+
+    def systemd_run(machine, cmd):
+        machine.log(f"Executing command (via systemd-run): \"{cmd}\"")
+
+        (status, out) = machine.execute( " ".join([
+            "systemd-run",
+            "--service-type=exec",
+            "--quiet",
+            "--wait",
+            "-E PATH=\"$PATH\"",
+            "-p StandardOutput=journal",
+            "-p StandardError=journal",
+            f"-p LoadCredentialEncrypted={CRED_NAME}:{CRED_FILE}",
+            f"$SHELL -c '{cmd}'"
+            ]) )
+
+        if status != 0:
+            raise Exception(f"systemd_run failed (status {status})")
+
+        machine.log("systemd-run finished successfully")
+
+    tpm = Tpm()
+
+    @polling_condition
+    def swtpm_running():
+        tpm.check()
+
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Check whether TPM device exists"):
+        machine.succeed("test -e /dev/tpm0")
+        machine.succeed("test -e /dev/tpmrm0")
+
+    with subtest("Check whether systemd-creds detects TPM2 correctly"):
+        cmd = "systemd-creds has-tpm2"
+        machine.log(f"Running \"{cmd}\"")
+        (status, _) = machine.execute(cmd)
+
+        # Check exit code equals 0 or 1 (1 means firmware support is missing, which is OK here)
+        if status != 0 and status != 1:
+            raise Exception("systemd-creds failed to detect TPM2")
+
+    with subtest("Encrypt credential using systemd-creds"):
+        machine.succeed(f"dd if=/dev/urandom of={CRED_RAW_FILE} bs=1k count=16")
+        machine.succeed(f"systemd-creds --with-key=host+tpm2 encrypt --name=testkey {CRED_RAW_FILE} {CRED_FILE}")
+
+    with subtest("Write provided credential and check for equality"):
+        CRED_OUT_FILE = f"/root/{CRED_NAME}.out"
+        systemd_run(machine, f"systemd-creds cat testkey > {CRED_OUT_FILE}")
+        machine.succeed(f"cmp --silent -- {CRED_RAW_FILE} {CRED_OUT_FILE}")
+
+    with subtest("Check whether systemd service can see credential in systemd-creds list"):
+        systemd_run(machine, f"systemd-creds list | grep {CRED_NAME}")
+
+    with subtest("Check whether systemd service can access credential in $CREDENTIALS_DIRECTORY"):
+        systemd_run(machine, f"cmp --silent -- $CREDENTIALS_DIRECTORY/{CRED_NAME} {CRED_RAW_FILE}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-homed.nix b/nixpkgs/nixos/tests/systemd-homed.nix
new file mode 100644
index 000000000000..ecc92e98eddc
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-homed.nix
@@ -0,0 +1,99 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  password = "foobar";
+  newPass = "barfoo";
+in
+{
+  name = "systemd-homed";
+  nodes.machine = { config, pkgs, ... }: {
+    services.homed.enable = true;
+
+    users.users.test-normal-user = {
+      extraGroups = [ "wheel" ];
+      isNormalUser = true;
+      initialPassword = password;
+    };
+  };
+  testScript = ''
+    def switchTTY(number):
+      machine.send_key(f"alt-f{number}")
+      machine.wait_until_succeeds(f"[ $(fgconsole) = {number} ]")
+      machine.wait_for_unit(f"getty@tty{number}.service")
+      machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{number}'")
+
+    machine.wait_for_unit("multi-user.target")
+
+    # Smoke test to make sure the pam changes didn't break regular users.
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    with subtest("login as regular user"):
+      switchTTY(2)
+      machine.wait_until_tty_matches("2", "login: ")
+      machine.send_chars("test-normal-user\n")
+      machine.wait_until_tty_matches("2", "login: test-normal-user")
+      machine.wait_until_tty_matches("2", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -u test-normal-user bash")
+      machine.send_chars("whoami > /tmp/1\n")
+      machine.wait_for_file("/tmp/1")
+      assert "test-normal-user" in machine.succeed("cat /tmp/1")
+
+    with subtest("create homed encrypted user"):
+      # TODO: Figure out how to pass password manually.
+      #
+      # This environment variable is used for homed internal testing
+      # and is not documented.
+      machine.succeed("NEWPASSWORD=${password} homectl create --shell=/run/current-system/sw/bin/bash --storage=luks -G wheel test-homed-user")
+
+    with subtest("login as homed user"):
+      switchTTY(3)
+      machine.wait_until_tty_matches("3", "login: ")
+      machine.send_chars("test-homed-user\n")
+      machine.wait_until_tty_matches("3", "login: test-homed-user")
+      machine.wait_until_tty_matches("3", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -t tty3 -u test-homed-user bash")
+      machine.send_chars("whoami > /tmp/2\n")
+      machine.wait_for_file("/tmp/2")
+      assert "test-homed-user" in machine.succeed("cat /tmp/2")
+
+    with subtest("change homed user password"):
+      switchTTY(4)
+      machine.wait_until_tty_matches("4", "login: ")
+      machine.send_chars("test-homed-user\n")
+      machine.wait_until_tty_matches("4", "login: test-homed-user")
+      machine.wait_until_tty_matches("4", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -t tty4 -u test-homed-user bash")
+      machine.send_chars("passwd\n")
+      # homed does it in a weird order, it asks for new passes, then it asks
+      # for the old one.
+      machine.sleep(2)
+      machine.send_chars("${newPass}\n")
+      machine.sleep(2)
+      machine.send_chars("${newPass}\n")
+      machine.sleep(4)
+      machine.send_chars("${password}\n")
+      machine.wait_until_fails("pgrep -t tty4 passwd")
+
+      @polling_condition
+      def not_logged_in_tty5():
+        machine.fail("pgrep -t tty5 bash")
+
+      switchTTY(5)
+      with not_logged_in_tty5: # type: ignore[union-attr]
+        machine.wait_until_tty_matches("5", "login: ")
+        machine.send_chars("test-homed-user\n")
+        machine.wait_until_tty_matches("5", "login: test-homed-user")
+        machine.wait_until_tty_matches("5", "Password: ")
+        machine.send_chars("${password}\n")
+        machine.wait_until_tty_matches("5", "Password incorrect or not sufficient for authentication of user test-homed-user.")
+        machine.wait_until_tty_matches("5", "Sorry, try again: ")
+      machine.send_chars("${newPass}\n")
+      machine.send_chars("whoami > /tmp/4\n")
+      machine.wait_for_file("/tmp/4")
+      assert "test-homed-user" in machine.succeed("cat /tmp/4")
+
+    with subtest("homed user should be in wheel according to NSS"):
+      machine.succeed("userdbctl group wheel -s io.systemd.NameServiceSwitch | grep test-homed-user")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix b/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix
index 40fd2d4dc611..9196033789cb 100644
--- a/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix
+++ b/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix
@@ -6,6 +6,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     virtualisation = {
       emptyDiskImages = [ 512 512 ];
       useBootLoader = true;
+      # Booting off the BTRFS RAID requires an available init script from the Nix store
+      mountHostNixStore = true;
       useEFIBoot = true;
     };
     boot.loader.systemd-boot.enable = true;
@@ -21,14 +23,14 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       fileSystems = lib.mkVMOverride {
         "/".fsType = lib.mkForce "btrfs";
       };
-      virtualisation.bootDevice = "/dev/vdc";
+      virtualisation.rootDevice = "/dev/vdb";
     };
   };
 
   testScript = ''
     # Create RAID
-    machine.succeed("mkfs.btrfs -d raid0 /dev/vdc /dev/vdd")
-    machine.succeed("mkdir -p /mnt && mount /dev/vdc /mnt && echo hello > /mnt/test && umount /mnt")
+    machine.succeed("mkfs.btrfs -d raid0 /dev/vdb /dev/vdc")
+    machine.succeed("mkdir -p /mnt && mount /dev/vdb /mnt && echo hello > /mnt/test && umount /mnt")
 
     # Boot from the RAID
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-btrfs-raid.conf")
@@ -38,7 +40,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
 
     # Ensure we have successfully booted from the RAID
     assert "(initrd)" in machine.succeed("systemd-analyze")  # booted with systemd in stage 1
-    assert "/dev/vdc on / type btrfs" in machine.succeed("mount")
+    assert "/dev/vdb on / type btrfs" in machine.succeed("mount")
     assert "hello" in machine.succeed("cat /test")
     assert "Total devices 2" in machine.succeed("btrfs filesystem show")
   '';
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix
new file mode 100644
index 000000000000..32c79b731d80
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-fido2";
+
+  nodes.machine = { pkgs, config, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      # Booting off the encrypted disk requires having a Nix store available for the init script
+      mountHostNixStore = true;
+      useEFIBoot = true;
+      qemu.package = lib.mkForce (pkgs.qemu_test.override { canokeySupport = true; });
+      qemu.options = [ "-device canokey,file=/tmp/canokey-file" ];
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.initrd.systemd.enable = true;
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          crypttabExtraOpts = [ "fido2-device=auto" ];
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --fido2-device=auto /dev/vdb |& systemd-cat")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix
index 25c0c5bd866d..5ca0f48c333a 100644
--- a/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix
@@ -14,6 +14,8 @@ in {
     virtualisation = {
       emptyDiskImages = [ 512 ];
       useBootLoader = true;
+      # Necessary to boot off the encrypted disk because it requires a init script coming from the Nix store
+      mountHostNixStore = true;
       useEFIBoot = true;
     };
     boot.loader.systemd-boot.enable = true;
@@ -27,11 +29,11 @@ in {
     specialisation.boot-luks.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         cryptroot = {
-          device = "/dev/vdc";
+          device = "/dev/vdb";
           keyFile = "/etc/cryptroot.key";
         };
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
       boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
     };
   };
@@ -39,7 +41,7 @@ in {
   testScript = ''
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
-    machine.succeed("cryptsetup luksFormat -q --iter-time=1 -d ${keyfile} /dev/vdc")
+    machine.succeed("cryptsetup luksFormat -q --iter-time=1 -d ${keyfile} /dev/vdb")
 
     # Boot from the encrypted disk
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix
index e8e651f7b35f..a90a59feed6f 100644
--- a/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix
@@ -6,6 +6,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     virtualisation = {
       emptyDiskImages = [ 512 512 ];
       useBootLoader = true;
+      # Booting off the encrypted disk requires an available init script
+      mountHostNixStore = true;
       useEFIBoot = true;
     };
     boot.loader.systemd-boot.enable = true;
@@ -19,18 +21,22 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     specialisation.boot-luks.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         # We have two disks and only type one password - key reuse is in place
-        cryptroot.device = "/dev/vdc";
-        cryptroot2.device = "/dev/vdd";
+        cryptroot.device = "/dev/vdb";
+        cryptroot2.device = "/dev/vdc";
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      # test mounting device unlocked in initrd after switching root
+      virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2";
     };
   };
 
   testScript = ''
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
     machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
-    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdd -")
+    machine.succeed("echo -n supersecret | cryptsetup luksOpen   -q               /dev/vdc cryptroot2")
+    machine.succeed("mkfs.ext4 /dev/mapper/cryptroot2")
 
     # Boot from the encrypted disk
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
@@ -43,6 +49,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     machine.send_console("supersecret\n")
     machine.wait_for_unit("multi-user.target")
 
-    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount"), "/dev/mapper/cryptroot do not appear in mountpoints list"
+    assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount")
   '';
 })
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix
new file mode 100644
index 000000000000..73aa190ad620
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-tpm2";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      # Booting off the TPM2-encrypted device requires an available init script
+      mountHostNixStore = true;
+      useEFIBoot = true;
+      qemu.options = ["-chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"];
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.initrd.availableKernelModules = [ "tpm_tis" ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+    boot.initrd.systemd = {
+      enable = true;
+    };
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          crypttabExtraOpts = [ "tpm2-device=auto" ];
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+    };
+  };
+
+  testScript = ''
+    import subprocess
+    import os
+    import time
+
+
+    class Tpm:
+        def __init__(self):
+            os.mkdir("/tmp/mytpm1")
+            self.start()
+
+        def start(self):
+            self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", "socket", "--tpmstate", "dir=/tmp/mytpm1", "--ctrl", "type=unixio,path=/tmp/mytpm1/swtpm-sock", "--log", "level=20", "--tpm2"])
+
+        def wait_for_death_then_restart(self):
+            while self.proc.poll() is None:
+                print("waiting for tpm to die")
+                time.sleep(1)
+            assert self.proc.returncode == 0
+            self.start()
+
+    tpm = Tpm()
+
+
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --tpm2-pcrs= --tpm2-device=auto /dev/vdb |& systemd-cat")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    tpm.wait_for_death_then_restart()
+
+    # Boot and decrypt the disk
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-modprobe.nix b/nixpkgs/nixos/tests/systemd-initrd-modprobe.nix
new file mode 100644
index 000000000000..bf635a10d0e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-modprobe.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-modprobe";
+
+  nodes.machine = { pkgs, ... }: {
+    boot.initrd.systemd.enable = true;
+    boot.initrd.kernelModules = [ "loop" ]; # Load module in initrd.
+    boot.extraModprobeConfig = ''
+      options loop max_loop=42
+    '';
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    max_loop = machine.succeed("cat /sys/module/loop/parameters/max_loop")
+    assert int(max_loop) == 42, "Parameter should be respected for initrd kernel modules"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix b/nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix
new file mode 100644
index 000000000000..46dbdf537393
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix
@@ -0,0 +1,84 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-network-ssh";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes = with lib; {
+    server = { config, pkgs, ... }: {
+      environment.systemPackages = [pkgs.cryptsetup];
+      boot.loader.systemd-boot.enable = true;
+      boot.loader.timeout = 0;
+      virtualisation = {
+        emptyDiskImages = [ 4096 ];
+        useBootLoader = true;
+        # Booting off the encrypted disk requires an available init script from the Nix store
+        mountHostNixStore = true;
+        useEFIBoot = true;
+      };
+
+      specialisation.encrypted-root.configuration = {
+        virtualisation.rootDevice = "/dev/mapper/root";
+        boot.initrd.luks.devices = lib.mkVMOverride {
+          root.device = "/dev/vdb";
+        };
+        boot.initrd.systemd.enable = true;
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ (readFile ./initrd-network-ssh/id_ed25519.pub) ];
+            port = 22;
+            # Terrible hack so it works with useBootLoader
+            hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ];
+          };
+        };
+      };
+    };
+
+    client = { config, ... }: {
+      environment.etc = {
+        knownHosts = {
+          text = concatStrings [
+            "server,"
+            "${
+              toString (head (splitString " " (toString
+                (elemAt (splitString "\n" config.networking.extraHosts) 2))))
+            } "
+            "${readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}"
+          ];
+        };
+        sshKey = {
+          source = ./initrd-network-ssh/id_ed25519;
+          mode = "0600";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    def ssh_is_up(_) -> bool:
+        status, _ = client.execute("nc -z server 22")
+        return status == 0
+
+    server.wait_for_unit("multi-user.target")
+    server.succeed(
+        "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdb",
+        "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf",
+        "sync",
+    )
+    server.shutdown()
+    server.start()
+
+    client.wait_for_unit("network.target")
+    with client.nested("waiting for SSH server to come up"):
+        retry(ssh_is_up)
+
+    client.succeed(
+        "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit"
+    )
+
+    server.wait_for_unit("multi-user.target")
+    server.succeed("mount | grep '/dev/mapper/root on /'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-networkd.nix b/nixpkgs/nixos/tests/systemd-initrd-networkd.nix
new file mode 100644
index 000000000000..8376276d8f63
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-networkd.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-initrd-network";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes = let
+    mkFlushTest = flush: script: { ... }: {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network = {
+        enable = true;
+        flushBeforeStage2 = flush;
+      };
+      systemd.services.check-flush = {
+        requiredBy = ["multi-user.target"];
+        before = ["network-pre.target" "multi-user.target"];
+        wants = ["network-pre.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig.Type = "oneshot";
+        path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+        inherit script;
+      };
+    };
+  in {
+    basic = { ... }: {
+      boot.initrd.network.enable = true;
+
+      boot.initrd.systemd = {
+        enable = true;
+        # Enable network-online to fail the test in case of timeout
+        network.wait-online.timeout = 10;
+        network.wait-online.anyInterface = true;
+        targets.network-online.requiredBy = [ "initrd.target" ];
+        services.systemd-networkd-wait-online.requiredBy =
+          [ "network-online.target" ];
+
+          initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+          services.check = {
+            requiredBy = [ "initrd.target" ];
+            before = [ "initrd.target" ];
+            after = [ "network-online.target" ];
+            serviceConfig.Type = "oneshot";
+            path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+            script = ''
+              ip addr | grep 10.0.2.15 || exit 1
+              ping -c1 10.0.2.2 || exit 1
+            '';
+          };
+      };
+    };
+
+    doFlush = mkFlushTest true ''
+      if ip addr | grep 10.0.2.15; then
+        echo "Network configuration survived switch-root; flushBeforeStage2 failed"
+        exit 1
+      fi
+    '';
+
+    dontFlush = mkFlushTest false ''
+      if ! (ip addr | grep 10.0.2.15); then
+        echo "Network configuration didn't survive switch-root"
+        exit 1
+      fi
+    '';
+  };
+
+  testScript = ''
+    start_all()
+    basic.wait_for_unit("multi-user.target")
+    doFlush.wait_for_unit("multi-user.target")
+    dontFlush.wait_for_unit("multi-user.target")
+    # Make sure the systemd-network user was set correctly in initrd
+    basic.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
+    basic.succeed("ip addr show >&2")
+    basic.succeed("ip route show >&2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-simple.nix b/nixpkgs/nixos/tests/systemd-initrd-simple.nix
index 5d98114304b7..a6a22e9d48e0 100644
--- a/nixpkgs/nixos/tests/systemd-initrd-simple.nix
+++ b/nixpkgs/nixos/tests/systemd-initrd-simple.nix
@@ -6,9 +6,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       enable = true;
       emergencyAccess = true;
     };
-    fileSystems = lib.mkVMOverride {
-      "/".autoResize = true;
-    };
+    virtualisation.fileSystems."/".autoResize = true;
   };
 
   testScript = ''
@@ -29,6 +27,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         machine.succeed("[ -e /dev/pts/ptmx ]") # /dev/pts
         machine.succeed("[ -e /run/keys ]") # /run/keys
 
+    with subtest("groups work"):
+        machine.fail("journalctl -b 0 | grep 'systemd-udevd.*Unknown group.*ignoring'")
 
     with subtest("growfs works"):
         oldAvail = machine.succeed("df --output=avail / | sed 1d")
diff --git a/nixpkgs/nixos/tests/systemd-initrd-swraid.nix b/nixpkgs/nixos/tests/systemd-initrd-swraid.nix
index 28a0fb3192ae..0d5a1c6354d0 100644
--- a/nixpkgs/nixos/tests/systemd-initrd-swraid.nix
+++ b/nixpkgs/nixos/tests/systemd-initrd-swraid.nix
@@ -6,6 +6,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     virtualisation = {
       emptyDiskImages = [ 512 512 ];
       useBootLoader = true;
+      # Booting off the RAID requires an available init script
+      mountHostNixStore = true;
       useEFIBoot = true;
     };
     boot.loader.systemd-boot.enable = true;
@@ -20,18 +22,18 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       services.swraid = {
         enable = true;
         mdadmConf = ''
-          ARRAY /dev/md0 devices=/dev/vdc,/dev/vdd
+          ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc
         '';
       };
       kernelModules = [ "raid0" ];
     };
 
-    specialisation.boot-swraid.configuration.virtualisation.bootDevice = "/dev/disk/by-label/testraid";
+    specialisation.boot-swraid.configuration.virtualisation.rootDevice = "/dev/disk/by-label/testraid";
   };
 
   testScript = ''
     # Create RAID
-    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid0 /dev/vdc /dev/vdd")
+    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid0 /dev/vdb /dev/vdc")
     machine.succeed("mkfs.ext4 -L testraid /dev/md0")
     machine.succeed("mkdir -p /mnt && mount /dev/md0 /mnt && echo hello > /mnt/test && umount /mnt")
 
diff --git a/nixpkgs/nixos/tests/systemd-initrd-vconsole.nix b/nixpkgs/nixos/tests/systemd-initrd-vconsole.nix
new file mode 100644
index 000000000000..b74df410c422
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-vconsole.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-vconsole";
+
+  nodes.machine = { pkgs, ... }: {
+    boot.kernelParams = [ "rd.systemd.unit=rescue.target" ];
+
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+
+    console = {
+      earlySetup = true;
+      keyMap = "colemak";
+    };
+  };
+
+  testScript = ''
+    # Boot into rescue shell in initrd
+    machine.start()
+    machine.wait_for_console_text("Press Enter for maintenance")
+    machine.send_console("\n")
+    machine.wait_for_console_text("Logging in with home")
+
+    # Check keymap
+    machine.send_console("(printf '%s to receive text: \\n' Ready && read text && echo \"$text\") </dev/tty1\n")
+    machine.wait_for_console_text("Ready to receive text:")
+    for key in "asdfjkl;\n":
+      machine.send_key(key)
+    machine.wait_for_console_text("arstneio")
+    machine.send_console("systemctl poweroff\n")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-machinectl.nix b/nixpkgs/nixos/tests/systemd-machinectl.nix
index 5c7926e24abf..b8ed0c33e8e4 100644
--- a/nixpkgs/nixos/tests/systemd-machinectl.nix
+++ b/nixpkgs/nixos/tests/systemd-machinectl.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix (
+import ./make-test-python.nix ({ pkgs, ... }:
   let
 
     container = {
@@ -18,6 +18,7 @@ import ./make-test-python.nix (
     };
 
     containerSystem = (import ../lib/eval-config.nix {
+      inherit (pkgs) system;
       modules = [ container ];
     }).config.system.build.toplevel;
 
@@ -40,6 +41,17 @@ import ./make-test-python.nix (
       systemd.targets.machines.wants = [ "systemd-nspawn@${containerName}.service" ];
 
       virtualisation.additionalPaths = [ containerSystem ];
+
+      # not needed, but we want to test the nspawn file generation
+      systemd.nspawn.${containerName} = { };
+
+      systemd.services."systemd-nspawn@${containerName}" = {
+        serviceConfig.Environment = [
+          # Disable tmpfs for /tmp
+          "SYSTEMD_NSPAWN_TMPFS_TMP=0"
+        ];
+        overrideStrategy = "asDropin";
+      };
     };
 
     testScript = ''
@@ -91,6 +103,9 @@ import ./make-test-python.nix (
       machine.succeed("machinectl stop ${containerName}");
       machine.wait_until_succeeds("test $(systemctl is-active systemd-nspawn@${containerName}) = inactive");
 
+      # Test tmpfs for /tmp
+      machine.fail("mountpoint /tmp");
+
       # Show to to delete the container
       machine.succeed("chattr -i ${containerRoot}/var/empty");
       machine.succeed("rm -rf ${containerRoot}");
diff --git a/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index 37a89fc21e44..e6bed6b9218f 100644
--- a/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -1,16 +1,16 @@
 # This test verifies that we can request and assign IPv6 prefixes from upstream
 # (e.g. ISP) routers.
-# The setup consits of three VMs. One for the ISP, as your residential router
+# The setup consists of three VMs. One for the ISP, as your residential router
 # and the third as a client machine in the residential network.
 #
 # There are two VLANs in this test:
 # - VLAN 1 is the connection between the ISP and the router
 # - VLAN 2 is the connection between the router and the client
 
-import ./make-test-python.nix ({pkgs, ...}: {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "systemd-networkd-ipv6-prefix-delegation";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ andir ];
+  meta = with lib.maintainers; {
+    maintainers = [ andir hexa ];
   };
   nodes = {
 
@@ -22,26 +22,38 @@ import ./make-test-python.nix ({pkgs, ...}: {
     #
     # Note: On the ISPs device we don't really care if we are using networkd in
     # this example. That being said we can't use it (yet) as networkd doesn't
-    # implement the serving side of DHCPv6. We will use ISC's well aged dhcpd6
-    # for that task.
+    # implement the serving side of DHCPv6. We will use ISC Kea for that task.
     isp = { lib, pkgs, ... }: {
       virtualisation.vlans = [ 1 ];
       networking = {
         useDHCP = false;
         firewall.enable = false;
-        interfaces.eth1.ipv4.addresses = lib.mkForce []; # no need for legacy IP
-        interfaces.eth1.ipv6.addresses = lib.mkForce [
-          { address = "2001:DB8::1"; prefixLength = 64; }
-        ];
+        interfaces.eth1 = lib.mkForce {}; # Don't use scripted networking
+      };
+
+      systemd.network = {
+        enable = true;
+
+        networks = {
+          "eth1" = {
+            matchConfig.Name = "eth1";
+            address = [
+              "2001:DB8::1/64"
+            ];
+            networkConfig.IPForward = true;
+          };
+        };
       };
 
       # Since we want to program the routes that we delegate to the "customer"
-      # into our routing table we must give dhcpd the required privs.
-      systemd.services.dhcpd6.serviceConfig.AmbientCapabilities =
-        [ "CAP_NET_ADMIN" ];
+      # into our routing table we must provide kea with the required capability.
+      systemd.services.kea-dhcp6-server.serviceConfig = {
+        AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+        CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
+      };
 
       services = {
-        # Configure the DHCPv6 server
+        # Configure the DHCPv6 server to hand out both IA_NA and IA_PD.
         #
         # We will hand out /48 prefixes from the subnet 2001:DB8:F000::/36.
         # That gives us ~8k prefixes. That should be enough for this test.
@@ -49,31 +61,70 @@ import ./make-test-python.nix ({pkgs, ...}: {
         # Since (usually) you will not receive a prefix with the router
         # advertisements we also hand out /128 leases from the range
         # 2001:DB8:0000:0000:FFFF::/112.
-        dhcpd6 = {
+        kea.dhcp6 = {
           enable = true;
-          interfaces = [ "eth1" ];
-          extraConfig = ''
-            subnet6 2001:DB8::/36 {
-              range6 2001:DB8:0000:0000:FFFF:: 2001:DB8:0000:0000:FFFF::FFFF;
-              prefix6 2001:DB8:F000:: 2001:DB8:FFFF:: /48;
-            }
-
-            # This is the secret sauce. We have to extract the prefix and the
-            # next hop when commiting the lease to the database.  dhcpd6
-            # (rightfully) has not concept of adding routes to the systems
-            # routing table. It really depends on the setup.
+          settings = {
+            interfaces-config.interfaces = [ "eth1" ];
+            subnet6 = [ {
+              interface = "eth1";
+              subnet = "2001:DB8:F::/36";
+              pd-pools = [ {
+                prefix = "2001:DB8:F::";
+                prefix-len = 36;
+                delegated-len = 48;
+              } ];
+              pools = [ {
+                pool = "2001:DB8:0000:0000:FFFF::-2001:DB8:0000:0000:FFFF::FFFF";
+              } ];
+            } ];
+
+            # This is the glue between Kea and the Kernel FIB. DHCPv6
+            # rightfully has no concept of setting up a route in your
+            # FIB. This step really depends on your setup.
             #
-            # In a production environment your DHCPv6 server is likely not the
-            # router. You might want to consider BGP, custom NetConf calls, …
-            # in those cases.
-            on commit {
-              set IP = pick-first-value(binary-to-ascii(16, 16, ":", substring(option dhcp6.ia-na, 16, 16)), "n/a");
-              set Prefix = pick-first-value(binary-to-ascii(16, 16, ":", suffix(option dhcp6.ia-pd, 16)), "n/a");
-              set PrefixLength = pick-first-value(binary-to-ascii(10, 8, ":", substring(suffix(option dhcp6.ia-pd, 17), 0, 1)), "n/a");
-              log(concat(IP, " ", Prefix, " ", PrefixLength));
-              execute("${pkgs.iproute2}/bin/ip", "-6", "route", "replace", concat(Prefix,"/",PrefixLength), "via", IP);
-            }
-          '';
+            # In a production environment your DHCPv6 server is likely
+            # not the router. You might want to consider BGP, NETCONF
+            # calls, … in those cases.
+            #
+            # In this example we use the run script hook, that lets use
+            # execute anything and passes information via the environment.
+            # https://kea.readthedocs.io/en/kea-2.2.0/arm/hooks.html#run-script-run-script-support-for-external-hook-scripts
+            hooks-libraries = [ {
+              library = "${pkgs.kea}/lib/kea/hooks/libdhcp_run_script.so";
+              parameters = {
+                name = pkgs.writeShellScript "kea-run-hooks" ''
+                  export PATH="${lib.makeBinPath (with pkgs; [ coreutils iproute2 ])}"
+
+                  set -euxo pipefail
+
+                  leases6_committed() {
+                    for i in $(seq $LEASES6_SIZE); do
+                      idx=$((i-1))
+                      prefix_var="LEASES6_AT''${idx}_ADDRESS"
+                      plen_var="LEASES6_AT''${idx}_PREFIX_LEN"
+
+                      ip -6 route replace ''${!prefix_var}/''${!plen_var} via $QUERY6_REMOTE_ADDR dev $QUERY6_IFACE_NAME
+                    done
+                  }
+
+                  unknown_handler() {
+                    echo "Unhandled function call ''${*}"
+                    exit 123
+                  }
+
+                  case "$1" in
+                      "leases6_committed")
+                          leases6_committed
+                          ;;
+                      *)
+                          unknown_handler "''${@}"
+                          ;;
+                  esac
+                '';
+                sync = false;
+              };
+            } ];
+          };
         };
 
         # Finally we have to set up the router advertisements. While we could be
@@ -176,7 +227,7 @@ import ./make-test-python.nix ({pkgs, ...}: {
               IPv6AcceptRA = false;
 
               # Delegate prefixes from the DHCPv6 PD pool.
-              DHCPv6PrefixDelegation = true;
+              DHCPPrefixDelegation = true;
               IPv6SendRA = true;
             };
 
@@ -217,7 +268,7 @@ import ./make-test-python.nix ({pkgs, ...}: {
       systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
     };
 
-    # This is the client behind the router. We should be receving router
+    # This is the client behind the router. We should be receiving router
     # advertisements for both the ULA and the delegated prefix.
     # All we have to do is boot with the default (networkd) configuration.
     client = {
diff --git a/nixpkgs/nixos/tests/systemd-networkd-vrf.nix b/nixpkgs/nixos/tests/systemd-networkd-vrf.nix
index 3c18f788e927..d4227526a30d 100644
--- a/nixpkgs/nixos/tests/systemd-networkd-vrf.nix
+++ b/nixpkgs/nixos/tests/systemd-networkd-vrf.nix
@@ -1,5 +1,26 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: let
   inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  mkNode = vlan: id: {
+    virtualisation.vlans = [ vlan ];
+    networking = {
+      useDHCP = false;
+      useNetworkd = true;
+    };
+
+    systemd.network = {
+      enable = true;
+
+      networks."10-eth${toString vlan}" = {
+        matchConfig.Name = "eth${toString vlan}";
+        linkConfig.RequiredForOnline = "no";
+        networkConfig = {
+          Address = "192.168.${toString vlan}.${toString id}/24";
+          IPForward = "yes";
+        };
+      };
+    };
+  };
 in {
   name = "systemd-networkd-vrf";
   meta.maintainers = with lib.maintainers; [ ma27 ];
@@ -54,7 +75,7 @@ in {
           linkConfig.RequiredForOnline = "no";
           networkConfig = {
             VRF = "vrf1";
-            Address = "192.168.1.1";
+            Address = "192.168.1.1/24";
             IPForward = "yes";
           };
         };
@@ -63,78 +84,23 @@ in {
           linkConfig.RequiredForOnline = "no";
           networkConfig = {
             VRF = "vrf2";
-            Address = "192.168.2.1";
-            IPForward = "yes";
-          };
-        };
-      };
-    };
-
-    node1 = { pkgs, ... }: {
-      virtualisation.vlans = [ 1 ];
-      networking = {
-        useDHCP = false;
-        useNetworkd = true;
-      };
-
-      services.openssh.enable = true;
-      users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
-
-      systemd.network = {
-        enable = true;
-
-        networks."10-eth1" = {
-          matchConfig.Name = "eth1";
-          linkConfig.RequiredForOnline = "no";
-          networkConfig = {
-            Address = "192.168.1.2";
-            IPForward = "yes";
-          };
-        };
-      };
-    };
-
-    node2 = { pkgs, ... }: {
-      virtualisation.vlans = [ 2 ];
-      networking = {
-        useDHCP = false;
-        useNetworkd = true;
-      };
-
-      systemd.network = {
-        enable = true;
-
-        networks."10-eth2" = {
-          matchConfig.Name = "eth2";
-          linkConfig.RequiredForOnline = "no";
-          networkConfig = {
-            Address = "192.168.2.3";
+            Address = "192.168.2.1/24";
             IPForward = "yes";
           };
         };
       };
     };
 
-    node3 = { pkgs, ... }: {
-      virtualisation.vlans = [ 2 ];
-      networking = {
-        useDHCP = false;
-        useNetworkd = true;
-      };
-
-      systemd.network = {
-        enable = true;
+    node1 = lib.mkMerge [
+      (mkNode 1 2)
+      {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      }
+    ];
 
-        networks."10-eth2" = {
-          matchConfig.Name = "eth2";
-          linkConfig.RequiredForOnline = "no";
-          networkConfig = {
-            Address = "192.168.2.4";
-            IPForward = "yes";
-          };
-        };
-      };
-    };
+    node2 = mkNode 2 3;
+    node3 = mkNode 2 4;
   };
 
   testScript = ''
@@ -159,22 +125,6 @@ in {
     node2.wait_for_unit("network.target")
     node3.wait_for_unit("network.target")
 
-    client_ipv4_table = """
-    192.168.1.2 dev vrf1 proto static metric 100\x20
-    192.168.2.3 dev vrf2 proto static metric 100
-    """.strip()
-    vrf1_table = """
-    192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1\x20
-    local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1\x20
-    broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.1
-    """.strip()
-    vrf2_table = """
-    192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1\x20
-    local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1\x20
-    broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1
-    """.strip()
-    # editorconfig-checker-enable
-
     # Check that networkd properly configures the main routing table
     # and the routing tables for the VRF.
     with subtest("check vrf routing tables"):
diff --git a/nixpkgs/nixos/tests/systemd-no-tainted.nix b/nixpkgs/nixos/tests/systemd-no-tainted.nix
new file mode 100644
index 000000000000..f0504065f2a4
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-no-tainted.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "systemd-no-tainted";
+
+  nodes.machine = { };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    with subtest("systemctl should not report tainted with unmerged-usr"):
+        output = machine.succeed("systemctl status")
+        print(output)
+        assert "Tainted" not in output
+        assert "unmerged-usr" not in output
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-oomd.nix b/nixpkgs/nixos/tests/systemd-oomd.nix
new file mode 100644
index 000000000000..55c4c1350000
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-oomd.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "systemd-oomd";
+
+  # This test is a simplified version of systemd's testsuite-55.
+  # https://github.com/systemd/systemd/blob/v251/test/units/testsuite-55.sh
+  nodes.machine = { pkgs, ... }: {
+    # Limit VM resource usage.
+    virtualisation.memorySize = 1024;
+    systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = "1s";
+
+    systemd.slices.workload = {
+      description = "Test slice for memory pressure kills";
+      sliceConfig = {
+        MemoryAccounting = true;
+        ManagedOOMMemoryPressure = "kill";
+        ManagedOOMMemoryPressureLimit = "10%";
+      };
+    };
+
+    systemd.services.testbloat = {
+      description = "Create a lot of memory pressure";
+      serviceConfig = {
+        Slice = "workload.slice";
+        MemoryHigh = "5M";
+        ExecStart = "${pkgs.coreutils}/bin/tail /dev/zero";
+      };
+    };
+
+    systemd.services.testchill = {
+      description = "No memory pressure";
+      serviceConfig = {
+        Slice = "workload.slice";
+        MemoryHigh = "3M";
+        ExecStart = "${pkgs.coreutils}/bin/sleep infinity";
+      };
+    };
+  };
+
+  testScript = ''
+    # Start the system.
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("oomctl")
+
+    machine.succeed("systemctl start testchill.service")
+    with subtest("OOMd should kill the bad service"):
+        machine.fail("systemctl start --wait testbloat.service")
+        assert machine.get_unit_info("testbloat.service")["Result"] == "oom-kill"
+
+    with subtest("Service without memory pressure should be untouched"):
+        machine.require_unit_state("testchill.service", "active")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-portabled.nix b/nixpkgs/nixos/tests/systemd-portabled.nix
new file mode 100644
index 000000000000..ef38258b0d86
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-portabled.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({pkgs, lib, ...}: let
+  demo-program = pkgs.writeShellScriptBin "demo" ''
+      while ${pkgs.coreutils}/bin/sleep 3; do
+          echo Hello World > /dev/null
+      done
+  '';
+  demo-service = pkgs.writeText "demo.service" ''
+    [Unit]
+    Description=demo service
+    Requires=demo.socket
+    After=demo.socket
+
+    [Service]
+    Type=simple
+    ExecStart=${demo-program}/bin/demo
+    Restart=always
+
+    [Install]
+    WantedBy=multi-user.target
+    Also=demo.socket
+  '';
+  demo-socket = pkgs.writeText "demo.socket" ''
+    [Unit]
+    Description=demo socket
+
+    [Socket]
+    ListenStream=/run/demo.sock
+    SocketMode=0666
+
+    [Install]
+    WantedBy=sockets.target
+  '';
+  demo-portable = pkgs.portableService {
+    pname = "demo";
+    version = "1.0";
+    description = ''A demo "Portable Service" for a shell program built with nix'';
+    units = [ demo-service demo-socket ];
+  };
+in {
+
+  name = "systemd-portabled";
+  nodes.machine = {};
+  testScript = ''
+    machine.succeed("portablectl")
+    machine.wait_for_unit("systemd-portabled.service")
+    machine.succeed("portablectl attach --now --runtime ${demo-portable}/demo_1.0.raw")
+    machine.wait_for_unit("demo.service")
+    machine.succeed("portablectl detach --now --runtime demo_1.0")
+    machine.fail("systemctl status demo.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-repart.nix b/nixpkgs/nixos/tests/systemd-repart.nix
new file mode 100644
index 000000000000..22ea8fbd2271
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-repart.nix
@@ -0,0 +1,192 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  # A testScript fragment that prepares a disk with some empty, unpartitioned
+  # space. and uses it to boot the test with. Takes a single argument `machine`
+  # from which the diskImage is extracted.
+  useDiskImage = machine: ''
+    import os
+    import shutil
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name)
+
+    subprocess.run([
+      "${machine.config.virtualisation.qemu.package}/bin/qemu-img",
+      "resize",
+      "-f",
+      "raw",
+      tmp_disk_image.name,
+      "+32M",
+    ])
+
+    # Fix the GPT table by moving the backup table to the end of the enlarged
+    # disk image. This is necessary because we increased the size of the disk
+    # before. The disk needs to be a raw disk because sgdisk can only run on
+    # raw images.
+    subprocess.run([
+      "${pkgs.gptfdisk}/bin/sgdisk",
+      "--move-second-header",
+      tmp_disk_image.name,
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+  '';
+
+  common = { config, pkgs, lib, ... }: {
+    virtualisation.useDefaultFilesystems = false;
+    virtualisation.fileSystems = {
+      "/" = {
+        device = "/dev/vda2";
+        fsType = "ext4";
+      };
+    };
+
+    # systemd-repart operates on disks with a partition table. The qemu module,
+    # however, creates separate filesystem images without a partition table, so
+    # we have to create a disk image manually.
+    #
+    # This creates two partitions, an ESP available as /dev/vda1 and the root
+    # partition available as /dev/vda2.
+    system.build.diskImage = import ../lib/make-disk-image.nix {
+      inherit config pkgs lib;
+      # Use a raw format disk so that it can be resized before starting the
+      # test VM.
+      format = "raw";
+      # Keep the image as small as possible but leave some room for changes.
+      bootSize = "32M";
+      additionalSpace = "0M";
+      # GPT with an EFI System Partition is the typical use case for
+      # systemd-repart because it does not support MBR.
+      partitionTableType = "efi";
+      # We do not actually care much about the content of the partitions, so we
+      # do not need a bootloader installed.
+      installBootLoader = false;
+      # Improve determinism by not copying a channel.
+      copyChannel = false;
+    };
+  };
+in
+{
+  basic = makeTest {
+    name = "systemd-repart";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      imports = [ common ];
+
+      boot.initrd.systemd.enable = true;
+
+      boot.initrd.systemd.repart.enable = true;
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "linux-generic";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
+      assert "Growing existing partition 1." in systemd_repart_logs
+    '';
+  };
+
+  after-initrd = makeTest {
+    name = "systemd-repart-after-initrd";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      imports = [ common ];
+
+      systemd.repart.enable = true;
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "linux-generic";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --unit systemd-repart.service")
+      assert "Growing existing partition 1." in systemd_repart_logs
+    '';
+  };
+
+  create-root = makeTest {
+    name = "systemd-repart-create-root";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, lib, pkgs, ... }: {
+      virtualisation.useDefaultFilesystems = false;
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-partlabel/created-root";
+          fsType = "ext4";
+        };
+        "/nix/store" = {
+          device = "/dev/vda2";
+          fsType = "ext4";
+        };
+      };
+
+      # Create an image containing only the Nix store. This enables creating
+      # the root partition with systemd-repart and then successfully booting
+      # into a working system.
+      #
+      # This creates two partitions, an ESP available as /dev/vda1 and the Nix
+      # store available as /dev/vda2.
+      system.build.diskImage = import ../lib/make-disk-image.nix {
+        inherit config pkgs lib;
+        onlyNixStore = true;
+        format = "raw";
+        bootSize = "32M";
+        additionalSpace = "0M";
+        partitionTableType = "efi";
+        installBootLoader = false;
+        copyChannel = false;
+      };
+
+      boot.initrd.systemd.enable = true;
+
+      boot.initrd.systemd.repart.enable = true;
+      boot.initrd.systemd.repart.device = "/dev/vda";
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "root";
+          Label = "created-root";
+          Format = "ext4";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
+      assert "Adding new partition 2 to partition table." in systemd_repart_logs
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/systemd-shutdown.nix b/nixpkgs/nixos/tests/systemd-shutdown.nix
index 688cd6dd2c17..dad8167f198f 100644
--- a/nixpkgs/nixos/tests/systemd-shutdown.nix
+++ b/nixpkgs/nixos/tests/systemd-shutdown.nix
@@ -11,6 +11,7 @@ in {
     systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/shutdown-message".source = pkgs.writeShellScript "shutdown-message" ''
       echo "${msg}"
     '';
+    boot.initrd.systemd.enable = systemdStage1;
   };
 
   testScript = ''
diff --git a/nixpkgs/nixos/tests/systemd-timesyncd.nix b/nixpkgs/nixos/tests/systemd-timesyncd.nix
index ad5b9a47383b..43abd36c47d9 100644
--- a/nixpkgs/nixos/tests/systemd-timesyncd.nix
+++ b/nixpkgs/nixos/tests/systemd-timesyncd.nix
@@ -11,11 +11,11 @@ in {
   name = "systemd-timesyncd";
   nodes = {
     current = mkVM {};
-    pre1909 = mkVM ({lib, ... }: with lib; {
+    pre1909 = mkVM ({lib, ... }: {
       # create the path that should be migrated by our activation script when
       # upgrading to a newer nixos version
       system.stateVersion = "19.03";
-      system.activationScripts.simulate-old-timesync-state-dir = mkBefore ''
+      system.activationScripts.simulate-old-timesync-state-dir = lib.mkBefore ''
         rm -f /var/lib/systemd/timesync
         mkdir -p /var/lib/systemd /var/lib/private/systemd/timesync
         ln -s /var/lib/private/systemd/timesync /var/lib/systemd/timesync
diff --git a/nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix b/nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix
new file mode 100644
index 000000000000..bf29b4b57be3
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-user-tmpfiles-rules";
+
+  meta = with lib.maintainers; {
+    maintainers = [ schnusch ];
+  };
+
+  nodes.machine = { ... }: {
+    users.users = {
+      alice.isNormalUser = true;
+      bob.isNormalUser = true;
+    };
+
+    systemd.user.tmpfiles = {
+      rules = [
+        "d %h/user_tmpfiles_created"
+      ];
+      users.alice.rules = [
+        "d %h/only_alice"
+      ];
+    };
+  };
+
+  testScript = { ... }: ''
+    machine.succeed("loginctl enable-linger alice bob")
+
+    machine.wait_until_succeeds("systemctl --user --machine=alice@ is-active systemd-tmpfiles-setup.service")
+    machine.succeed("[ -d ~alice/user_tmpfiles_created ]")
+    machine.succeed("[ -d ~alice/only_alice ]")
+
+    machine.wait_until_succeeds("systemctl --user --machine=bob@ is-active systemd-tmpfiles-setup.service")
+    machine.succeed("[ -d ~bob/user_tmpfiles_created ]")
+    machine.succeed("[ ! -e ~bob/only_alice ]")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-userdbd.nix b/nixpkgs/nixos/tests/systemd-userdbd.nix
new file mode 100644
index 000000000000..5d0233ffd9fb
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-userdbd.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-userdbd";
+  nodes.machine = { config, pkgs, ... }: {
+    services.userdbd.enable = true;
+
+    users.users.test-user-nss = {
+      isNormalUser = true;
+    };
+
+    environment.etc."userdb/test-user-dropin.user".text = builtins.toJSON {
+      userName = "test-user-dropin";
+    };
+
+    environment.systemPackages = with pkgs; [ libvarlink ];
+  };
+  testScript = ''
+    import json
+    from shlex import quote
+
+    def getUserRecord(name):
+      Interface = "unix:/run/systemd/userdb/io.systemd.Multiplexer/io.systemd.UserDatabase"
+      payload = json.dumps({
+        "service": "io.systemd.Multiplexer",
+        "userName": name
+      })
+      return json.loads(machine.succeed(f"varlink call {Interface}.GetUserRecord {quote(payload)}"))
+
+    machine.wait_for_unit("systemd-userdbd.socket")
+    getUserRecord("test-user-nss")
+    getUserRecord("test-user-dropin")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd.nix b/nixpkgs/nixos/tests/systemd.nix
index 3317823e03f7..3c36291b733d 100644
--- a/nixpkgs/nixos/tests/systemd.nix
+++ b/nixpkgs/nixos/tests/systemd.nix
@@ -87,12 +87,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         machine.succeed("test -e /home/alice/user_conf_read")
         machine.succeed("test -z $(ls -1 /var/log/journal)")
 
-    # Regression test for https://github.com/NixOS/nixpkgs/issues/50273
-    with subtest("DynamicUser actually allocates a user"):
-        assert "iamatest" in machine.succeed(
-            "systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami"
-        )
-
     with subtest("regression test for https://bugs.freedesktop.org/show_bug.cgi?id=77507"):
         retcode, output = machine.execute("systemctl status testservice1.service")
         assert retcode in [0, 3]  # https://bugs.freedesktop.org/show_bug.cgi?id=77507
diff --git a/nixpkgs/nixos/tests/tandoor-recipes.nix b/nixpkgs/nixos/tests/tandoor-recipes.nix
new file mode 100644
index 000000000000..54456238fe63
--- /dev/null
+++ b/nixpkgs/nixos/tests/tandoor-recipes.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "tandoor-recipes";
+  meta.maintainers = with lib.maintainers; [ ambroisie ];
+
+  nodes.machine = { pkgs, ... }: {
+    # Setup using Postgres
+    services.tandoor-recipes = {
+      enable = true;
+
+      extraConfig = {
+        DB_ENGINE = "django.db.backends.postgresql";
+        POSTGRES_HOST = "/run/postgresql";
+        POSTGRES_USER = "tandoor_recipes";
+        POSTGRES_DB = "tandoor_recipes";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "tandoor_recipes" ];
+      ensureUsers = [
+        {
+          name = "tandoor_recipes";
+          ensurePermissions."DATABASE tandoor_recipes" = "ALL PRIVILEGES";
+        }
+      ];
+    };
+
+    systemd.services = {
+      tandoor-recipes = {
+        after = [ "postgresql.service" ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("tandoor-recipes.service")
+
+    with subtest("Web interface gets ready"):
+        # Wait until server accepts connections
+        machine.wait_until_succeeds("curl -fs localhost:8080")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/taskserver.nix b/nixpkgs/nixos/tests/taskserver.nix
index b2bd421e231f..254bc8822f89 100644
--- a/nixpkgs/nixos/tests/taskserver.nix
+++ b/nixpkgs/nixos/tests/taskserver.nix
@@ -69,23 +69,14 @@ in {
         testOrganisation.users = [ "alice" "foo" ];
         anotherOrganisation.users = [ "bob" ];
       };
-    };
 
-    # New generation of the server with manual config
-    newServer = { lib, nodes, ... }: {
-      imports = [ server ];
-      services.taskserver.pki.manual = {
-        ca.cert = snakeOil.cacert;
-        server.cert = snakeOil.cert;
-        server.key = snakeOil.key;
-        server.crl = snakeOil.crl;
-      };
-      # This is to avoid assigning a different network address to the new
-      # generation.
-      networking = lib.mapAttrs (lib.const lib.mkForce) {
-        interfaces.eth1.ipv4 = nodes.server.config.networking.interfaces.eth1.ipv4;
-        inherit (nodes.server.config.networking)
-          hostName primaryIPAddress extraHosts;
+      specialisation.manual-config.configuration = {
+        services.taskserver.pki.manual = {
+          ca.cert = snakeOil.cacert;
+          server.cert = snakeOil.cert;
+          server.key = snakeOil.key;
+          server.crl = snakeOil.crl;
+        };
       };
     };
 
@@ -103,7 +94,8 @@ in {
   testScript = { nodes, ... }: let
     cfg = nodes.server.config.services.taskserver;
     portStr = toString cfg.listenPort;
-    newServerSystem = nodes.newServer.config.system.build.toplevel;
+    specialisations = "${nodes.server.system.build.toplevel}/specialisation";
+    newServerSystem = "${specialisations}/manual-config";
     switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
   in ''
     from shlex import quote
diff --git a/nixpkgs/nixos/tests/tayga.nix b/nixpkgs/nixos/tests/tayga.nix
new file mode 100644
index 000000000000..44974f6efea8
--- /dev/null
+++ b/nixpkgs/nixos/tests/tayga.nix
@@ -0,0 +1,235 @@
+# This test verifies that we can ping an IPv4-only server from an IPv6-only
+# client via a NAT64 router. The hosts and networks are configured as follows:
+#
+#        +------
+# Client | eth1    Address: 2001:db8::2/64
+#        |  |      Route:   64:ff9b::/96 via 2001:db8::1
+#        +--|---
+#           | VLAN 3
+#        +--|---
+#        | eth2    Address: 2001:db8::1/64
+# Router |
+#        | nat64   Address: 64:ff9b::1/128
+#        |         Route:   64:ff9b::/96
+#        |         Address: 192.0.2.0/32
+#        |         Route:   192.0.2.0/24
+#        |
+#        | eth1    Address: 100.64.0.1/24
+#        +--|---
+#           | VLAN 2
+#        +--|---
+# Server | eth1    Address: 100.64.0.2/24
+#        |         Route:   192.0.2.0/24 via 100.64.0.1
+#        +------
+
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "tayga";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes = {
+    # The server is configured with static IPv4 addresses. RFC 6052 Section 3.1
+    # disallows the mapping of non-global IPv4 addresses like RFC 1918 into the
+    # Well-Known Prefix 64:ff9b::/96. TAYGA also does not allow the mapping of
+    # documentation space (RFC 5737). To circumvent this, 100.64.0.2/24 from
+    # RFC 6589 (Carrier Grade NAT) is used here.
+    # To reach the IPv4 address pool of the NAT64 gateway, there is a static
+    # route configured. In normal cases, where the router would also source NAT
+    # the pool addresses to one IPv4 addresses, this would not be needed.
+    server = {
+      virtualisation.vlans = [
+        2 # towards router
+      ];
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "100.64.0.2/24"
+          ];
+          routes = [
+            { routeConfig = { Destination = "192.0.2.0/24"; Gateway = "100.64.0.1"; }; }
+          ];
+        };
+      };
+    };
+
+    # The router is configured with static IPv4 addresses towards the server
+    # and IPv6 addresses towards the client. For NAT64, the Well-Known prefix
+    # 64:ff9b::/96 is used. NAT64 is done with TAYGA which provides the
+    # tun-interface nat64 and does the translation over it. The IPv6 packets
+    # are sent to this interfaces and received as IPv4 packets and vice versa.
+    # As TAYGA only translates IPv6 addresses to dedicated IPv4 addresses, it
+    # needs a pool of IPv4 addresses which must be at least as big as the
+    # expected amount of clients. In this test, the packets from the pool are
+    # directly routed towards the client. In normal cases, there would be a
+    # second source NAT44 to map all clients behind one IPv4 address.
+    router_systemd = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        useNetworkd = true;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    router_nixos = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    # The client is configured with static IPv6 addresses. It has also a static
+    # route for the NAT64 IP space where the IPv4 addresses are mapped in. In
+    # normal cases, there would be only a default route.
+    client = {
+      virtualisation.vlans = [
+        3 # towards router
+      ];
+
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "2001:db8::2/64"
+          ];
+          routes = [
+            { routeConfig = { Destination = "64:ff9b::/96"; Gateway = "2001:db8::1"; }; }
+          ];
+        };
+      };
+      environment.systemPackages = [ pkgs.mtr ];
+    };
+  };
+
+  testScript = ''
+    # start client and server
+    for machine in client, server:
+      machine.wait_for_unit("network-online.target")
+      machine.log(machine.execute("ip addr")[1])
+      machine.log(machine.execute("ip route")[1])
+      machine.log(machine.execute("ip -6 route")[1])
+
+    # test systemd-networkd and nixos-scripts based router
+    for router in router_systemd, router_nixos:
+      router.start()
+      router.wait_for_unit("network-online.target")
+      router.wait_for_unit("tayga.service")
+      router.log(machine.execute("ip addr")[1])
+      router.log(machine.execute("ip route")[1])
+      router.log(machine.execute("ip -6 route")[1])
+
+      with subtest("Wait for tayga"):
+        router.wait_for_unit("tayga.service")
+
+      with subtest("Test ICMP"):
+        client.wait_until_succeeds("ping -c 3 64:ff9b::100.64.0.2 >&2")
+
+      with subtest("Test ICMP and show a traceroute"):
+        client.wait_until_succeeds("mtr --show-ips --report-wide 64:ff9b::100.64.0.2 >&2")
+
+      router.log(router.execute("systemd-analyze security tayga.service")[1])
+      router.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/teleport.nix b/nixpkgs/nixos/tests/teleport.nix
index 34bf1bc0c70d..cdf762b12844 100644
--- a/nixpkgs/nixos/tests/teleport.nix
+++ b/nixpkgs/nixos/tests/teleport.nix
@@ -1,18 +1,28 @@
 { system ? builtins.currentSystem
 , config ? { }
 , pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
 
 let
-  minimal = { config, ... }: {
-    services.teleport.enable = true;
+  packages = with pkgs; {
+    "default" = teleport;
+    "11" = teleport_11;
   };
 
-  client = { config, ... }: {
+  minimal = package: {
     services.teleport = {
       enable = true;
+      inherit package;
+    };
+  };
+
+  client = package: {
+    services.teleport = {
+      enable = true;
+      inherit package;
       settings = {
         teleport = {
           nodename = "client";
@@ -37,9 +47,10 @@ let
     }];
   };
 
-  server = { config, ... }: {
+  server = package: {
     services.teleport = {
       enable = true;
+      inherit package;
       settings = {
         teleport = {
           nodename = "server";
@@ -64,36 +75,41 @@ let
     };
   };
 in
-{
-  minimal = makeTest {
-    # minimal setup should always work
-    name = "teleport-minimal-setup";
-    meta.maintainers = with pkgs.lib.maintainers; [ ymatsiuk ];
-    nodes = { inherit minimal; };
+lib.concatMapAttrs
+  (name: package: {
+    "minimal_${name}" = makeTest {
+      # minimal setup should always work
+      name = "teleport-minimal-setup";
+      meta.maintainers = with pkgs.lib.maintainers; [ justinas ];
+      nodes.minimal = minimal package;
 
-    testScript = ''
-      minimal.wait_for_open_port(3025)
-      minimal.wait_for_open_port(3080)
-      minimal.wait_for_open_port(3022)
-    '';
-  };
+      testScript = ''
+        minimal.wait_for_open_port(3025)
+        minimal.wait_for_open_port(3080)
+        minimal.wait_for_open_port(3022)
+      '';
+    };
 
-  basic = makeTest {
-    # basic server and client test
-    name = "teleport-server-client";
-    meta.maintainers = with pkgs.lib.maintainers; [ ymatsiuk ];
-    nodes = { inherit server client; };
+    "basic_${name}" = makeTest {
+      # basic server and client test
+      name = "teleport-server-client";
+      meta.maintainers = with pkgs.lib.maintainers; [ justinas ];
+      nodes = {
+        server = server package;
+        client = client package;
+      };
 
-    testScript = ''
-      with subtest("teleport ready"):
-          server.wait_for_open_port(3025)
-          client.wait_for_open_port(3022)
+      testScript = ''
+        with subtest("teleport ready"):
+            server.wait_for_open_port(3025)
+            client.wait_for_open_port(3022)
 
-      with subtest("check applied configuration"):
-          server.wait_until_succeeds("tctl get nodes --format=json | ${pkgs.jq}/bin/jq -e '.[] | select(.spec.hostname==\"client\") | .metadata.labels.role==\"client\"'")
-          server.wait_for_open_port(3000)
-          client.succeed("journalctl -u teleport.service --grep='DEBU'")
-          server.succeed("journalctl -u teleport.service --grep='Starting teleport in insecure mode.'")
-    '';
-  };
-}
+        with subtest("check applied configuration"):
+            server.wait_until_succeeds("tctl get nodes --format=json | ${pkgs.jq}/bin/jq -e '.[] | select(.spec.hostname==\"client\") | .metadata.labels.role==\"client\"'")
+            server.wait_for_open_port(3000)
+            client.succeed("journalctl -u teleport.service --grep='DEBU'")
+            server.succeed("journalctl -u teleport.service --grep='Starting teleport in insecure mode.'")
+      '';
+    };
+  })
+  packages
diff --git a/nixpkgs/nixos/tests/terminal-emulators.nix b/nixpkgs/nixos/tests/terminal-emulators.nix
index c724608b9155..4269d05056d8 100644
--- a/nixpkgs/nixos/tests/terminal-emulators.nix
+++ b/nixpkgs/nixos/tests/terminal-emulators.nix
@@ -23,8 +23,9 @@ with pkgs.lib;
 let tests = {
       alacritty.pkg = p: p.alacritty;
 
-      contour.pkg = p: p.contour;
-      contour.cmd = "contour $command";
+      # times out after spending many hours
+      #contour.pkg = p: p.contour;
+      #contour.cmd = "contour $command";
 
       cool-retro-term.pkg = p: p.cool-retro-term;
       cool-retro-term.colourTest = false; # broken by gloss effect
@@ -103,7 +104,8 @@ let tests = {
       wayst.pkg = p: p.wayst;
       wayst.pinkValue = "#FF0066";
 
-      wezterm.pkg = p: p.wezterm;
+      # times out after spending many hours
+      #wezterm.pkg = p: p.wezterm;
 
       xfce4-terminal.pkg = p: p.xfce.xfce4-terminal;
 
diff --git a/nixpkgs/nixos/tests/thelounge.nix b/nixpkgs/nixos/tests/thelounge.nix
index e9b85685bf2d..8d5a37d46c46 100644
--- a/nixpkgs/nixos/tests/thelounge.nix
+++ b/nixpkgs/nixos/tests/thelounge.nix
@@ -1,4 +1,6 @@
 import ./make-test-python.nix {
+  name = "thelounge";
+
   nodes = {
     private = { config, pkgs, ... }: {
       services.thelounge = {
diff --git a/nixpkgs/nixos/tests/timescaledb.nix b/nixpkgs/nixos/tests/timescaledb.nix
new file mode 100644
index 000000000000..00a7f9af09fb
--- /dev/null
+++ b/nixpkgs/nixos/tests/timescaledb.nix
@@ -0,0 +1,93 @@
+# mostly copied from ./postgresql.nix as it seemed unapproriate to
+# test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION timescaledb;
+    CREATE EXTENSION timescaledb_toolkit;
+
+    CREATE TABLE sth (
+      time TIMESTAMPTZ NOT NULL,
+      value DOUBLE PRECISION
+    );
+
+    SELECT create_hypertable('sth', 'time');
+
+    INSERT INTO sth (time, value) VALUES
+    ('2003-04-12 04:05:06 America/New_York', 1.0),
+    ('2003-04-12 04:05:07 America/New_York', 2.0),
+    ('2003-04-12 04:05:08 America/New_York', 3.0),
+    ('2003-04-12 04:05:09 America/New_York', 4.0),
+    ('2003-04-12 04:05:10 America/New_York', 5.0)
+    ;
+
+    WITH t AS (
+      SELECT
+        time_bucket('1 day'::interval, time) AS dt,
+        stats_agg(value) AS stats
+      FROM sth
+      GROUP BY time_bucket('1 day'::interval, time)
+    )
+    SELECT
+      average(stats)
+    FROM t;
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ typetetris ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = with postgresql-package.pkgs; [
+            timescaledb
+            timescaledb_toolkit
+          ];
+          settings = { shared_preload_libraries = "timescaledb, timescaledb_toolkit"; };
+        };
+      };
+
+    testScript = ''
+      def check_count(statement, lines):
+          return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format(
+              statement, lines
+          )
+
+
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"):
+          machine.succeed(
+              "sudo -u postgres psql -f ${test-sql}"
+          )
+
+      machine.fail(check_count("SELECT * FROM sth;", 3))
+      machine.succeed(check_count("SELECT * FROM sth;", 5))
+      machine.fail(check_count("SELECT * FROM sth;", 4))
+
+      machine.shutdown()
+    '';
+
+  };
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12") postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions
diff --git a/nixpkgs/nixos/tests/tmate-ssh-server.nix b/nixpkgs/nixos/tests/tmate-ssh-server.nix
new file mode 100644
index 000000000000..e7f94db9bfcf
--- /dev/null
+++ b/nixpkgs/nixos/tests/tmate-ssh-server.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  inherit (import ./ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+
+  setUpPrivateKey = name: ''
+    ${name}.succeed(
+        "mkdir -p /root/.ssh",
+        "chown 700 /root/.ssh",
+        "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil",
+        "chown 600 /root/.ssh/id_snakeoil",
+    )
+    ${name}.wait_for_file("/root/.ssh/id_snakeoil")
+  '';
+
+  sshOpts = "-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentityFile=/root/.ssh/id_snakeoil";
+
+in
+{
+  name = "tmate-ssh-server";
+  nodes =
+    {
+      server = { ... }: {
+        services.tmate-ssh-server = {
+          enable = true;
+          port = 2223;
+        };
+      };
+      client = { ... }: {
+        environment.systemPackages = [ pkgs.tmate ];
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      };
+      client2 = { ... }: {
+        environment.systemPackages = [ pkgs.openssh ];
+      };
+    };
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("tmate-ssh-server.service")
+    server.wait_for_open_port(2223)
+    server.wait_for_file("/etc/tmate-ssh-server-keys/ssh_host_ed25519_key.pub")
+    server.wait_for_file("/etc/tmate-ssh-server-keys/ssh_host_rsa_key.pub")
+    server.succeed("tmate-client-config > /tmp/tmate.conf")
+    server.wait_for_file("/tmp/tmate.conf")
+
+    ${setUpPrivateKey "server"}
+    client.wait_for_unit("sshd.service")
+    client.wait_for_open_port(22)
+    server.succeed("scp ${sshOpts} /tmp/tmate.conf client:/tmp/tmate.conf")
+
+    client.wait_for_file("/tmp/tmate.conf")
+    client.send_chars("root\n")
+    client.sleep(2)
+    client.send_chars("tmate -f /tmp/tmate.conf\n")
+    client.sleep(2)
+    client.send_chars("q")
+    client.sleep(2)
+    client.send_chars("tmate display -p '#{tmate_ssh}' > /tmp/ssh_command\n")
+    client.wait_for_file("/tmp/ssh_command")
+    ssh_cmd = client.succeed("cat /tmp/ssh_command")
+
+    client2.succeed("mkdir -p ~/.ssh; ssh-keyscan -p 2223 server > ~/.ssh/known_hosts")
+    client2.send_chars("root\n")
+    client2.sleep(2)
+    client2.send_chars(ssh_cmd.strip() + "\n")
+    client2.sleep(2)
+    client2.send_chars("touch /tmp/client_2\n")
+
+    client.wait_for_file("/tmp/client_2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tor.nix b/nixpkgs/nixos/tests/tor.nix
index 71ec9df4641f..b55fbf91232c 100644
--- a/nixpkgs/nixos/tests/tor.nix
+++ b/nixpkgs/nixos/tests/tor.nix
@@ -1,15 +1,13 @@
-import ./make-test-python.nix ({ lib, ... }: with lib;
-
-{
+import ./make-test-python.nix ({ lib, ... }: {
   name = "tor";
-  meta.maintainers = with maintainers; [ joachifm ];
+  meta.maintainers = with lib.maintainers; [ joachifm ];
 
   nodes.client = { pkgs, ... }: {
     boot.kernelParams = [ "audit=0" "apparmor=0" "quiet" ];
     networking.firewall.enable = false;
     networking.useDHCP = false;
 
-    environment.systemPackages = with pkgs; [ netcat ];
+    environment.systemPackages = [ pkgs.netcat ];
     services.tor.enable = true;
     services.tor.client.enable = true;
     services.tor.settings.ControlPort = 9051;
diff --git a/nixpkgs/nixos/tests/tracee.nix b/nixpkgs/nixos/tests/tracee.nix
new file mode 100644
index 000000000000..8ec86ef091ef
--- /dev/null
+++ b/nixpkgs/nixos/tests/tracee.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tracee-integration";
+  meta.maintainers = pkgs.tracee.meta.maintainers;
+
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      # EventFilters/trace_only_events_from_new_containers and
+      # Test_EventFilters/trace_only_events_from_"dockerd"_binary_and_contain_it's_pid
+      # require docker/dockerd
+      virtualisation.docker.enable = true;
+
+      environment.systemPackages = with pkgs; [
+        # required by Test_EventFilters/trace_events_from_ls_and_which_binary_in_separate_scopes
+        which
+        # build the go integration tests as a binary
+        (tracee.overrideAttrs (oa: {
+          pname = oa.pname + "-integration";
+          postPatch = oa.postPatch or "" + ''
+            # prepare tester.sh (which will be embedded in the test binary)
+            patchShebangs tests/integration/tester.sh
+
+            # fix the test to look at nixos paths for running programs
+            substituteInPlace tests/integration/integration_test.go \
+              --replace "bin=/usr/bin/" "comm=" \
+              --replace "binary=/usr/bin/" "comm=" \
+              --replace "/usr/bin/dockerd" "dockerd" \
+              --replace "/usr/bin" "/run/current-system/sw/bin"
+          '';
+          nativeBuildInputs = oa.nativeBuildInputs or [ ] ++ [ makeWrapper ];
+          buildPhase = ''
+            runHook preBuild
+            # just build the static lib we need for the go test binary
+            make $makeFlags ''${enableParallelBuilding:+-j$NIX_BUILD_CORES} bpf-core ./dist/btfhub
+
+            # then compile the tests to be ran later
+            CGO_LDFLAGS="$(pkg-config --libs libbpf)" go test -tags core,ebpf,integration -p 1 -c -o $GOPATH/tracee-integration ./tests/integration/...
+            runHook postBuild
+          '';
+          doCheck = false;
+          outputs = [ "out" ];
+          installPhase = ''
+            mkdir -p $out/bin
+            mv $GOPATH/tracee-integration $out/bin/
+          '';
+          doInstallCheck = false;
+        }))
+      ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("docker.service")
+
+    with subtest("run integration tests"):
+      # EventFilters/trace_only_events_from_new_containers also requires a container called "alpine"
+      machine.succeed('tar c -C ${pkgs.pkgsStatic.busybox} . | docker import - alpine --change "ENTRYPOINT [\"sleep\"]"')
+
+      # Test_EventFilters/trace_event_set_in_a_specific_scope expects to be in a dir that includes "integration"
+      print(machine.succeed(
+        'mkdir /tmp/integration',
+        'cd /tmp/integration && tracee-integration -test.v'
+      ))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/traefik.nix b/nixpkgs/nixos/tests/traefik.nix
index 989ec390c060..ce808e6ec95a 100644
--- a/nixpkgs/nixos/tests/traefik.nix
+++ b/nixpkgs/nixos/tests/traefik.nix
@@ -52,10 +52,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             sendAnonymousUsage = false;
           };
 
-          entryPoints.web.address = ":80";
+          entryPoints.web.address = ":\${HTTP_PORT}";
 
           providers.docker.exposedByDefault = false;
         };
+        environmentFiles = [(pkgs.writeText "traefik.env" ''
+          HTTP_PORT=80
+        '')];
       };
 
       systemd.services.simplehttp = {
diff --git a/nixpkgs/nixos/tests/trafficserver.nix b/nixpkgs/nixos/tests/trafficserver.nix
index 983ded4f172e..e4557c6c50e5 100644
--- a/nixpkgs/nixos/tests/trafficserver.nix
+++ b/nixpkgs/nixos/tests/trafficserver.nix
@@ -172,6 +172,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         assert re.fullmatch(expected, out) is not None, "no matching logs"
 
         out = json.loads(ats.succeed(f"traffic_logstats -jf {access_log_path}"))
+        assert isinstance(out, dict)
         assert out["total"]["error.total"]["req"] == "0", "unexpected log stat"
   '';
 })
diff --git a/nixpkgs/nixos/tests/turbovnc-headless-server.nix b/nixpkgs/nixos/tests/turbovnc-headless-server.nix
index 1dbf9297c813..a155f9f907b2 100644
--- a/nixpkgs/nixos/tests/turbovnc-headless-server.nix
+++ b/nixpkgs/nixos/tests/turbovnc-headless-server.nix
@@ -26,7 +26,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     # So that we can ssh into the VM, see e.g.
     # http://blog.patapon.info/nixos-local-vm/#accessing-the-vm-with-ssh
     services.openssh.enable = true;
-    services.openssh.permitRootLogin = "yes";
+    services.openssh.settings.PermitRootLogin = "yes";
     users.extraUsers.root.password = "";
     users.mutableUsers = false;
   };
diff --git a/nixpkgs/nixos/tests/tuxguitar.nix b/nixpkgs/nixos/tests/tuxguitar.nix
index 037f489e5448..00833024bfea 100644
--- a/nixpkgs/nixos/tests/tuxguitar.nix
+++ b/nixpkgs/nixos/tests/tuxguitar.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "tuxguitar";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ asbachb ];
+    maintainers = [ ];
   };
 
   nodes.machine = { config, pkgs, ... }: {
diff --git a/nixpkgs/nixos/tests/txredisapi.nix b/nixpkgs/nixos/tests/txredisapi.nix
index 7c6b36a5c47d..47c2ba6d3749 100644
--- a/nixpkgs/nixos/tests/txredisapi.nix
+++ b/nixpkgs/nixos/tests/txredisapi.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       {
         services.redis.servers."".enable = true;
 
-        environment.systemPackages = with pkgs; [ (python38.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))];
+        environment.systemPackages = with pkgs; [ (python3.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))];
       };
   };
 
diff --git a/nixpkgs/nixos/tests/ulogd.nix b/nixpkgs/nixos/tests/ulogd.nix
new file mode 100644
index 000000000000..d351fdae7983
--- /dev/null
+++ b/nixpkgs/nixos/tests/ulogd.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "ulogd";
+
+  meta.maintainers = with lib.maintainers; [ p-h ];
+
+  nodes.machine = { ... }: {
+    networking.firewall.enable = false;
+    networking.nftables.enable = true;
+    networking.nftables.ruleset = ''
+      table inet filter {
+        chain input {
+          type filter hook input priority 0;
+          log group 2 accept
+        }
+
+        chain output {
+          type filter hook output priority 0; policy accept;
+          log group 2 accept
+        }
+
+        chain forward {
+          type filter hook forward priority 0; policy drop;
+          log group 2 accept
+        }
+
+      }
+    '';
+    services.ulogd = {
+      enable = true;
+      settings = {
+        global = {
+          logfile = "/var/log/ulogd.log";
+          stack = "log1:NFLOG,base1:BASE,pcap1:PCAP";
+        };
+
+        log1.group = 2;
+
+        pcap1 = {
+          file = "/var/log/ulogd.pcap";
+          sync = 1;
+        };
+      };
+    };
+
+    environment.systemPackages = with pkgs; [
+      tcpdump
+    ];
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("ulogd.service")
+    machine.wait_for_unit("network-online.target")
+
+    with subtest("Ulogd is running"):
+        machine.succeed("pgrep ulogd >&2")
+
+    # All packets show up twice in the logs
+    with subtest("Logs are collected"):
+        machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
+        machine.succeed("sleep 2")
+        machine.wait_until_succeeds("du /var/log/ulogd.pcap >&2")
+        _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
+        expected, actual = 5*2, len(echo_request_packets.splitlines())
+        assert expected == actual, f"Expected {expected} packets, got: {actual}"
+        _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
+        expected, actual = 5*2, len(echo_reply_packets.splitlines())
+        assert expected == actual, f"Expected {expected} packets, got: {actual}"
+
+    with subtest("Reloading service reopens log file"):
+        machine.succeed("mv /var/log/ulogd.pcap /var/log/old_ulogd.pcap")
+        machine.succeed("systemctl reload ulogd.service")
+        machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
+        machine.succeed("sleep 2")
+        _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
+        expected, actual = 5*2, len(echo_request_packets.splitlines())
+        assert expected == actual, f"Expected {expected} packets, got: {actual}"
+        _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
+        expected, actual = 5*2, len(echo_reply_packets.splitlines())
+        assert expected == actual, f"Expected {expected} packets, got: {actual}"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/unbound.nix b/nixpkgs/nixos/tests/unbound.nix
index 576287a9fe5d..f6732390b434 100644
--- a/nixpkgs/nixos/tests/unbound.nix
+++ b/nixpkgs/nixos/tests/unbound.nix
@@ -1,7 +1,7 @@
 /*
  Test that our unbound module indeed works as most users would expect.
  There are a few settings that we must consider when modifying the test. The
- ususal use-cases for unbound are
+ usual use-cases for unbound are
    * running a recursive DNS resolver on the local machine
    * running a recursive DNS resolver on the local machine, forwarding to a local DNS server via UDP/53 & TCP/53
    * running a recursive DNS resolver on the local machine, forwarding to a local DNS server via TCP/853 (DoT)
@@ -74,7 +74,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
         };
       };
 
-      # The resolver that knows that fowards (only) to the authoritative server
+      # The resolver that knows that forwards (only) to the authoritative server
       # and listens on UDP/53, TCP/53 & TCP/853.
       resolver = { lib, nodes, ... }: {
         imports = [ common ];
diff --git a/nixpkgs/nixos/tests/unifi.nix b/nixpkgs/nixos/tests/unifi.nix
index 9dc7e5d04bd5..d371bafd6965 100644
--- a/nixpkgs/nixos/tests/unifi.nix
+++ b/nixpkgs/nixos/tests/unifi.nix
@@ -16,6 +16,8 @@ let
     };
 
     nodes.server = {
+      nixpkgs.config = config;
+
       services.unifi = {
         enable = true;
         unifiPackage = unifi;
diff --git a/nixpkgs/nixos/tests/upnp.nix b/nixpkgs/nixos/tests/upnp.nix
index 451c8607d0eb..af7cc1fe2413 100644
--- a/nixpkgs/nixos/tests/upnp.nix
+++ b/nixpkgs/nixos/tests/upnp.nix
@@ -47,7 +47,7 @@ in
 
       client1 =
         { pkgs, nodes, ... }:
-        { environment.systemPackages = [ pkgs.miniupnpc_2 pkgs.netcat ];
+        { environment.systemPackages = [ pkgs.miniupnpc pkgs.netcat ];
           virtualisation.vlans = [ 2 ];
           networking.defaultGateway = internalRouterAddress;
           networking.interfaces.eth1.ipv4.addresses = [
@@ -65,7 +65,7 @@ in
 
       client2 =
         { pkgs, ... }:
-        { environment.systemPackages = [ pkgs.miniupnpc_2 ];
+        { environment.systemPackages = [ pkgs.miniupnpc ];
           virtualisation.vlans = [ 1 ];
           networking.interfaces.eth1.ipv4.addresses = [
             { address = externalClient2Address; prefixLength = 24; }
diff --git a/nixpkgs/nixos/tests/uptime-kuma.nix b/nixpkgs/nixos/tests/uptime-kuma.nix
new file mode 100644
index 000000000000..00e2008a5257
--- /dev/null
+++ b/nixpkgs/nixos/tests/uptime-kuma.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "uptime-kuma";
+  meta.maintainers = with lib.maintainers; [ julienmalka ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.uptime-kuma.enable = true; };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("uptime-kuma.service")
+    machine.wait_for_open_port(3001)
+    machine.succeed("curl --fail http://localhost:3001/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/v2ray.nix b/nixpkgs/nixos/tests/v2ray.nix
index fb36ea8557d5..9eee962c64e4 100644
--- a/nixpkgs/nixos/tests/v2ray.nix
+++ b/nixpkgs/nixos/tests/v2ray.nix
@@ -20,7 +20,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: let
         port = 1081;
         listen = "127.0.0.1";
         protocol = "vmess";
-        settings.clients = [v2rayUser];
+        settings.clients = [ v2rayUser ];
       }
     ];
     outbounds = [
@@ -30,7 +30,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: let
         settings.vnext = [{
           address = "127.0.0.1";
           port = 1081;
-          users = [v2rayUser];
+          users = [ v2rayUser ];
         }];
       }
       {
@@ -49,6 +49,14 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: let
         inboundTag = "vmess_in";
         outboundTag = "direct";
       }
+
+      # Assert assets "geoip" and "geosite" are accessible.
+      {
+        type = "field";
+        ip = [ "geoip:private" ];
+        domain = [ "geosite:category-ads" ];
+        outboundTag = "direct";
+      }
     ];
   };
 
diff --git a/nixpkgs/nixos/tests/varnish.nix b/nixpkgs/nixos/tests/varnish.nix
new file mode 100644
index 000000000000..9dcdeec9d8c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/varnish.nix
@@ -0,0 +1,55 @@
+{
+  system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; }
+, package
+}:
+import ./make-test-python.nix ({ pkgs, ... }: let
+  testPath = pkgs.hello;
+in {
+  name = "varnish";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ajs124 ];
+  };
+
+  nodes = {
+    varnish = { config, pkgs, ... }: {
+        services.nix-serve = {
+          enable = true;
+        };
+
+        services.varnish = {
+          inherit package;
+          enable = true;
+          http_address = "0.0.0.0:80";
+          config = ''
+            vcl 4.0;
+
+            backend nix-serve {
+              .host = "127.0.0.1";
+              .port = "${toString config.services.nix-serve.port}";
+            }
+          '';
+        };
+
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        system.extraDependencies = [ testPath ];
+      };
+
+    client = { lib, ... }: {
+      nix.settings = {
+        require-sigs = false;
+        substituters = lib.mkForce [ "http://varnish" ];
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    varnish.wait_for_open_port(80)
+
+    client.wait_until_succeeds("curl -f http://varnish/nix-cache-info");
+
+    client.wait_until_succeeds("nix-store -r ${testPath}");
+    client.succeed("${testPath}/bin/hello");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vault-agent.nix b/nixpkgs/nixos/tests/vault-agent.nix
new file mode 100644
index 000000000000..dc86c829b67a
--- /dev/null
+++ b/nixpkgs/nixos/tests/vault-agent.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vault-agent";
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.vault-agent.instances.example.settings = {
+      vault.address = config.environment.variables.VAULT_ADDR;
+
+      auto_auth = [{
+        method = [{
+          type = "token_file";
+          config.token_file_path = pkgs.writeText "vault-token" config.environment.variables.VAULT_TOKEN;
+        }];
+      }];
+
+      template = [{
+        contents = ''
+          {{- with secret "secret/example" }}
+          {{ .Data.data.key }}"
+          {{- end }}
+        '';
+        perms = "0600";
+        destination = "/example";
+      }];
+    };
+
+    services.vault = {
+      enable = true;
+      dev = true;
+      devRootTokenID = config.environment.variables.VAULT_TOKEN;
+    };
+
+    environment = {
+      systemPackages = [ pkgs.vault ];
+      variables = {
+        VAULT_ADDR = "http://localhost:8200";
+        VAULT_TOKEN = "root";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("vault.service")
+    machine.wait_for_open_port(8200)
+
+    machine.wait_until_succeeds('vault kv put secret/example key=example')
+
+    machine.wait_for_unit("vault-agent-example.service")
+
+    machine.wait_for_file("/example")
+    machine.succeed('grep "example" /example')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vaultwarden.nix b/nixpkgs/nixos/tests/vaultwarden.nix
index 408019666da3..95d00c1d8ec1 100644
--- a/nixpkgs/nixos/tests/vaultwarden.nix
+++ b/nixpkgs/nixos/tests/vaultwarden.nix
@@ -87,6 +87,9 @@ let
                 testRunner = pkgs.writers.writePython3Bin "test-runner"
                   {
                     libraries = [ pkgs.python3Packages.selenium ];
+                    flakeIgnore = [
+                      "E501"
+                    ];
                   } ''
 
                   from selenium.webdriver.common.by import By
@@ -104,39 +107,43 @@ let
 
                   wait = WebDriverWait(driver, 10)
 
-                  wait.until(EC.title_contains("Create Account"))
+                  wait.until(EC.title_contains("Create account"))
 
-                  driver.find_element(By.CSS_SELECTOR, 'input#email').send_keys(
-                    '${userEmail}'
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys(
+                      '${userEmail}'
                   )
-                  driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
-                    'A Cat'
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys(
+                      'A Cat'
                   )
-                  driver.find_element(By.CSS_SELECTOR, 'input#masterPassword').send_keys(
-                    '${userPassword}'
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys(
+                      '${userPassword}'
                   )
-                  driver.find_element(By.CSS_SELECTOR, 'input#masterPasswordRetype').send_keys(
-                    '${userPassword}'
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys(
+                      '${userPassword}'
                   )
+                  if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected():
+                      driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click()
+
+                  driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click()
 
-                  driver.find_element(By.XPATH, "//button[contains(., 'Submit')]").click()
+                  wait.until_not(EC.title_contains("Create account"))
 
-                  wait.until_not(EC.title_contains("Create Account"))
+                  driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click()
 
-                  driver.find_element(By.CSS_SELECTOR, 'input#masterPassword').send_keys(
-                    '${userPassword}'
+                  driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys(
+                      '${userPassword}'
                   )
-                  driver.find_element(By.XPATH, "//button[contains(., 'Log In')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click()
 
-                  wait.until(EC.title_contains("Bitwarden Web Vault"))
+                  wait.until(EC.title_contains("Vaults"))
 
-                  driver.find_element(By.XPATH, "//button[contains(., 'Add Item')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click()
 
                   driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
-                    'secrets'
+                      'secrets'
                   )
                   driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys(
-                    '${storedPassword}'
+                      '${storedPassword}'
                   )
 
                   driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click()
@@ -161,7 +168,7 @@ let
       with subtest("configure the cli"):
           client.succeed("bw --nointeraction config server http://server")
 
-      with subtest("can't login to nonexistant account"):
+      with subtest("can't login to nonexistent account"):
           client.fail(
               "bw --nointeraction --raw login ${userEmail} ${userPassword}"
           )
diff --git a/nixpkgs/nixos/tests/vector.nix b/nixpkgs/nixos/tests/vector.nix
index ecf94e33ff17..a55eb4e012c5 100644
--- a/nixpkgs/nixos/tests/vector.nix
+++ b/nixpkgs/nixos/tests/vector.nix
@@ -21,7 +21,7 @@ with pkgs.lib;
               type = "file";
               inputs = [ "journald" ];
               path = "/var/lib/vector/logs.log";
-              encoding = { codec = "ndjson"; };
+              encoding = { codec = "json"; };
             };
           };
         };
@@ -31,7 +31,7 @@ with pkgs.lib;
     # ensure vector is forwarding the messages appropriately
     testScript = ''
       machine.wait_for_unit("vector.service")
-      machine.succeed("test -f /var/lib/vector/logs.log")
+      machine.wait_for_file("/var/lib/vector/logs.log")
     '';
   };
 }
diff --git a/nixpkgs/nixos/tests/vengi-tools.nix b/nixpkgs/nixos/tests/vengi-tools.nix
index 5bc8d72c7723..fd7567991487 100644
--- a/nixpkgs/nixos/tests/vengi-tools.nix
+++ b/nixpkgs/nixos/tests/vengi-tools.nix
@@ -20,10 +20,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       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
+      # Let the window load fully
       machine.sleep(15)
-      machine.wait_for_text("Solid")
       machine.screenshot("screen")
     '';
 })
diff --git a/nixpkgs/nixos/tests/vikunja.nix b/nixpkgs/nixos/tests/vikunja.nix
index 2f6c4c1f4661..2660aa9767ca 100644
--- a/nixpkgs/nixos/tests/vikunja.nix
+++ b/nixpkgs/nixos/tests/vikunja.nix
@@ -26,6 +26,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         };
         frontendScheme = "http";
         frontendHostname = "localhost";
+        port = 9090;
       };
       services.postgresql = {
         enable = true;
@@ -52,8 +53,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       vikunjaSqlite.succeed("curl --fail http://localhost")
 
       vikunjaPostgresql.wait_for_unit("vikunja-api.service")
-      vikunjaPostgresql.wait_for_open_port(3456)
-      vikunjaPostgresql.succeed("curl --fail http://localhost:3456/api/v1/info")
+      vikunjaPostgresql.wait_for_open_port(9090)
+      vikunjaPostgresql.succeed("curl --fail http://localhost:9090/api/v1/info")
 
       vikunjaPostgresql.wait_for_unit("nginx.service")
       vikunjaPostgresql.wait_for_open_port(80)
diff --git a/nixpkgs/nixos/tests/virtualbox.nix b/nixpkgs/nixos/tests/virtualbox.nix
index 1c1b0dac7f37..1ebd99f5d75d 100644
--- a/nixpkgs/nixos/tests/virtualbox.nix
+++ b/nixpkgs/nixos/tests/virtualbox.nix
@@ -28,8 +28,8 @@ let
       messagebus:x:1:
       EOF
 
-      "${pkgs.dbus.daemon}/bin/dbus-daemon" --fork \
-        --config-file="${pkgs.dbus.daemon}/share/dbus-1/system.conf"
+      "${pkgs.dbus}/bin/dbus-daemon" --fork \
+        --config-file="${pkgs.dbus}/share/dbus-1/system.conf"
 
       ${guestAdditions}/bin/VBoxService
       ${(attrs.vmScript or (const "")) pkgs}
@@ -386,7 +386,7 @@ let
     '';
 
     meta = with pkgs.lib.maintainers; {
-      maintainers = [ aszlig cdepillabout ];
+      maintainers = [ aszlig ];
     };
   };
 
diff --git a/nixpkgs/nixos/tests/vscodium.nix b/nixpkgs/nixos/tests/vscodium.nix
index 3bdb99947a40..3eda8b6cfb20 100644
--- a/nixpkgs/nixos/tests/vscodium.nix
+++ b/nixpkgs/nixos/tests/vscodium.nix
@@ -33,13 +33,6 @@ let
       };
       enableOCR = true;
 
-      # testScriptWithTypes:55: error: Item "function" of
-      # "Union[Callable[[Callable[..., Any]], ContextManager[Any]], ContextManager[Any]]"
-      # has no attribute "__enter__"
-      #     with codium_running:
-      #          ^
-      skipTypeCheck = true;
-
       testScript = ''
         @polling_condition
         def codium_running():
@@ -49,11 +42,11 @@ let
         start_all()
 
         machine.wait_for_unit('graphical.target')
-        machine.wait_until_succeeds('pgrep -x codium')
 
-        with codium_running:
+        codium_running.wait() # type: ignore[union-attr]
+        with codium_running: # type: ignore[union-attr]
             # Wait until vscodium is visible. "File" is in the menu bar.
-            machine.wait_for_text('Get Started')
+            machine.wait_for_text('Welcome')
             machine.screenshot('start_screen')
 
             test_string = 'testfile'
@@ -70,15 +63,15 @@ let
 
             # Save the file
             machine.send_key('ctrl-s')
-            machine.wait_for_text('Save')
+            machine.wait_for_text('(Save|Desktop|alice|Size)')
             machine.screenshot('save_window')
             machine.send_key('ret')
 
             # (the default filename is the first line of the file)
             machine.wait_for_file(f'/home/alice/{test_string}')
 
-        machine.send_key('ctrl-q')
-        machine.wait_until_fails('pgrep -x codium')
+        # machine.send_key('ctrl-q')
+        # machine.wait_until_fails('pgrep -x codium')
       '';
     });
 
diff --git a/nixpkgs/nixos/tests/warzone2100.nix b/nixpkgs/nixos/tests/warzone2100.nix
new file mode 100644
index 000000000000..568e04a46999
--- /dev/null
+++ b/nixpkgs/nixos/tests/warzone2100.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "warzone2100";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.warzone2100 ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("warzone2100 >&2 &")
+      machine.wait_for_window("Warzone 2100")
+      machine.wait_for_text(r"(Single Player|Multi Player|Tutorial|Options|Quit Game)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/healthchecks.nix b/nixpkgs/nixos/tests/web-apps/healthchecks.nix
index 41374f5e314b..41c40cd5dd8d 100644
--- a/nixpkgs/nixos/tests/web-apps/healthchecks.nix
+++ b/nixpkgs/nixos/tests/web-apps/healthchecks.nix
@@ -33,10 +33,10 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: {
         )
 
     with subtest("Manage script works"):
-        # Should fail if not called by healthchecks user
-        machine.fail("echo 'print(\"foo\")' | healthchecks-manage help")
-
         # "shell" sucommand should succeed, needs python in PATH.
         assert "foo\n" == machine.succeed("echo 'print(\"foo\")' | sudo -u healthchecks healthchecks-manage shell")
+
+        # Shouldn't fail if not called by healthchecks user
+        assert "foo\n" == machine.succeed("echo 'print(\"foo\")' | healthchecks-manage shell")
   '';
 })
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon.nix b/nixpkgs/nixos/tests/web-apps/mastodon.nix
deleted file mode 100644
index 279a1c59169f..000000000000
--- a/nixpkgs/nixos/tests/web-apps/mastodon.nix
+++ /dev/null
@@ -1,170 +0,0 @@
-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
-  '';
-
-  hosts = ''
-    192.168.2.10 ca.local
-    192.168.2.11 mastodon.local
-  '';
-
-in
-{
-  name = "mastodon";
-  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin ];
-
-  nodes = {
-    ca = { pkgs, ... }: {
-      networking = {
-        interfaces.eth1 = {
-          ipv4.addresses = [
-            { address = "192.168.2.10"; prefixLength = 24; }
-          ];
-        };
-        extraHosts = hosts;
-      };
-      services.step-ca = {
-        enable = true;
-        address = "0.0.0.0";
-        port = 8443;
-        openFirewall = true;
-        intermediatePasswordFile = "${test-certificates}/intermediate-password-file";
-        settings = {
-          dnsNames = [ "ca.local" ];
-          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";
-              }
-            ];
-          };
-        };
-      };
-    };
-
-    server = { pkgs, ... }: {
-      networking = {
-        interfaces.eth1 = {
-          ipv4.addresses = [
-            { address = "192.168.2.11"; prefixLength = 24; }
-          ];
-        };
-        extraHosts = hosts;
-        firewall.allowedTCPPorts = [ 80 443 ];
-      };
-
-      security = {
-        acme = {
-          acceptTerms = true;
-          defaults.server = "https://ca.local:8443/acme/acme/directory";
-          defaults.email = "mastodon@mastodon.local";
-        };
-        pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
-      };
-
-      services.redis.servers.mastodon = {
-        enable = true;
-        bind = "127.0.0.1";
-        port = 31637;
-      };
-
-      services.mastodon = {
-        enable = true;
-        configureNginx = true;
-        localDomain = "mastodon.local";
-        enableUnixSocket = false;
-        redis = {
-          createLocally = true;
-          host = "127.0.0.1";
-          port = 31637;
-        };
-        database = {
-          createLocally = true;
-          host = "/run/postgresql";
-          port = 5432;
-        };
-        smtp = {
-          createLocally = false;
-          fromAddress = "mastodon@mastodon.local";
-        };
-        extraConfig = {
-          EMAIL_DOMAIN_ALLOWLIST = "example.com";
-        };
-      };
-    };
-
-    client = { pkgs, ... }: {
-      environment.systemPackages = [ pkgs.jq ];
-      networking = {
-        interfaces.eth1 = {
-          ipv4.addresses = [
-            { address = "192.168.2.12"; prefixLength = 24; }
-          ];
-        };
-        extraHosts = hosts;
-      };
-
-      security = {
-        pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
-      };
-    };
-  };
-
-  testScript = ''
-    start_all()
-
-    ca.wait_for_unit("step-ca.service")
-    ca.wait_for_open_port(8443)
-
-    server.wait_for_unit("nginx.service")
-    server.wait_for_unit("redis-mastodon.service")
-    server.wait_for_unit("postgresql.service")
-    server.wait_for_unit("mastodon-sidekiq.service")
-    server.wait_for_unit("mastodon-streaming.service")
-    server.wait_for_unit("mastodon-web.service")
-    server.wait_for_open_port(55000)
-    server.wait_for_open_port(55001)
-
-    # Check Mastodon version from remote client
-    client.succeed("curl --fail https://mastodon.local/api/v1/instance | jq -r '.version' | grep '${pkgs.mastodon.version}'")
-
-    # Check using admin CLI
-    # Check Mastodon version
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl version' | grep '${pkgs.mastodon.version}'")
-
-    # Manage accounts
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks add example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks list' | grep 'example.com'")
-    server.fail("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks list' | grep 'mastodon.local'")
-    server.fail("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts create alice --email=alice@example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks remove example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts create bob --email=bob@example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts approve bob'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts delete bob'")
-
-    # Manage IP access
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks add 192.168.0.0/16 --severity=no_access'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks export' | grep '192.168.0.0/16'")
-    server.fail("su - mastodon -s /bin/sh -c 'mastodon-env tootctl p_blocks export' | grep '172.16.0.0/16'")
-    client.fail("curl --fail https://mastodon.local/about")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks remove 192.168.0.0/16'")
-    client.succeed("curl --fail https://mastodon.local/about")
-
-    ca.shutdown()
-    server.shutdown()
-    client.shutdown()
-  '';
-})
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/default.nix b/nixpkgs/nixos/tests/web-apps/mastodon/default.nix
new file mode 100644
index 000000000000..411ebfcd731b
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem, handleTestOn }:
+let
+  supportedSystems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
+
+in
+{
+  standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
+  remote-postgresql = handleTestOn supportedSystems ./remote-postgresql.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/remote-postgresql.nix b/nixpkgs/nixos/tests/web-apps/mastodon/remote-postgresql.nix
new file mode 100644
index 000000000000..2fd3983e13ec
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/remote-postgresql.nix
@@ -0,0 +1,160 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=mastodon.local' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+
+  hosts = ''
+    192.168.2.103 mastodon.local
+  '';
+
+in
+{
+  name = "mastodon-remote-postgresql";
+  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin turion ];
+
+  nodes = {
+    database = {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.102"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 5432 ];
+      };
+
+      services.postgresql = {
+        enable = true;
+        enableTCPIP = true;
+        authentication = ''
+          hostnossl mastodon_local mastodon_test 192.168.2.201/32 md5
+        '';
+        initialScript = pkgs.writeText "postgresql_init.sql" ''
+          CREATE ROLE mastodon_test LOGIN PASSWORD 'SoDTZcISc3f1M1LJsRLT';
+          CREATE DATABASE mastodon_local TEMPLATE template0 ENCODING UTF8;
+          GRANT ALL PRIVILEGES ON DATABASE mastodon_local TO mastodon_test;
+        '';
+      };
+    };
+
+    nginx = {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.103"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 80 443 ];
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts."mastodon.local" = {
+          root = "/var/empty";
+          forceSSL = true;
+          enableACME = pkgs.lib.mkForce false;
+          sslCertificate = "${cert pkgs}/cert.pem";
+          sslCertificateKey = "${cert pkgs}/key.pem";
+          locations."/" = {
+            tryFiles = "$uri @proxy";
+          };
+          locations."@proxy" = {
+            proxyPass = "http://192.168.2.201:55001";
+            proxyWebsockets = true;
+          };
+          locations."/api/v1/streaming/" = {
+            proxyPass = "http://192.168.2.201:55002";
+            proxyWebsockets = true;
+          };
+        };
+      };
+    };
+
+    server = { pkgs, ... }: {
+      virtualisation.memorySize = 2048;
+
+      environment = {
+        etc = {
+          "mastodon/password-posgressql-db".text = ''
+            SoDTZcISc3f1M1LJsRLT
+          '';
+        };
+      };
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.201"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 55001 55002 ];
+      };
+
+      services.mastodon = {
+        enable = true;
+        configureNginx = false;
+        localDomain = "mastodon.local";
+        enableUnixSocket = false;
+        database = {
+          createLocally = false;
+          host = "192.168.2.102";
+          port = 5432;
+          name = "mastodon_local";
+          user = "mastodon_test";
+          passwordFile = "/etc/mastodon/password-posgressql-db";
+        };
+        smtp = {
+          createLocally = false;
+          fromAddress = "mastodon@mastodon.local";
+        };
+        extraConfig = {
+          BIND = "0.0.0.0";
+          EMAIL_DOMAIN_ALLOWLIST = "example.com";
+          RAILS_SERVE_STATIC_FILES = "true";
+          TRUSTED_PROXY_IP = "192.168.2.103";
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.202"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+    };
+  };
+
+  testScript = import ./script.nix {
+    inherit pkgs;
+    extraInit = ''
+      nginx.wait_for_unit("nginx.service")
+      nginx.wait_for_open_port(443)
+      database.wait_for_unit("postgresql.service")
+      database.wait_for_open_port(5432)
+    '';
+    extraShutdown = ''
+      nginx.shutdown()
+      database.shutdown()
+    '';
+  };
+})
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/script.nix b/nixpkgs/nixos/tests/web-apps/mastodon/script.nix
new file mode 100644
index 000000000000..a89b4b7480e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/script.nix
@@ -0,0 +1,54 @@
+{ pkgs
+, extraInit ? ""
+, extraShutdown ? ""
+}:
+
+''
+  start_all()
+
+  ${extraInit}
+
+  server.wait_for_unit("redis-mastodon.service")
+  server.wait_for_unit("mastodon-sidekiq-all.service")
+  server.wait_for_unit("mastodon-streaming.service")
+  server.wait_for_unit("mastodon-web.service")
+  server.wait_for_open_port(55000)
+  server.wait_for_open_port(55001)
+
+  # Check that mastodon-media-auto-remove is scheduled
+  server.succeed("systemctl status mastodon-media-auto-remove.timer")
+
+  # Check Mastodon version from remote client
+  client.succeed("curl --fail https://mastodon.local/api/v1/instance | jq -r '.version' | grep '${pkgs.mastodon.version}'")
+
+  # Check access from remote client
+  client.succeed("curl --fail https://mastodon.local/about | grep 'Mastodon hosted on mastodon.local'")
+  client.succeed("curl --fail $(curl https://mastodon.local/api/v1/instance 2> /dev/null | jq -r .thumbnail) --output /dev/null")
+
+  # Simple check tootctl commands
+  # Check Mastodon version
+  server.succeed("mastodon-tootctl version | grep '${pkgs.mastodon.version}'")
+
+  # Manage accounts
+  server.succeed("mastodon-tootctl email_domain_blocks add example.com")
+  server.succeed("mastodon-tootctl email_domain_blocks list | grep example.com")
+  server.fail("mastodon-tootctl email_domain_blocks list | grep mastodon.local")
+  server.fail("mastodon-tootctl accounts create alice --email=alice@example.com")
+  server.succeed("mastodon-tootctl email_domain_blocks remove example.com")
+  server.succeed("mastodon-tootctl accounts create bob --email=bob@example.com")
+  server.succeed("mastodon-tootctl accounts approve bob")
+  server.succeed("mastodon-tootctl accounts delete bob")
+
+  # Manage IP access
+  server.succeed("mastodon-tootctl ip_blocks add 192.168.0.0/16 --severity=no_access")
+  server.succeed("mastodon-tootctl ip_blocks export | grep 192.168.0.0/16")
+  server.fail("mastodon-tootctl ip_blocks export | grep 172.16.0.0/16")
+  client.fail("curl --fail https://mastodon.local/about")
+  server.succeed("mastodon-tootctl ip_blocks remove 192.168.0.0/16")
+  client.succeed("curl --fail https://mastodon.local/about")
+
+  server.shutdown()
+  client.shutdown()
+
+  ${extraShutdown}
+''
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/standard.nix b/nixpkgs/nixos/tests/web-apps/mastodon/standard.nix
new file mode 100644
index 000000000000..14311afea3f7
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/standard.nix
@@ -0,0 +1,92 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=mastodon.local' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+
+  hosts = ''
+    192.168.2.101 mastodon.local
+  '';
+
+in
+{
+  name = "mastodon-standard";
+  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin turion ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+
+      virtualisation.memorySize = 2048;
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.101"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 80 443 ];
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+
+      services.redis.servers.mastodon = {
+        enable = true;
+        bind = "127.0.0.1";
+        port = 31637;
+      };
+
+      services.mastodon = {
+        enable = true;
+        configureNginx = true;
+        localDomain = "mastodon.local";
+        enableUnixSocket = false;
+        smtp = {
+          createLocally = false;
+          fromAddress = "mastodon@mastodon.local";
+        };
+        extraConfig = {
+          EMAIL_DOMAIN_ALLOWLIST = "example.com";
+        };
+      };
+
+      services.nginx = {
+        virtualHosts."mastodon.local" = {
+          enableACME = pkgs.lib.mkForce false;
+          sslCertificate = "${cert pkgs}/cert.pem";
+          sslCertificateKey = "${cert pkgs}/key.pem";
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.102"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+    };
+  };
+
+  testScript = import ./script.nix {
+    inherit pkgs;
+    extraInit = ''
+      server.wait_for_unit("nginx.service")
+      server.wait_for_open_port(443)
+      server.wait_for_unit("postgresql.service")
+      server.wait_for_open_port(5432)
+    '';
+  };
+})
diff --git a/nixpkgs/nixos/tests/web-apps/monica.nix b/nixpkgs/nixos/tests/web-apps/monica.nix
new file mode 100644
index 000000000000..29f5cb85bb13
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/monica.nix
@@ -0,0 +1,33 @@
+import ../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs.runCommand "selfSignedCerts" { nativeBuildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=localhost' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+in
+{
+  name = "monica";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.monica = {
+        enable = true;
+        hostname = "localhost";
+        appKeyFile = "${pkgs.writeText "keyfile" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}";
+        nginx = {
+          forceSSL = true;
+          sslCertificate = "${cert}/cert.pem";
+          sslCertificateKey = "${cert}/key.pem";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("monica-setup.service")
+    machine.wait_for_open_port(443)
+    machine.succeed("curl -k --fail https://localhost", timeout=10)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/netbox.nix b/nixpkgs/nixos/tests/web-apps/netbox.nix
index 35decdd49e87..30de74f1886c 100644
--- a/nixpkgs/nixos/tests/web-apps/netbox.nix
+++ b/nixpkgs/nixos/tests/web-apps/netbox.nix
@@ -1,21 +1,146 @@
-import ../make-test-python.nix ({ lib, pkgs, ... }: {
+let
+  ldapDomain = "example.org";
+  ldapSuffix = "dc=example,dc=org";
+
+  ldapRootUser = "admin";
+  ldapRootPassword = "foobar";
+
+  testUser = "alice";
+  testPassword = "verySecure";
+  testGroup = "netbox-users";
+in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: {
   name = "netbox";
 
   meta = with lib.maintainers; {
-    maintainers = [ n0emis ];
+    maintainers = [ minijackson n0emis ];
   };
 
-  nodes.machine = { ... }: {
+  nodes.machine = { config, ... }: {
     services.netbox = {
       enable = true;
+      package = netbox;
       secretKeyFile = pkgs.writeText "secret" ''
         abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
       '';
+
+      enableLdap = true;
+      ldapConfigPath = pkgs.writeText "ldap_config.py" ''
+        import ldap
+        from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+        AUTH_LDAP_SERVER_URI = "ldap://localhost/"
+
+        AUTH_LDAP_USER_SEARCH = LDAPSearch(
+            "ou=accounts,ou=posix,${ldapSuffix}",
+            ldap.SCOPE_SUBTREE,
+            "(uid=%(user)s)",
+        )
+
+        AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+            "ou=groups,ou=posix,${ldapSuffix}",
+            ldap.SCOPE_SUBTREE,
+            "(objectClass=posixGroup)",
+        )
+        AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+
+        # Mirror LDAP group assignments.
+        AUTH_LDAP_MIRROR_GROUPS = True
+
+        # For more granular permissions, we can map LDAP groups to Django groups.
+        AUTH_LDAP_FIND_GROUP_PERMS = True
+      '';
+    };
+
+    services.nginx = {
+      enable = true;
+
+      recommendedProxySettings = true;
+
+      virtualHosts.netbox = {
+        default = true;
+        locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
+        locations."/static/".alias = "/var/lib/netbox/static/";
+      };
+    };
+
+    # Adapted from the sssd-ldap NixOS test
+    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/lib/openldap/db";
+              olcSuffix = ldapSuffix;
+              olcRootDN = "cn=${ldapRootUser},${ldapSuffix}";
+              olcRootPW = ldapRootPassword;
+            };
+          };
+        };
+      };
+      declarativeContents = {
+        ${ldapSuffix} = ''
+          dn: ${ldapSuffix}
+          objectClass: top
+          objectClass: dcObject
+          objectClass: organization
+          o: ${ldapDomain}
+
+          dn: ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: ou=accounts,ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: uid=${testUser},ou=accounts,ou=posix,${ldapSuffix}
+          objectClass: person
+          objectClass: posixAccount
+          userPassword: ${testPassword}
+          homeDirectory: /home/${testUser}
+          uidNumber: 1234
+          gidNumber: 1234
+          cn: ""
+          sn: ""
+
+          dn: ou=groups,ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix}
+          objectClass: posixGroup
+          gidNumber: 2345
+          memberUid: ${testUser}
+        '';
+      };
     };
+
+    users.users.nginx.extraGroups = [ "netbox" ];
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
   };
 
-  testScript = ''
-    machine.start()
+  testScript = let
+    changePassword = pkgs.writeText "change-password.py" ''
+      from django.contrib.auth.models import User
+      u = User.objects.get(username='netbox')
+      u.set_password('netbox')
+      u.save()
+    '';
+  in ''
+    from typing import Any, Dict
+    import json
+
+    start_all()
     machine.wait_for_unit("netbox.target")
     machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
 
@@ -26,5 +151,167 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: {
 
     with subtest("Staticfiles are generated"):
         machine.succeed("test -e /var/lib/netbox/static/netbox.js")
+
+    with subtest("Superuser can be created"):
+        machine.succeed(
+            "netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com"
+        )
+        # Django doesn't have a "clean" way of inputting the password from the command line
+        machine.succeed("cat '${changePassword}' | netbox-manage shell")
+
+    machine.wait_for_unit("network.target")
+
+    with subtest("Home screen loads from nginx"):
+        machine.succeed(
+            "curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'"
+        )
+
+    with subtest("Staticfiles can be fetched"):
+        machine.succeed("curl -sSfL http://localhost/static/netbox.js")
+        machine.succeed("curl -sSfL http://localhost/static/docs/")
+
+    with subtest("Can interact with API"):
+        json.loads(
+            machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'")
+        )
+
+    def login(username: str, password: str):
+        encoded_data = json.dumps({"username": username, "password": password})
+        uri = "/users/tokens/provision/"
+        result = json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-X POST "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"'http://localhost/api{uri}' "
+                f"--data '{encoded_data}'"
+            )
+        )
+        return result["key"]
+
+    with subtest("Can login"):
+        auth_token = login("netbox", "netbox")
+
+    def get(uri: str):
+        return json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-H 'Accept: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                f"'http://localhost/api{uri}'"
+            )
+        )
+
+    def delete(uri: str):
+        return machine.succeed(
+            "curl -sSfL "
+            f"-X DELETE "
+            "-H 'Accept: application/json' "
+            f"-H 'Authorization: Token {auth_token}' "
+            f"'http://localhost/api{uri}'"
+        )
+
+
+    def data_request(uri: str, method: str, data: Dict[str, Any]):
+        encoded_data = json.dumps(data)
+        return json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                f"-X {method} "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                f"'http://localhost/api{uri}' "
+                f"--data '{encoded_data}'"
+            )
+        )
+
+    def post(uri: str, data: Dict[str, Any]):
+      return data_request(uri, "POST", data)
+
+    def patch(uri: str, data: Dict[str, Any]):
+      return data_request(uri, "PATCH", data)
+
+    with subtest("Can create objects"):
+        result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
+        site_id = result["id"]
+
+        # Example from:
+        # http://netbox.extra.cea.fr/static/docs/integrations/rest-api/#creating-a-new-object
+        post("/ipam/prefixes/", {"prefix": "192.0.2.0/24", "site": site_id})
+
+        result = post(
+            "/dcim/manufacturers/",
+            {"name": "Test manufacturer", "slug": "test-manufacturer"}
+        )
+        manufacturer_id = result["id"]
+
+        # Had an issue with device-types before NetBox 3.4.0
+        result = post(
+            "/dcim/device-types/",
+            {
+                "model": "Test device type",
+                "manufacturer": manufacturer_id,
+                "slug": "test-device-type",
+            },
+        )
+        device_type_id = result["id"]
+
+    with subtest("Can list objects"):
+        result = get("/dcim/sites/")
+
+        assert result["count"] == 1
+        assert result["results"][0]["id"] == site_id
+        assert result["results"][0]["name"] == "Test site"
+        assert result["results"][0]["description"] == ""
+
+        result = get("/dcim/device-types/")
+        assert result["count"] == 1
+        assert result["results"][0]["id"] == device_type_id
+        assert result["results"][0]["model"] == "Test device type"
+
+    with subtest("Can update objects"):
+        new_description = "Test site description"
+        patch(f"/dcim/sites/{site_id}/", {"description": new_description})
+        result = get(f"/dcim/sites/{site_id}/")
+        assert result["description"] == new_description
+
+    with subtest("Can delete objects"):
+        # Delete a device-type since no object depends on it
+        delete(f"/dcim/device-types/{device_type_id}/")
+
+        result = get("/dcim/device-types/")
+        assert result["count"] == 0
+
+    with subtest("Can use the GraphQL API"):
+        encoded_data = json.dumps({
+            "query": "query { prefix_list { prefix, site { id, description } } }",
+        })
+        result = json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                "'http://localhost/graphql/' "
+                f"--data '{encoded_data}'"
+            )
+        )
+
+        assert len(result["data"]["prefix_list"]) == 1
+        assert result["data"]["prefix_list"][0]["prefix"] == "192.0.2.0/24"
+        assert result["data"]["prefix_list"][0]["site"]["id"] == str(site_id)
+        assert result["data"]["prefix_list"][0]["site"]["description"] == new_description
+
+    with subtest("Can login with LDAP"):
+        machine.wait_for_unit("openldap.service")
+        login("alice", "${testPassword}")
+
+    with subtest("Can associate LDAP groups"):
+        result = get("/users/users/?username=${testUser}")
+
+        assert result["count"] == 1
+        assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
   '';
 })
diff --git a/nixpkgs/nixos/tests/web-apps/peering-manager.nix b/nixpkgs/nixos/tests/web-apps/peering-manager.nix
new file mode 100644
index 000000000000..56b7eebfadff
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/peering-manager.nix
@@ -0,0 +1,40 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "peering-manager";
+
+  meta = with lib.maintainers; {
+    maintainers = [ yuka ];
+  };
+
+  nodes.machine = { ... }: {
+    services.peering-manager = {
+      enable = true;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+    };
+  };
+
+  testScript = { nodes }: ''
+    machine.start()
+    machine.wait_for_unit("peering-manager.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit peering-manager --grep Listening")
+
+    print(machine.succeed(
+        "curl -sSfL http://[::1]:8001"
+    ))
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://[::1]:8001 | grep '<title>Home - Peering Manager</title>'"
+        )
+    with subtest("checks succeed"):
+        machine.succeed(
+            "systemctl stop peering-manager peering-manager-rq"
+        )
+        machine.succeed(
+            "sudo -u postgres psql -c 'ALTER USER \"peering-manager\" WITH SUPERUSER;'"
+        )
+        machine.succeed(
+            "cd ${nodes.machine.config.system.build.peeringManagerPkg}/opt/peering-manager ; peering-manager-manage test --no-input"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/peertube.nix b/nixpkgs/nixos/tests/web-apps/peertube.nix
index ecc45bff2e2c..0e5f39c08a02 100644
--- a/nixpkgs/nixos/tests/web-apps/peertube.nix
+++ b/nixpkgs/nixos/tests/web-apps/peertube.nix
@@ -41,6 +41,9 @@ import ../make-test-python.nix ({pkgs, ...}:
     server = { pkgs, ... }: {
       environment = {
         etc = {
+          "peertube/secrets-peertube".text = ''
+            063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee
+          '';
           "peertube/password-posgressql-db".text = ''
             0gUN0C1mgST6czvjZ8T9
           '';
@@ -67,6 +70,10 @@ import ../make-test-python.nix ({pkgs, ...}:
         localDomain = "peertube.local";
         enableWebHttps = false;
 
+        secrets = {
+          secretsFile = "/etc/peertube/secrets-peertube";
+        };
+
         database = {
           host = "192.168.2.10";
           name = "peertube_local";
diff --git a/nixpkgs/nixos/tests/web-apps/pixelfed/default.nix b/nixpkgs/nixos/tests/web-apps/pixelfed/default.nix
new file mode 100644
index 000000000000..4464ebe43486
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/pixelfed/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem, handleTestOn }:
+let
+  supportedSystems = [ "x86_64-linux" "i686-linux" ];
+
+in
+{
+  standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix b/nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix
new file mode 100644
index 000000000000..9260e27af960
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix
@@ -0,0 +1,38 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+{
+  name = "pixelfed-standard";
+  meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+      services.pixelfed = {
+        enable = true;
+        domain = "pixelfed.local";
+        # Configure NGINX.
+        nginx = {};
+        secretFile = (pkgs.writeText "secrets.env" ''
+          # Snakeoil secret, can be any random 32-chars secret via CSPRNG.
+          APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
+        '');
+        settings."FORCE_HTTPS_URLS" = false;
+      };
+    };
+  };
+
+  testScript = ''
+    # Wait for Pixelfed PHP pool
+    server.wait_for_unit("phpfpm-pixelfed.service")
+    # Wait for NGINX
+    server.wait_for_unit("nginx.service")
+    # Wait for HTTP port
+    server.wait_for_open_port(80)
+    # Access the homepage.
+    server.succeed("curl -H 'Host: pixelfed.local' http://localhost")
+    # Create an account
+    server.succeed("pixelfed-manage user:create --name=test --username=test --email=test@test.com --password=test")
+    # Create a OAuth token.
+    # TODO: figure out how to use it to send a image/toot
+    # server.succeed("pixelfed-manage passport:client --personal")
+    # server.succeed("curl -H 'Host: pixefed.local' -H 'Accept: application/json' -H 'Authorization: Bearer secret' -F'status'='test' http://localhost/api/v1/statuses")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/snipe-it.nix b/nixpkgs/nixos/tests/web-apps/snipe-it.nix
new file mode 100644
index 000000000000..123d7742056b
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/snipe-it.nix
@@ -0,0 +1,101 @@
+/*
+Snipe-IT NixOS test
+
+It covers the following scenario:
+- Installation
+- Backup and restore
+
+Scenarios NOT covered by this test (but perhaps in the future):
+- Sending and receiving emails
+*/
+{ pkgs, ... }: let
+  siteName = "NixOS Snipe-IT Test Instance";
+in {
+  name = "snipe-it";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ yayayayaka ];
+
+  nodes = {
+    snipeit = { ... }: {
+      services.snipe-it = {
+        enable = true;
+        appKeyFile = toString (pkgs.writeText "snipe-it-app-key" "uTqGUN5GUmUrh/zSAYmhyzRk62pnpXICyXv9eeITI8k=");
+        hostName = "localhost";
+        database.createLocally = true;
+        mail = {
+          driver = "smtp";
+          encryption = "tls";
+          host = "localhost";
+          port = 1025;
+          from.name = "Snipe-IT NixOS test";
+          from.address = "snipe-it@localhost";
+          replyTo.address = "snipe-it@localhost";
+          user = "snipe-it@localhost";
+          passwordFile = toString (pkgs.writeText "snipe-it-mail-pass" "a-secure-mail-password");
+        };
+      };
+    };
+  };
+
+  testScript = { nodes }: let
+    backupPath = "${nodes.snipeit.services.snipe-it.dataDir}/storage/app/backups";
+
+    # Snipe-IT has been installed successfully if the site name shows up on the login page
+    checkLoginPage = { shouldSucceed ? true }: ''
+      snipeit.${if shouldSucceed then "succeed" else "fail"}("""curl http://localhost/login | grep '${siteName}'""")
+    '';
+  in ''
+    start_all()
+
+    snipeit.wait_for_unit("nginx.service")
+    snipeit.wait_for_unit("snipe-it-setup.service")
+
+    # Create an admin user
+    snipeit.succeed(
+        """
+        snipe-it snipeit:create-admin \
+            --username="admin" \
+            --email="janedoe@localhost" \
+            --password="extremesecurepassword" \
+            --first_name="Jane" \
+            --last_name="Doe"
+        """
+    )
+
+    with subtest("Circumvent the pre-flight setup by just writing some settings into the database ourself"):
+        snipeit.succeed(
+            """
+            mysql -D ${nodes.snipeit.services.snipe-it.database.name} -e "INSERT INTO settings (id, user_id, site_name) VALUES ('1', '1', '${siteName}');"
+            """
+        )
+
+        # Usually these are generated during the pre-flight setup
+        snipeit.succeed("snipe-it passport:keys")
+
+
+    # Login page should now contain the configured site name
+    ${checkLoginPage {}}
+
+    with subtest("Test Backup and restore"):
+        snipeit.succeed("snipe-it snipeit:backup")
+
+        # One zip file should have been created
+        snipeit.succeed("""[ "$(ls -1 "${backupPath}" | wc -l)" -eq 1 ]""")
+
+        # Purge the state
+        snipeit.succeed("snipe-it migrate:fresh --force")
+
+        # Login page should disappear
+        ${checkLoginPage { shouldSucceed = false; }}
+
+        # Restore the state
+        snipeit.succeed(
+            """
+            snipe-it snipeit:restore --force $(find "${backupPath}/" -type f -name "*.zip")
+            """
+        )
+
+        # Login page should be back again
+        ${checkLoginPage {}}
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-apps/writefreely.nix b/nixpkgs/nixos/tests/web-apps/writefreely.nix
new file mode 100644
index 000000000000..ce614909706b
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/writefreely.nix
@@ -0,0 +1,44 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../../.. { inherit system config; } }:
+
+with import ../../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  writefreelyTest = { name, type }:
+    makeTest {
+      name = "writefreely-${name}";
+
+      nodes.machine = { config, pkgs, ... }: {
+        services.writefreely = {
+          enable = true;
+          host = "localhost:3000";
+          admin.name = "nixos";
+
+          database = {
+            inherit type;
+            createLocally = type == "mysql";
+            passwordFile = pkgs.writeText "db-pass" "pass";
+          };
+
+          settings.server.port = 3000;
+        };
+      };
+
+      testScript = ''
+        start_all()
+        machine.wait_for_unit("writefreely.service")
+        machine.wait_for_open_port(3000)
+        machine.succeed("curl --fail http://localhost:3000")
+      '';
+    };
+in {
+  sqlite = writefreelyTest {
+    name = "sqlite";
+    type = "sqlite3";
+  };
+  mysql = writefreelyTest {
+    name = "mysql";
+    type = "mysql";
+  };
+}
diff --git a/nixpkgs/nixos/tests/web-servers/agate.nix b/nixpkgs/nixos/tests/web-servers/agate.nix
index e364e134cfda..e8d789a9ca44 100644
--- a/nixpkgs/nixos/tests/web-servers/agate.nix
+++ b/nixpkgs/nixos/tests/web-servers/agate.nix
@@ -1,29 +1,27 @@
-import ../make-test-python.nix (
-  { pkgs, lib, ... }:
-  {
-    name = "agate";
-    meta = with lib.maintainers; { maintainers = [ jk ]; };
+{ pkgs, lib, ... }:
+{
+  name = "agate";
+  meta = with lib.maintainers; { maintainers = [ jk ]; };
 
-    nodes = {
-      geminiserver = { pkgs, ... }: {
-        services.agate = {
-          enable = true;
-          hostnames = [ "localhost" ];
-          contentDir = pkgs.writeTextDir "index.gmi" ''
-            # Hello NixOS!
-          '';
-        };
+  nodes = {
+    geminiserver = { pkgs, ... }: {
+      services.agate = {
+        enable = true;
+        hostnames = [ "localhost" ];
+        contentDir = pkgs.writeTextDir "index.gmi" ''
+          # Hello NixOS!
+        '';
       };
     };
+  };
 
-    testScript = { nodes, ... }: ''
-      geminiserver.wait_for_unit("agate")
-      geminiserver.wait_for_open_port(1965)
+  testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("agate")
+    geminiserver.wait_for_open_port(1965)
 
-      with subtest("check is serving over gemini"):
-        response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
-        print(response)
-        assert "Hello NixOS!" in response
-    '';
-  }
-)
+    with subtest("check is serving over gemini"):
+      response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
+      print(response)
+      assert "Hello NixOS!" in response
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-servers/stargazer.nix b/nixpkgs/nixos/tests/web-servers/stargazer.nix
new file mode 100644
index 000000000000..c522cfee5dbc
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-servers/stargazer.nix
@@ -0,0 +1,31 @@
+{ pkgs, lib, ... }:
+{
+  name = "stargazer";
+  meta = with lib.maintainers; { maintainers = [ gaykitty ]; };
+
+  nodes = {
+    geminiserver = { pkgs, ... }: {
+      services.stargazer = {
+        enable = true;
+        routes = [
+          {
+            route = "localhost";
+            root = toString (pkgs.writeTextDir "index.gmi" ''
+              # Hello NixOS!
+            '');
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("stargazer")
+    geminiserver.wait_for_open_port(1965)
+
+    with subtest("check is serving over gemini"):
+      response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
+      print(response)
+      assert "Hello NixOS!" in response
+  '';
+}
diff --git a/nixpkgs/nixos/tests/webhook.nix b/nixpkgs/nixos/tests/webhook.nix
new file mode 100644
index 000000000000..ed7051408640
--- /dev/null
+++ b/nixpkgs/nixos/tests/webhook.nix
@@ -0,0 +1,65 @@
+{ pkgs, ... }:
+let
+  forwardedPort = 19000;
+  internalPort = 9000;
+in
+{
+  name = "webhook";
+
+  nodes = {
+    webhookMachine = { pkgs, ... }: {
+      virtualisation.forwardPorts = [{
+        host.port = forwardedPort;
+        guest.port = internalPort;
+      }];
+      services.webhook = {
+        enable = true;
+        port = internalPort;
+        openFirewall = true;
+        hooks = {
+          echo = {
+            execute-command = "echo";
+            response-message = "Webhook is reachable!";
+          };
+        };
+        hooksTemplated = {
+          echoTemplate = ''
+            {
+              "id": "echo-template",
+              "execute-command": "echo",
+              "response-message": "{{ getenv "WEBHOOK_MESSAGE" }}"
+            }
+          '';
+        };
+        environment.WEBHOOK_MESSAGE = "Templates are working!";
+      };
+    };
+  };
+
+  extraPythonPackages = p: [
+    p.requests
+    p.types-requests
+  ];
+
+  testScript = { nodes, ... }: ''
+    import requests
+    webhookMachine.wait_for_unit("webhook")
+    webhookMachine.wait_for_open_port(${toString internalPort})
+
+    with subtest("Check that webhooks can be called externally"):
+      response = requests.get("http://localhost:${toString forwardedPort}/hooks/echo")
+      print(f"Response code: {response.status_code}")
+      print("Response: %r" % response.content)
+
+      assert response.status_code == 200
+      assert response.content == b"Webhook is reachable!"
+
+    with subtest("Check that templated webhooks can be called externally"):
+      response = requests.get("http://localhost:${toString forwardedPort}/hooks/echo-template")
+      print(f"Response code: {response.status_code}")
+      print("Response: %r" % response.content)
+
+      assert response.status_code == 200
+      assert response.content == b"Templates are working!"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/wiki-js.nix b/nixpkgs/nixos/tests/wiki-js.nix
index c3541be5d8b5..fd054a9c5909 100644
--- a/nixpkgs/nixos/tests/wiki-js.nix
+++ b/nixpkgs/nixos/tests/wiki-js.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   };
 
   nodes.machine = { pkgs, ... }: {
-    virtualisation.memorySize = 2048;
+    virtualisation.memorySize = 2047;
     services.wiki-js = {
       enable = true;
       settings.db.host = "/run/postgresql";
diff --git a/nixpkgs/nixos/tests/wine.nix b/nixpkgs/nixos/tests/wine.nix
index 8a64c3179c51..7cbe7ac94f1e 100644
--- a/nixpkgs/nixos/tests/wine.nix
+++ b/nixpkgs/nixos/tests/wine.nix
@@ -44,5 +44,8 @@ in
 listToAttrs (
   map (makeWineTest "winePackages" [ hello32 ]) variants
   ++ optionals pkgs.stdenv.is64bit
-    (map (makeWineTest "wineWowPackages" [ hello32 hello64 ]) variants)
+    (map (makeWineTest "wineWowPackages" [ hello32 hello64 ])
+         # This wayland combination times out after spending many hours.
+         # https://hydra.nixos.org/job/nixos/trunk-combined/nixos.tests.wine.wineWowPackages-wayland.x86_64-linux
+         (pkgs.lib.remove "wayland" variants))
 )
diff --git a/nixpkgs/nixos/tests/wireguard/basic.nix b/nixpkgs/nixos/tests/wireguard/basic.nix
index 36ab226cde0e..96b0a681c364 100644
--- a/nixpkgs/nixos/tests/wireguard/basic.nix
+++ b/nixpkgs/nixos/tests/wireguard/basic.nix
@@ -1,5 +1,4 @@
-{ kernelPackages ? null }:
-import ../make-test-python.nix ({ pkgs, lib, ...} :
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ...} :
   let
     wg-snakeoil-keys = import ./snakeoil-keys.nix;
     peer = (import ./make-peer.nix) { inherit lib; };
diff --git a/nixpkgs/nixos/tests/wireguard/default.nix b/nixpkgs/nixos/tests/wireguard/default.nix
index dedb321ff2ef..c30f1b74770b 100644
--- a/nixpkgs/nixos/tests/wireguard/default.nix
+++ b/nixpkgs/nixos/tests/wireguard/default.nix
@@ -7,10 +7,11 @@
 with pkgs.lib;
 
 let
-  tests = let callTest = p: flip (import p) { inherit system pkgs; }; in {
+  tests = let callTest = p: args: import p ({ inherit system pkgs; } // args); in {
     basic = callTest ./basic.nix;
     namespaces = callTest ./namespaces.nix;
     wg-quick = callTest ./wg-quick.nix;
+    wg-quick-nftables = args: callTest ./wg-quick.nix ({ nftables = true; } // args);
     generated = callTest ./generated.nix;
   };
 in
diff --git a/nixpkgs/nixos/tests/wireguard/generated.nix b/nixpkgs/nixos/tests/wireguard/generated.nix
index 84a35d29b453..c58f7a75071e 100644
--- a/nixpkgs/nixos/tests/wireguard/generated.nix
+++ b/nixpkgs/nixos/tests/wireguard/generated.nix
@@ -1,5 +1,4 @@
-{ kernelPackages ? null }:
-import ../make-test-python.nix ({ pkgs, lib, ... } : {
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ... } : {
   name = "wireguard-generated";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ ma27 grahamc ];
diff --git a/nixpkgs/nixos/tests/wireguard/namespaces.nix b/nixpkgs/nixos/tests/wireguard/namespaces.nix
index 93dc84a8768e..d0eb009e1107 100644
--- a/nixpkgs/nixos/tests/wireguard/namespaces.nix
+++ b/nixpkgs/nixos/tests/wireguard/namespaces.nix
@@ -1,5 +1,3 @@
-{ kernelPackages ? null }:
-
 let
   listenPort = 12345;
   socketNamespace = "foo";
@@ -15,7 +13,7 @@ let
 
 in
 
-import ../make-test-python.nix ({ pkgs, lib, ... } : {
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ... } : {
   name = "wireguard-with-namespaces";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ asymmetric ];
@@ -41,6 +39,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... } : {
         preSetup = ''
           ip netns add ${interfaceNamespace}
         '';
+        mtu = 1280;
         inherit interfaceNamespace;
       };
     };
diff --git a/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix b/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix
index 55ad582d4059..c979f0e0c8a9 100644
--- a/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix
+++ b/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix
@@ -6,6 +6,7 @@
 
   peer1 = {
     privateKey = "uO8JVo/sanx2DOM0L9GUEtzKZ82RGkRnYgpaYc7iXmg=";
-    publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI=";
+    # readFile'd keys may have trailing newlines, emulate this
+    publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI=\n";
   };
 }
diff --git a/nixpkgs/nixos/tests/wireguard/wg-quick.nix b/nixpkgs/nixos/tests/wireguard/wg-quick.nix
index bc2cba911888..ec2b8d7f2d9d 100644
--- a/nixpkgs/nixos/tests/wireguard/wg-quick.nix
+++ b/nixpkgs/nixos/tests/wireguard/wg-quick.nix
@@ -1,9 +1,13 @@
-{ kernelPackages ? null }:
-
-import ../make-test-python.nix ({ pkgs, lib, ... }:
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, nftables ? false, ... }:
   let
     wg-snakeoil-keys = import ./snakeoil-keys.nix;
-    peer = (import ./make-peer.nix) { inherit lib; };
+    peer = import ./make-peer.nix { inherit lib; };
+    commonConfig = {
+      boot.kernelPackages = lib.mkIf (kernelPackages != null) kernelPackages;
+      networking.nftables.enable = nftables;
+      # Make sure iptables doesn't work with nftables enabled
+      boot.blacklistedKernelModules = lib.mkIf nftables [ "nft_compat" ];
+    };
   in
   {
     name = "wg-quick";
@@ -15,47 +19,51 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
       peer0 = peer {
         ip4 = "192.168.0.1";
         ip6 = "fd00::1";
-        extraConfig = {
-          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
-          networking.firewall.allowedUDPPorts = [ 23542 ];
-          networking.wg-quick.interfaces.wg0 = {
-            address = [ "10.23.42.1/32" "fc00::1/128" ];
-            listenPort = 23542;
+        extraConfig = lib.mkMerge [
+          commonConfig
+          {
+            networking.firewall.allowedUDPPorts = [ 23542 ];
+            networking.wg-quick.interfaces.wg0 = {
+              address = [ "10.23.42.1/32" "fc00::1/128" ];
+              listenPort = 23542;
 
-            inherit (wg-snakeoil-keys.peer0) privateKey;
+              inherit (wg-snakeoil-keys.peer0) privateKey;
 
-            peers = lib.singleton {
-              allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
+              peers = lib.singleton {
+                allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
 
-              inherit (wg-snakeoil-keys.peer1) publicKey;
-            };
+                inherit (wg-snakeoil-keys.peer1) publicKey;
+              };
 
-            dns = [ "10.23.42.2" "fc00::2" "wg0" ];
-          };
-        };
+              dns = [ "10.23.42.2" "fc00::2" "wg0" ];
+            };
+          }
+        ];
       };
 
       peer1 = peer {
         ip4 = "192.168.0.2";
         ip6 = "fd00::2";
-        extraConfig = {
-          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
-          networking.useNetworkd = true;
-          networking.wg-quick.interfaces.wg0 = {
-            address = [ "10.23.42.2/32" "fc00::2/128" ];
-            inherit (wg-snakeoil-keys.peer1) privateKey;
+        extraConfig = lib.mkMerge [
+          commonConfig
+          {
+            networking.useNetworkd = true;
+            networking.wg-quick.interfaces.wg0 = {
+              address = [ "10.23.42.2/32" "fc00::2/128" ];
+              inherit (wg-snakeoil-keys.peer1) privateKey;
 
-            peers = lib.singleton {
-              allowedIPs = [ "0.0.0.0/0" "::/0" ];
-              endpoint = "192.168.0.1:23542";
-              persistentKeepalive = 25;
+              peers = lib.singleton {
+                allowedIPs = [ "0.0.0.0/0" "::/0" ];
+                endpoint = "192.168.0.1:23542";
+                persistentKeepalive = 25;
 
-              inherit (wg-snakeoil-keys.peer0) publicKey;
-            };
+                inherit (wg-snakeoil-keys.peer0) publicKey;
+              };
 
-            dns = [ "10.23.42.1" "fc00::1" "wg0" ];
-          };
-        };
+              dns = [ "10.23.42.1" "fc00::1" "wg0" ];
+            };
+          }
+        ];
       };
     };
 
diff --git a/nixpkgs/nixos/tests/wordpress.nix b/nixpkgs/nixos/tests/wordpress.nix
index 416a20aa7fe8..4e322774fef5 100644
--- a/nixpkgs/nixos/tests/wordpress.nix
+++ b/nixpkgs/nixos/tests/wordpress.nix
@@ -1,6 +1,6 @@
-import ./make-test-python.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ lib, pkgs, ... }:
 
-{
+rec {
   name = "wordpress";
   meta = with pkgs.lib.maintainers; {
     maintainers = [
@@ -10,17 +10,22 @@ import ./make-test-python.nix ({ pkgs, ... }:
     ];
   };
 
-  nodes = {
-    wp_httpd = { ... }: {
+  nodes = lib.foldl (a: version: let
+    package = pkgs."wordpress${version}";
+  in a // {
+    "wp${version}_httpd" = _: {
       services.httpd.adminAddr = "webmaster@site.local";
       services.httpd.logPerVirtualHost = true;
 
+      services.wordpress.webserver = "httpd";
       services.wordpress.sites = {
         "site1.local" = {
           database.tablePrefix = "site1_";
+          inherit package;
         };
         "site2.local" = {
           database.tablePrefix = "site2_";
+          inherit package;
         };
       };
 
@@ -28,14 +33,16 @@ import ./make-test-python.nix ({ pkgs, ... }:
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
 
-    wp_nginx = { ... }: {
+    "wp${version}_nginx" = _: {
       services.wordpress.webserver = "nginx";
       services.wordpress.sites = {
         "site1.local" = {
           database.tablePrefix = "site1_";
+          inherit package;
         };
         "site2.local" = {
           database.tablePrefix = "site2_";
+          inherit package;
         };
       };
 
@@ -43,34 +50,38 @@ import ./make-test-python.nix ({ pkgs, ... }:
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
 
-    wp_caddy = { ... }: {
+    "wp${version}_caddy" = _: {
       services.wordpress.webserver = "caddy";
       services.wordpress.sites = {
         "site1.local" = {
           database.tablePrefix = "site1_";
+          inherit package;
         };
         "site2.local" = {
           database.tablePrefix = "site2_";
+          inherit package;
         };
       };
 
       networking.firewall.allowedTCPPorts = [ 80 ];
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
-  };
+  }) {} [
+    "6_1" "6_2"
+  ];
 
   testScript = ''
     import re
 
     start_all()
 
-    wp_httpd.wait_for_unit("httpd")
-    wp_nginx.wait_for_unit("nginx")
-    wp_caddy.wait_for_unit("caddy")
+    ${lib.concatStrings (lib.mapAttrsToList (name: value: ''
+      ${name}.wait_for_unit("${(value null).services.wordpress.webserver}")
+    '') nodes)}
 
     site_names = ["site1.local", "site2.local"]
 
-    for machine in (wp_httpd, wp_nginx, wp_caddy):
+    for machine in (${lib.concatStringsSep ", " (builtins.attrNames nodes)}):
         for site_name in site_names:
             machine.wait_for_unit(f"phpfpm-wordpress-{site_name}")
 
diff --git a/nixpkgs/nixos/tests/wrappers.nix b/nixpkgs/nixos/tests/wrappers.nix
new file mode 100644
index 000000000000..08c1ad0b6b99
--- /dev/null
+++ b/nixpkgs/nixos/tests/wrappers.nix
@@ -0,0 +1,79 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  userUid = 1000;
+  usersGid = 100;
+  busybox = pkgs : pkgs.busybox.override {
+    # Without this, the busybox binary drops euid to ruid for most applets, including id.
+    # See https://bugs.busybox.net/show_bug.cgi?id=15101
+    extraConfig = "CONFIG_FEATURE_SUID n";
+  };
+in
+{
+  name = "wrappers";
+
+  nodes.machine = { config, pkgs, ... }: {
+    ids.gids.users = usersGid;
+
+    users.users = {
+      regular = {
+        uid = userUid;
+        isNormalUser = true;
+      };
+    };
+
+    security.wrappers = {
+      suidRoot = {
+        owner = "root";
+        group = "root";
+        setuid = true;
+        source = "${busybox pkgs}/bin/busybox";
+        program = "suid_root_busybox";
+      };
+      sgidRoot = {
+        owner = "root";
+        group = "root";
+        setgid = true;
+        source = "${busybox pkgs}/bin/busybox";
+        program = "sgid_root_busybox";
+      };
+      withChown = {
+        owner = "root";
+        group = "root";
+        source = "${pkgs.libcap}/bin/capsh";
+        program = "capsh_with_chown";
+        capabilities = "cap_chown+ep";
+      };
+    };
+  };
+
+  testScript =
+    ''
+      def cmd_as_regular(cmd):
+        return "su -l regular -c '{0}'".format(cmd)
+
+      def test_as_regular(cmd, expected):
+        out = machine.succeed(cmd_as_regular(cmd)).strip()
+        assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out)
+
+      test_as_regular('${busybox pkgs}/bin/busybox id -u', '${toString userUid}')
+      test_as_regular('${busybox pkgs}/bin/busybox id -ru', '${toString userUid}')
+      test_as_regular('${busybox pkgs}/bin/busybox id -g', '${toString usersGid}')
+      test_as_regular('${busybox pkgs}/bin/busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -u', '0')
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -g', '0')
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
+
+      # We are only testing the permitted set, because it's easiest to look at with capsh.
+      machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
+      machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))
+      machine.succeed(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_CHOWN'))
+      machine.fail(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_SYS_ADMIN'))
+    '';
+})
diff --git a/nixpkgs/nixos/tests/xautolock.nix b/nixpkgs/nixos/tests/xautolock.nix
index 529567e07971..cf81c4a1cf05 100644
--- a/nixpkgs/nixos/tests/xautolock.nix
+++ b/nixpkgs/nixos/tests/xautolock.nix
@@ -1,10 +1,8 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }:
 
-with lib;
-
 {
   name = "xautolock";
-  meta.maintainers = with pkgs.lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   nodes.machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
diff --git a/nixpkgs/nixos/tests/xfce.nix b/nixpkgs/nixos/tests/xfce.nix
index 31f00f77c40d..3758ccbccf42 100644
--- a/nixpkgs/nixos/tests/xfce.nix
+++ b/nixpkgs/nixos/tests/xfce.nix
@@ -26,7 +26,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     };
 
   testScript = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
   in ''
       machine.wait_for_x()
       machine.wait_for_file("${user.home}/.Xauthority")
diff --git a/nixpkgs/nixos/tests/xmpp/prosody.nix b/nixpkgs/nixos/tests/xmpp/prosody.nix
index 14eab56fb821..045ae6430fd4 100644
--- a/nixpkgs/nixos/tests/xmpp/prosody.nix
+++ b/nixpkgs/nixos/tests/xmpp/prosody.nix
@@ -42,7 +42,7 @@ in import ../make-test-python.nix {
         ${nodes.server.config.networking.primaryIPAddress} uploads.example.com
       '';
       environment.systemPackages = [
-        (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; })
+        (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = "example.com"; })
       ];
     };
     server = { config, pkgs, ... }: {
@@ -82,6 +82,7 @@ in import ../make-test-python.nix {
 
   testScript = { nodes, ... }: ''
     # Check with sqlite storage
+    start_all()
     server.wait_for_unit("prosody.service")
     server.succeed('prosodyctl status | grep "Prosody is running"')
 
diff --git a/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix b/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix
index 4c009464b704..8ccac0612491 100644
--- a/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix
+++ b/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix
@@ -12,6 +12,7 @@ in writeScriptBin "send-message" ''
 #!${(python3.withPackages (ps: [ ps.slixmpp ])).interpreter}
 import logging
 import sys
+import signal
 from types import MethodType
 
 from slixmpp import ClientXMPP
@@ -64,8 +65,13 @@ class CthonTest(ClientXMPP):
         log.info('MUC join success!')
         log.info('XMPP SCRIPT TEST SUCCESS')
 
+def timeout_handler(signalnum, stackframe):
+    print('ERROR: xmpp-sendmessage timed out')
+    sys.exit(1)
 
 if __name__ == '__main__':
+    signal.signal(signal.SIGALRM, timeout_handler)
+    signal.alarm(120)
     logging.basicConfig(level=logging.DEBUG,
                         format='%(levelname)-8s %(message)s')
 
@@ -76,7 +82,7 @@ if __name__ == '__main__':
     ct.register_plugin('xep_0363')
     # MUC
     ct.register_plugin('xep_0045')
-    ct.connect(("server", 5222))
+    ct.connect(("${connectTo}", 5222))
     ct.process(forever=False)
 
     if not ct.test_succeeded:
diff --git a/nixpkgs/nixos/tests/xpadneo.nix b/nixpkgs/nixos/tests/xpadneo.nix
new file mode 100644
index 000000000000..c7b72831fce8
--- /dev/null
+++ b/nixpkgs/nixos/tests/xpadneo.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "xpadneo";
+  meta.maintainers = with lib.maintainers; [ kira-bruneau ];
+
+  nodes = {
+    machine = {
+      config.hardware.xpadneo.enable = true;
+    };
+  };
+
+  # This is just a sanity check to make sure the module was
+  # loaded. We'd have to find some way to mock an xbox controller if
+  # we wanted more in-depth testing.
+  testScript = ''
+    machine.start();
+    machine.succeed("modinfo hid_xpadneo | grep 'version:\s\+${pkgs.linuxPackages.xpadneo.version}'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xss-lock.nix b/nixpkgs/nixos/tests/xss-lock.nix
index c927d9274e65..e4e41a5aa797 100644
--- a/nixpkgs/nixos/tests/xss-lock.nix
+++ b/nixpkgs/nixos/tests/xss-lock.nix
@@ -1,10 +1,6 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
-
-with lib;
-
-{
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "xss-lock";
-  meta.maintainers = with pkgs.lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   nodes = {
     simple = {
diff --git a/nixpkgs/nixos/tests/yabar.nix b/nixpkgs/nixos/tests/yabar.nix
index ff7a47ae6370..212a8ce4bbf5 100644
--- a/nixpkgs/nixos/tests/yabar.nix
+++ b/nixpkgs/nixos/tests/yabar.nix
@@ -1,12 +1,6 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
-
-with lib;
-
-{
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "yabar";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ];
-  };
+  meta.maintainers = [ ];
 
   nodes.machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
diff --git a/nixpkgs/nixos/tests/yggdrasil.nix b/nixpkgs/nixos/tests/yggdrasil.nix
index b60a0e6b06cc..eaf14e29acb0 100644
--- a/nixpkgs/nixos/tests/yggdrasil.nix
+++ b/nixpkgs/nixos/tests/yggdrasil.nix
@@ -10,8 +10,13 @@ let
     InterfacePeers = {
       eth1 = [ "tcp://192.168.1.200:12345" ];
     };
-    MulticastInterfaces = [ "eth1" ];
-    LinkLocalTCPPort = 54321;
+    MulticastInterfaces = [ {
+      Regex = ".*";
+      Beacon = true;
+      Listen = true;
+      Port = 54321;
+      Priority = 0;
+    } ];
     PublicKey = "2b6f918b6c1a4b54d6bcde86cf74e074fb32ead4ee439b7930df2aa60c825186";
     PrivateKey = "0c4a24acd3402722ce9277ed179f4a04b895b49586493c25fbaed60653d857d62b6f918b6c1a4b54d6bcde86cf74e074fb32ead4ee439b7930df2aa60c825186";
   };
@@ -115,8 +120,12 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
           settings = {
             IfTAPMode = true;
             IfName = "ygg0";
-            MulticastInterfaces = [ "eth1" ];
-            LinkLocalTCPPort = 43210;
+            MulticastInterfaces = [
+              {
+                Port = 43210;
+              }
+            ];
+            openMulticastPort = true;
           };
           persistentKeys = true;
         };
diff --git a/nixpkgs/nixos/tests/zammad.nix b/nixpkgs/nixos/tests/zammad.nix
index 4e466f6e3b9b..7a2d40e82b3e 100644
--- a/nixpkgs/nixos/tests/zammad.nix
+++ b/nixpkgs/nixos/tests/zammad.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix (
   {
     name = "zammad";
 
-    meta.maintainers = with lib.maintainers; [ garbas taeer ];
+    meta.maintainers = with lib.maintainers; [ garbas taeer n0emis ];
 
     nodes.machine = { config, ... }: {
       services.zammad.enable = true;
diff --git a/nixpkgs/nixos/tests/zfs.nix b/nixpkgs/nixos/tests/zfs.nix
index 0b44961a3deb..8e52e0065745 100644
--- a/nixpkgs/nixos/tests/zfs.nix
+++ b/nixpkgs/nixos/tests/zfs.nix
@@ -8,110 +8,184 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 let
 
   makeZfsTest = name:
-    { kernelPackage ? if enableUnstable then pkgs.linuxPackages_latest else pkgs.linuxPackages
+    { kernelPackage ? if enableUnstable
+                      then pkgs.zfsUnstable.latestCompatibleLinuxPackages
+                      else pkgs.linuxPackages
     , enableUnstable ? false
+    , enableSystemdStage1 ? false
     , extraTest ? ""
     }:
     makeTest {
       name = "zfs-" + name;
       meta = with pkgs.lib.maintainers; {
-        maintainers = [ adisbladis ];
+        maintainers = [ adisbladis elvishjerricco ];
       };
 
       nodes.machine = { pkgs, lib, ... }:
         let
           usersharePath = "/var/lib/samba/usershares";
         in {
-        virtualisation.emptyDiskImages = [ 4096 ];
+        virtualisation = {
+          emptyDiskImages = [ 4096 4096 ];
+          useBootLoader = true;
+          useEFIBoot = true;
+        };
+        boot.loader.systemd-boot.enable = true;
+        boot.loader.timeout = 0;
+        boot.loader.efi.canTouchEfiVariables = true;
         networking.hostId = "deadbeef";
         boot.kernelPackages = kernelPackage;
         boot.supportedFilesystems = [ "zfs" ];
         boot.zfs.enableUnstable = enableUnstable;
+        boot.initrd.systemd.enable = enableSystemdStage1;
 
-        services.samba = {
-          enable = true;
-          extraConfig = ''
-            registry shares = yes
-            usershare path = ${usersharePath}
-            usershare allow guests = yes
-            usershare max shares = 100
-            usershare owner only = no
-          '';
+        environment.systemPackages = [ pkgs.parted ];
+
+        # /dev/disk/by-id doesn't get populated in the NixOS test framework
+        boot.zfs.devNodes = "/dev/disk/by-uuid";
+
+        specialisation.samba.configuration = {
+          services.samba = {
+            enable = true;
+            extraConfig = ''
+              registry shares = yes
+              usershare path = ${usersharePath}
+              usershare allow guests = yes
+              usershare max shares = 100
+              usershare owner only = no
+            '';
+          };
+          systemd.services.samba-smbd.serviceConfig.ExecStartPre =
+            "${pkgs.coreutils}/bin/mkdir -m +t -p ${usersharePath}";
+          virtualisation.fileSystems = {
+            "/tmp/mnt" = {
+              device = "rpool/root";
+              fsType = "zfs";
+            };
+          };
         };
-        systemd.services.samba-smbd.serviceConfig.ExecStartPre =
-          "${pkgs.coreutils}/bin/mkdir -m +t -p ${usersharePath}";
 
-        environment.systemPackages = [ pkgs.parted ];
+        specialisation.encryption.configuration = {
+          boot.zfs.requestEncryptionCredentials = [ "automatic" ];
+          virtualisation.fileSystems."/automatic" = {
+            device = "automatic";
+            fsType = "zfs";
+          };
+          virtualisation.fileSystems."/manual" = {
+            device = "manual";
+            fsType = "zfs";
+          };
+          virtualisation.fileSystems."/manual/encrypted" = {
+            device = "manual/encrypted";
+            fsType = "zfs";
+            options = [ "noauto" ];
+          };
+          virtualisation.fileSystems."/manual/httpkey" = {
+            device = "manual/httpkey";
+            fsType = "zfs";
+            options = [ "noauto" ];
+          };
+        };
 
-        # Setup regular fileSystems machinery to ensure forceImportAll can be
-        # tested via the regular service units.
-        virtualisation.fileSystems = {
-          "/forcepool" = {
+        specialisation.forcepool.configuration = {
+          systemd.services.zfs-import-forcepool.wantedBy = lib.mkVMOverride [ "forcepool.mount" ];
+          systemd.targets.zfs.wantedBy = lib.mkVMOverride [];
+          boot.zfs.forceImportAll = true;
+          virtualisation.fileSystems."/forcepool" = {
             device = "forcepool";
             fsType = "zfs";
             options = [ "noauto" ];
           };
         };
 
-        # forcepool doesn't exist at first boot, and we need to manually test
-        # the import after tweaking the hostId.
-        systemd.services.zfs-import-forcepool.wantedBy = lib.mkVMOverride [];
-        systemd.targets.zfs.wantedBy = lib.mkVMOverride [];
-        boot.zfs.forceImportAll = true;
-        # /dev/disk/by-id doesn't get populated in the NixOS test framework
-        boot.zfs.devNodes = "/dev/disk/by-uuid";
+        services.nginx = {
+          enable = true;
+          virtualHosts = {
+            localhost = {
+              locations = {
+                "/zfskey" = {
+                  return = ''200 "httpkeyabc"'';
+                };
+              };
+            };
+          };
+        };
       };
 
       testScript = ''
+        machine.wait_for_unit("multi-user.target")
         machine.succeed(
-            "modprobe zfs",
             "zpool status",
-            "ls /dev",
-            "mkdir /tmp/mnt",
-            "udevadm settle",
             "parted --script /dev/vdb mklabel msdos",
             "parted --script /dev/vdb -- mkpart primary 1024M -1s",
-            "udevadm settle",
-            "zpool create rpool /dev/vdb1",
-            "zfs create -o mountpoint=legacy rpool/root",
-            # shared datasets cannot have legacy mountpoint
-            "zfs create rpool/shared_smb",
-            "mount -t zfs rpool/root /tmp/mnt",
-            "udevadm settle",
-            # wait for samba services
-            "systemctl is-system-running --wait",
-            "zfs set sharesmb=on rpool/shared_smb",
-            "zfs share rpool/shared_smb",
-            "smbclient -gNL localhost | grep rpool_shared_smb",
-            "umount /tmp/mnt",
-            "zpool destroy rpool",
-            "udevadm settle",
+            "parted --script /dev/vdc mklabel msdos",
+            "parted --script /dev/vdc -- mkpart primary 1024M -1s",
         )
 
-        machine.succeed(
-            'echo password | zpool create -o altroot="/tmp/mnt" '
-            + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1",
-            "zfs create -o mountpoint=legacy rpool/root",
-            "mount -t zfs rpool/root /tmp/mnt",
-            "udevadm settle",
-            "umount /tmp/mnt",
-            "zpool destroy rpool",
-            "udevadm settle",
-        )
+        with subtest("sharesmb works"):
+            machine.succeed(
+                "zpool create rpool /dev/vdb1",
+                "zfs create -o mountpoint=legacy rpool/root",
+                # shared datasets cannot have legacy mountpoint
+                "zfs create rpool/shared_smb",
+                "bootctl set-default nixos-generation-1-specialisation-samba.conf",
+                "sync",
+            )
+            machine.crash()
+            machine.wait_for_unit("multi-user.target")
+            machine.succeed(
+                "zfs set sharesmb=on rpool/shared_smb",
+                "zfs share rpool/shared_smb",
+                "smbclient -gNL localhost | grep rpool_shared_smb",
+                "umount /tmp/mnt",
+                "zpool destroy rpool",
+            )
+
+        with subtest("encryption works"):
+            machine.succeed(
+                'echo password | zpool create -O mountpoint=legacy '
+                + "-O encryption=aes-256-gcm -O keyformat=passphrase automatic /dev/vdb1",
+                "zpool create -O mountpoint=legacy manual /dev/vdc1",
+                "echo otherpass | zfs create "
+                + "-o encryption=aes-256-gcm -o keyformat=passphrase manual/encrypted",
+                "zfs create -o encryption=aes-256-gcm -o keyformat=passphrase "
+                + "-o keylocation=http://localhost/zfskey manual/httpkey",
+                "bootctl set-default nixos-generation-1-specialisation-encryption.conf",
+                "sync",
+                "zpool export automatic",
+                "zpool export manual",
+            )
+            machine.crash()
+            machine.start()
+            machine.wait_for_console_text("Starting password query on")
+            machine.send_console("password\n")
+            machine.wait_for_unit("multi-user.target")
+            machine.succeed(
+                "zfs get -Ho value keystatus manual/encrypted | grep -Fx unavailable",
+                "echo otherpass | zfs load-key manual/encrypted",
+                "systemctl start manual-encrypted.mount",
+                "zfs load-key manual/httpkey",
+                "systemctl start manual-httpkey.mount",
+                "umount /automatic /manual/encrypted /manual/httpkey /manual",
+                "zpool destroy automatic",
+                "zpool destroy manual",
+            )
 
         with subtest("boot.zfs.forceImportAll works"):
             machine.succeed(
                 "rm /etc/hostid",
                 "zgenhostid deadcafe",
                 "zpool create forcepool /dev/vdb1 -O mountpoint=legacy",
+                "bootctl set-default nixos-generation-1-specialisation-forcepool.conf",
+                "rm /etc/hostid",
+                "sync",
             )
-            machine.shutdown()
-            machine.start()
-            machine.succeed("udevadm settle")
+            machine.crash()
+            machine.wait_for_unit("multi-user.target")
             machine.fail("zpool import forcepool")
             machine.succeed(
-                "systemctl start zfs-import-forcepool.service",
-                "mount -t zfs forcepool /tmp/mnt",
+                "systemctl start forcepool.mount",
+                "mount | grep forcepool",
             )
       '' + extraTest;
 
@@ -126,6 +200,11 @@ in {
     enableUnstable = true;
   };
 
+  unstableWithSystemdStage1 = makeZfsTest "unstable" {
+    enableUnstable = true;
+    enableSystemdStage1 = true;
+  };
+
   installer = (import ./installer.nix { }).zfsroot;
 
   expand-partitions = makeTest {
diff --git a/nixpkgs/nixos/tests/zigbee2mqtt.nix b/nixpkgs/nixos/tests/zigbee2mqtt.nix
index 1592202fb3a7..1a40d175df83 100644
--- a/nixpkgs/nixos/tests/zigbee2mqtt.nix
+++ b/nixpkgs/nixos/tests/zigbee2mqtt.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }:
-
   {
+    name = "zigbee2mqtt";
     nodes.machine = { pkgs, ... }:
       {
         services.zigbee2mqtt = {
diff --git a/nixpkgs/nixos/tests/zram-generator.nix b/nixpkgs/nixos/tests/zram-generator.nix
new file mode 100644
index 000000000000..2be7bd2e05b1
--- /dev/null
+++ b/nixpkgs/nixos/tests/zram-generator.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix {
+  name = "zram-generator";
+
+  nodes = {
+    single = { ... }: {
+      virtualisation = {
+        emptyDiskImages = [ 512 ];
+      };
+      zramSwap = {
+        enable = true;
+        priority = 10;
+        algorithm = "lz4";
+        swapDevices = 1;
+        memoryPercent = 30;
+        memoryMax = 10 * 1024 * 1024;
+        writebackDevice = "/dev/vdb";
+      };
+    };
+    machine = { ... }: {
+      zramSwap = {
+        enable = true;
+        priority = 10;
+        algorithm = "lz4";
+        swapDevices = 2;
+        memoryPercent = 30;
+        memoryMax = 10 * 1024 * 1024;
+      };
+    };
+  };
+
+  testScript = ''
+    single.wait_for_unit("systemd-zram-setup@zram0.service")
+
+    machine.wait_for_unit("systemd-zram-setup@zram0.service")
+    machine.wait_for_unit("systemd-zram-setup@zram1.service")
+    zram = machine.succeed("zramctl --noheadings --raw")
+    swap = machine.succeed("swapon --show --noheadings")
+    for i in range(2):
+        assert f"/dev/zram{i} lz4 10M" in zram
+        assert f"/dev/zram{i} partition  10M" in swap
+  '';
+}
diff --git a/nixpkgs/nixos/tests/zrepl.nix b/nixpkgs/nixos/tests/zrepl.nix
index 85dd834a6aaf..b16c7eddc7ae 100644
--- a/nixpkgs/nixos/tests/zrepl.nix
+++ b/nixpkgs/nixos/tests/zrepl.nix
@@ -1,5 +1,7 @@
 import ./make-test-python.nix (
   {
+    name = "zrepl";
+
     nodes.host = {config, pkgs, ...}: {
       config = {
         # Prerequisites for ZFS and tests.
@@ -56,8 +58,8 @@ import ./make-test-python.nix (
           out = host.succeed("curl -f localhost:9811/metrics")
 
           assert (
-              "zrepl_version_daemon" in out
-          ), "zrepl version metric was not found in Prometheus output"
+              "zrepl_start_time" in out
+          ), "zrepl start time metric was not found in Prometheus output"
 
           assert (
               "zrepl_zfs_snapshot_duration_count{filesystem=\"test\"}" in out